From a963469268c9ed41e9706f5ed515bff8757e63ff Mon Sep 17 00:00:00 2001 From: ktsn Date: Wed, 13 Feb 2019 18:53:58 +0800 Subject: [PATCH] fix: avoid possible infinite loop by accessing observables in error handler --- src/core/util/error.js | 32 ++++++++++------ .../features/options/errorCaptured.spec.js | 38 +++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/core/util/error.js b/src/core/util/error.js index e4604ecf230..dffd8d84c26 100644 --- a/src/core/util/error.js +++ b/src/core/util/error.js @@ -4,25 +4,33 @@ import config from '../config' import { warn } from './debug' import { inBrowser, inWeex } from './env' import { isPromise } from 'shared/util' +import { pushTarget, popTarget } from '../observer/dep' export function handleError (err: Error, vm: any, info: string) { - if (vm) { - let cur = vm - while ((cur = cur.$parent)) { - const hooks = cur.$options.errorCaptured - if (hooks) { - for (let i = 0; i < hooks.length; i++) { - try { - const capture = hooks[i].call(cur, err, vm, info) === false - if (capture) return - } catch (e) { - globalHandleError(e, cur, 'errorCaptured hook') + // Deactivate deps tracking while processing error handler to avoid possible infinite rendering. + // See: https://github.com/vuejs/vuex/issues/1505 + pushTarget() + try { + if (vm) { + let cur = vm + while ((cur = cur.$parent)) { + const hooks = cur.$options.errorCaptured + if (hooks) { + for (let i = 0; i < hooks.length; i++) { + try { + const capture = hooks[i].call(cur, err, vm, info) === false + if (capture) return + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook') + } } } } } + globalHandleError(err, vm, info) + } finally { + popTarget() } - globalHandleError(err, vm, info) } export function invokeWithErrorHandling ( diff --git a/test/unit/features/options/errorCaptured.spec.js b/test/unit/features/options/errorCaptured.spec.js index d16c057da6f..3da13b9ed38 100644 --- a/test/unit/features/options/errorCaptured.spec.js +++ b/test/unit/features/options/errorCaptured.spec.js @@ -209,4 +209,42 @@ describe('Options errorCaptured', () => { expect(calls).toEqual([1, 2, 3]) }) + + // ref: https://github.com/vuejs/vuex/issues/1505 + it('should not add watchers to render deps if they are referred from errorCaptured callback', done => { + const store = new Vue({ + data: { + errors: [] + } + }) + + const Child = { + computed: { + test() { + throw new Error('render error') + } + }, + + render(h) { + return h('div', { + attrs: { + 'data-test': this.test + } + }) + } + } + + new Vue({ + errorCaptured(error) { + store.errors.push(error) + }, + render: h => h(Child) + }).$mount() + + // Ensure not to trigger infinite loop + waitForUpdate(() => { + expect(store.errors.length).toBe(1) + expect(store.errors[0]).toEqual(new Error('render error')) + }).then(done) + }) })