Преглед на файлове

fix: avoid possible infinite loop by accessing observables in error handler (#9489)

katashin преди 7 години
родител
ревизия
ee29e41ef4
променени са 2 файла, в които са добавени 58 реда и са изтрити 12 реда
  1. 20 12
      src/core/util/error.js
  2. 38 0
      test/unit/features/options/errorCaptured.spec.js

+ 20 - 12
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 (

+ 38 - 0
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)
+  })
 })