Ignore default reject handler when looking for reject handlers.
authoryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 14 Aug 2014 06:57:48 +0000 (06:57 +0000)
committeryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 14 Aug 2014 06:57:48 +0000 (06:57 +0000)
LOG=N
BUG=v8:3093
R=aandrey@chromium.org

Review URL: https://codereview.chromium.org/461023002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23112 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/promise.js
test/mjsunit/es6/reject-caught-by-default-reject-handler.js [new file with mode: 0644]
test/mjsunit/es6/throw-caught-by-default-reject-handler.js [new file with mode: 0644]

index 2d8314a42fa5d8d5b0ec05638efec2d9a5ccbf03..cd96ec19244c166b5f00426a9fde56615bffa13e 100644 (file)
@@ -325,11 +325,22 @@ var lastMicrotaskId = 0;
 
   // Utility for debugger
 
+  function PromiseHasRejectHandlerRecursive(promise) {
+    var queue = GET_PRIVATE(promise, promiseOnReject);
+    if (IS_UNDEFINED(queue)) return false;
+    // Do a depth first search for a reject handler that's not
+    // the default PromiseIdRejectHandler.
+    for (var i = 0; i < queue.length; i += 2) {
+      if (queue[i] != PromiseIdRejectHandler) return true;
+      if (PromiseHasRejectHandlerRecursive(queue[i + 1])) return true;
+    }
+    return false;
+  }
+
   PromiseHasRejectHandler = function PromiseHasRejectHandler() {
     // Mark promise as already having triggered a reject event.
     SET_PRIVATE(this, promiseDebug, true);
-    var queue = GET_PRIVATE(this, promiseOnReject);
-    return !IS_UNDEFINED(queue) && queue.length > 0;
+    return PromiseHasRejectHandlerRecursive(this);
   };
 
   // -------------------------------------------------------------------
diff --git a/test/mjsunit/es6/reject-caught-by-default-reject-handler.js b/test/mjsunit/es6/reject-caught-by-default-reject-handler.js
new file mode 100644 (file)
index 0000000..7760b0e
--- /dev/null
@@ -0,0 +1,84 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-debug-as debug --allow-natives-syntax
+
+// Test debug events when we only listen to uncaught exceptions and
+// there is a catch handler for the to-be-rejected Promise.
+// We expect an Exception debug event with a promise to be triggered.
+
+Debug = debug.Debug;
+
+var expected_events = 2;
+var log = [];
+
+var resolve, reject;
+var p0 = new Promise(function(res, rej) { resolve = res; reject = rej; });
+var p1 = p0.then(function() {
+  log.push("p0.then");
+  return Promise.reject(new Error("123"));
+});
+var p2 = p1.then(function() {
+  log.push("p1.then");
+});
+
+var q = new Promise(function(res, rej) {
+  log.push("resolve q");
+  res();
+});
+
+q.then(function() {
+  log.push("resolve p");
+  resolve();
+})
+
+
+function listener(event, exec_state, event_data, data) {
+  try {
+    if (event == Debug.DebugEvent.Exception) {
+      expected_events--;
+      assertTrue(expected_events >= 0);
+      assertTrue(event_data.uncaught());
+      assertTrue(event_data.promise() instanceof Promise);
+      if (expected_events == 1) {
+        // p1 is rejected, uncaught except for its default reject handler.
+        assertEquals(0, exec_state.frameCount());
+        assertSame(p1, event_data.promise());
+      } else {
+        // p2 is rejected by p1's default reject handler.
+        assertEquals(0, exec_state.frameCount());
+        assertSame(p2, event_data.promise());
+      }
+    }
+  } catch (e) {
+    %AbortJS(e + "\n" + e.stack);
+  }
+}
+
+Debug.setBreakOnUncaughtException();
+Debug.setListener(listener);
+
+log.push("end main");
+
+function testDone(iteration) {
+  function checkResult() {
+    try {
+      assertTrue(iteration < 10);
+      if (expected_events === 0) {
+        assertEquals(["resolve q", "end main", "resolve p", "p0.then"], log);
+      } else {
+        testDone(iteration + 1);
+      }
+    } catch (e) {
+      %AbortJS(e + "\n" + e.stack);
+    }
+  }
+
+  // Run testDone through the Object.observe processing loop.
+  var dummy = {};
+  Object.observe(dummy, checkResult);
+  dummy.dummy = dummy;
+}
+
+testDone(0);
diff --git a/test/mjsunit/es6/throw-caught-by-default-reject-handler.js b/test/mjsunit/es6/throw-caught-by-default-reject-handler.js
new file mode 100644 (file)
index 0000000..89fc060
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-debug-as debug --allow-natives-syntax
+
+// Test debug events when we only listen to uncaught exceptions and
+// there is a catch handler for the to-be-rejected Promise.
+// We expect an Exception debug event with a promise to be triggered.
+
+Debug = debug.Debug;
+
+var expected_events = 2;
+var log = [];
+
+var resolve, reject;
+var p0 = new Promise(function(res, rej) { resolve = res; reject = rej; });
+var p1 = p0.then(function() {
+  log.push("p0.then");
+  throw new Error("123");  // event
+});
+var p2 = p1.then(function() {
+  log.push("p1.then");
+});
+
+var q = new Promise(function(res, rej) {
+  log.push("resolve q");
+  res();
+});
+
+q.then(function() {
+  log.push("resolve p");
+  resolve();
+})
+
+
+function listener(event, exec_state, event_data, data) {
+  try {
+    if (event == Debug.DebugEvent.Exception) {
+      expected_events--;
+      assertTrue(expected_events >= 0);
+      assertTrue(event_data.uncaught());
+      assertTrue(event_data.promise() instanceof Promise);
+      if (expected_events == 1) {
+        // p1 is rejected, uncaught except for its default reject handler.
+        assertTrue(
+            exec_state.frame(0).sourceLineText().indexOf("// event") > 0);
+        assertSame(p1, event_data.promise());
+      } else {
+        // p2 is rejected by p1's default reject handler.
+        assertEquals(0, exec_state.frameCount());
+        assertSame(p2, event_data.promise());
+      }
+    }
+  } catch (e) {
+    %AbortJS(e + "\n" + e.stack);
+  }
+}
+
+Debug.setBreakOnUncaughtException();
+Debug.setListener(listener);
+
+log.push("end main");
+
+function testDone(iteration) {
+  function checkResult() {
+    try {
+      assertTrue(iteration < 10);
+      if (expected_events === 0) {
+        assertEquals(["resolve q", "end main", "resolve p", "p0.then"], log);
+      } else {
+        testDone(iteration + 1);
+      }
+    } catch (e) {
+      %AbortJS(e + "\n" + e.stack);
+    }
+  }
+
+  // Run testDone through the Object.observe processing loop.
+  var dummy = {};
+  Object.observe(dummy, checkResult);
+  dummy.dummy = dummy;
+}
+
+testDone(0);