Commit 8be20eee authored by yangguo's avatar yangguo Committed by Commit bot

Debugger: correctly report uncaught rejections in Promise.all and Promise.race.

The debugger calls PromiseHasUserDefinedRejectHandler to recursively search the
tree of dependent promises for user-defined reject handlers. If no such reject
handler exists, rejecting the promise is considered an uncaught exception.

Promise.race and Promise.all interupt the link of promise dependency wrt the
search. This change fixes that link.

R=rossberg@chromium.org
BUG=chromium:439585
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#31392}
parent fa60b82b
......@@ -338,6 +338,7 @@ namespace internal {
V(normal_ic_symbol) \
V(observed_symbol) \
V(premonomorphic_symbol) \
V(promise_combined_deferred_symbol) \
V(promise_debug_marker_symbol) \
V(promise_has_handler_symbol) \
V(promise_on_resolve_symbol) \
......
......@@ -12,6 +12,8 @@
// Imports
var InternalArray = utils.InternalArray;
var promiseCombinedDeferredSymbol =
utils.ImportNow("promise_combined_deferred_symbol");
var promiseHasHandlerSymbol =
utils.ImportNow("promise_has_handler_symbol");
var promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol");
......@@ -298,6 +300,7 @@ function PromiseAll(iterable) {
var count = 0;
var i = 0;
for (var value of iterable) {
var reject = function(r) { deferred.reject(r) };
this.resolve(value).then(
// Nested scope to get closure over current i.
// TODO(arv): Use an inner let binding once available.
......@@ -306,8 +309,8 @@ function PromiseAll(iterable) {
resolutions[i] = x;
if (--count === 0) deferred.resolve(resolutions);
}
})(i),
function(r) { deferred.reject(r); });
})(i), reject);
SET_PRIVATE(reject, promiseCombinedDeferredSymbol, deferred);
++i;
++count;
}
......@@ -326,9 +329,9 @@ function PromiseRace(iterable) {
var deferred = %_CallFunction(this, PromiseDeferred);
try {
for (var value of iterable) {
this.resolve(value).then(
function(x) { deferred.resolve(x) },
function(r) { deferred.reject(r) });
var reject = function(r) { deferred.reject(r) };
this.resolve(value).then(function(x) { deferred.resolve(x) }, reject);
SET_PRIVATE(reject, promiseCombinedDeferredSymbol, deferred);
}
} catch (e) {
deferred.reject(e)
......@@ -343,8 +346,15 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
var queue = GET_PRIVATE(promise, promiseOnRejectSymbol);
if (IS_UNDEFINED(queue)) return false;
for (var i = 0; i < queue.length; i += 2) {
if (queue[i] != PromiseIdRejectHandler) return true;
if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) {
var handler = queue[i];
if (handler !== PromiseIdRejectHandler) {
var deferred = GET_PRIVATE(handler, promiseCombinedDeferredSymbol);
if (IS_UNDEFINED(deferred)) return true;
if (PromiseHasUserDefinedRejectHandlerRecursive(deferred.promise)) {
return true;
}
} else if (PromiseHasUserDefinedRejectHandlerRecursive(
queue[i + 1].promise)) {
return true;
}
}
......
// Copyright 2015 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 a
// Promise p3 created by Promise.all has a catch handler, and is rejected
// because one of the Promises p2 passed to Promise.all is rejected. We
// expect no Exception debug event to be triggered, since p3 and by
// extension p2 have a catch handler.
var Debug = debug.Debug;
var expected_events = 2;
var p1 = Promise.resolve();
p1.name = "p1";
var p2 = p1.then(function() {
throw new Error("caught");
});
p2.name = "p2";
var p3 = Promise.all([p2]);
p3.name = "p3";
p3.catch(function(e) {});
function listener(event, exec_state, event_data, data) {
try {
assertTrue(event != Debug.DebugEvent.Exception)
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
Debug.setBreakOnUncaughtException();
Debug.setListener(listener);
// Copyright 2015 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 a
// Promise p3 created by Promise.all has no catch handler, and is rejected
// because one of the Promises p2 passed to Promise.all is rejected. We
// expect two Exception debug events to be triggered, for p2 and p3 each,
// because neither has an user-defined catch handler.
var Debug = debug.Debug;
var expected_events = 2;
var log = [];
var p1 = Promise.resolve();
p1.name = "p1";
var p2 = p1.then(function() {
log.push("throw");
throw new Error("uncaught"); // event
});
p2.name = "p2";
var p3 = Promise.all([p2]);
p3.name = "p3";
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Exception) return;
try {
expected_events--;
assertTrue(expected_events >= 0);
assertEquals("uncaught", event_data.exception().message);
assertTrue(event_data.promise() instanceof Promise);
if (expected_events === 1) {
// Assert that the debug event is triggered at the throw site.
assertTrue(exec_state.frame(0).sourceLineText().indexOf("// event") > 0);
assertEquals("p2", event_data.promise().name);
} else {
assertEquals("p3", event_data.promise().name);
}
assertTrue(event_data.uncaught());
} 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(["end main", "throw"], log);
} else {
testDone(iteration + 1);
}
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
%EnqueueMicrotask(checkResult);
}
testDone(0);
// Copyright 2015 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 a
// Promise p3 created by Promise.race has a catch handler, and is rejected
// because one of the Promises p2 passed to Promise.all is rejected. We
// expect no Exception debug event to be triggered, since p3 and by
// extension p2 have a catch handler.
var Debug = debug.Debug;
var expected_events = 2;
var p1 = Promise.resolve();
p1.name = "p1";
var p2 = p1.then(function() {
throw new Error("caught");
});
p2.name = "p2";
var p3 = Promise.all([p2]);
p3.name = "p3";
p3.catch(function(e) {});
function listener(event, exec_state, event_data, data) {
try {
assertTrue(event != Debug.DebugEvent.Exception)
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
Debug.setBreakOnUncaughtException();
Debug.setListener(listener);
// Copyright 2015 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 a
// Promise p3 created by Promise.race has no catch handler, and is rejected
// because one of the Promises p2 passed to Promise.all is rejected. We
// expect two Exception debug events to be triggered, for p2 and p3 each,
// because neither has an user-defined catch handler.
var Debug = debug.Debug;
var expected_events = 2;
var log = [];
var p1 = Promise.resolve();
p1.name = "p1";
var p2 = p1.then(function() {
log.push("throw");
throw new Error("uncaught"); // event
});
p2.name = "p2";
var p3 = Promise.race([p2]);
p3.name = "p3";
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Exception) return;
try {
expected_events--;
assertTrue(expected_events >= 0);
assertEquals("uncaught", event_data.exception().message);
assertTrue(event_data.promise() instanceof Promise);
if (expected_events === 1) {
// Assert that the debug event is triggered at the throw site.
assertTrue(exec_state.frame(0).sourceLineText().indexOf("// event") > 0);
assertEquals("p2", event_data.promise().name);
} else {
assertEquals("p3", event_data.promise().name);
}
assertTrue(event_data.uncaught());
} 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(["end main", "throw"], log);
} else {
testDone(iteration + 1);
}
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
%EnqueueMicrotask(checkResult);
}
testDone(0);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment