Commit 7776370c authored by littledan's avatar littledan Committed by Commit bot

Async/await catch prediction for "the synchronous case"

Handle the "synchronous case" by marking try/catch blocks introduced for
async functions as ASYNC_AWAIT and traversing up the stack, finding successive
Promises and returning caught if any of them are predicted to be caught.

BUG=v8:5167

Review-Url: https://codereview.chromium.org/2325813002
Cr-Commit-Position: refs/heads/master@{#39433}
parent 4b8f6543
...@@ -1682,43 +1682,33 @@ void Debug::OnThrow(Handle<Object> exception) { ...@@ -1682,43 +1682,33 @@ void Debug::OnThrow(Handle<Object> exception) {
} }
} }
void Debug::OnPromiseReject(Handle<Object> promise, Handle<Object> value) {
void Debug::OnPromiseReject(Handle<JSObject> promise, Handle<Object> value) {
if (in_debug_scope() || ignore_events()) return; if (in_debug_scope() || ignore_events()) return;
HandleScope scope(isolate_); HandleScope scope(isolate_);
// Check whether the promise has been marked as having triggered a message. // Check whether the promise has been marked as having triggered a message.
Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol(); Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
if (JSReceiver::GetDataProperty(promise, key)->IsUndefined(isolate_)) { if (!promise->IsJSObject() ||
JSReceiver::GetDataProperty(Handle<JSObject>::cast(promise), key)
->IsUndefined(isolate_)) {
OnException(value, promise); OnException(value, promise);
} }
} }
MaybeHandle<Object> Debug::PromiseHasUserDefinedRejectHandler(
Handle<JSObject> promise) {
Handle<JSFunction> fun = isolate_->promise_has_user_defined_reject_handler();
return Execution::Call(isolate_, fun, promise, 0, NULL);
}
void Debug::OnException(Handle<Object> exception, Handle<Object> promise) { void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
Isolate::CatchType catch_type = isolate_->PredictExceptionCatcher(); Isolate::CatchType catch_type = isolate_->PredictExceptionCatcher();
// Don't notify listener of exceptions that are internal to a desugaring. // Don't notify listener of exceptions that are internal to a desugaring.
if (catch_type == Isolate::CAUGHT_BY_DESUGARING) return; if (catch_type == Isolate::CAUGHT_BY_DESUGARING) return;
bool uncaught = (catch_type == Isolate::NOT_CAUGHT); bool uncaught = catch_type == Isolate::NOT_CAUGHT;
if (promise->IsJSObject()) { if (promise->IsJSObject()) {
Handle<JSObject> jspromise = Handle<JSObject>::cast(promise); Handle<JSObject> jspromise = Handle<JSObject>::cast(promise);
// Mark the promise as already having triggered a message. // Mark the promise as already having triggered a message.
Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol(); Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
JSObject::SetProperty(jspromise, key, key, STRICT).Assert(); JSObject::SetProperty(jspromise, key, key, STRICT).Assert();
// Check whether the promise reject is considered an uncaught exception. // Check whether the promise reject is considered an uncaught exception.
Handle<Object> has_reject_handler; uncaught = !isolate_->PromiseHasUserDefinedRejectHandler(jspromise);
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate_, has_reject_handler,
PromiseHasUserDefinedRejectHandler(jspromise), /* void */);
uncaught = has_reject_handler->IsFalse(isolate_);
} }
// Bail out if exception breaks are not active // Bail out if exception breaks are not active
if (uncaught) { if (uncaught) {
......
...@@ -413,7 +413,7 @@ class Debug { ...@@ -413,7 +413,7 @@ class Debug {
void OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue); void OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue);
void OnThrow(Handle<Object> exception); void OnThrow(Handle<Object> exception);
void OnPromiseReject(Handle<JSObject> promise, Handle<Object> value); void OnPromiseReject(Handle<Object> promise, Handle<Object> value);
void OnCompileError(Handle<Script> script); void OnCompileError(Handle<Script> script);
void OnBeforeCompile(Handle<Script> script); void OnBeforeCompile(Handle<Script> script);
void OnAfterCompile(Handle<Script> script); void OnAfterCompile(Handle<Script> script);
...@@ -594,9 +594,6 @@ class Debug { ...@@ -594,9 +594,6 @@ class Debug {
// Mirror cache handling. // Mirror cache handling.
void ClearMirrorCache(); void ClearMirrorCache();
MaybeHandle<Object> PromiseHasUserDefinedRejectHandler(
Handle<JSObject> promise);
void CallEventCallback(v8::DebugEvent event, void CallEventCallback(v8::DebugEvent event,
Handle<Object> exec_state, Handle<Object> exec_state,
Handle<Object> event_data, Handle<Object> event_data,
......
...@@ -1703,6 +1703,19 @@ void Isolate::PopPromise() { ...@@ -1703,6 +1703,19 @@ void Isolate::PopPromise() {
global_handles()->Destroy(global_promise.location()); global_handles()->Destroy(global_promise.location());
} }
bool Isolate::PromiseHasUserDefinedRejectHandler(Handle<Object> promise) {
Handle<JSFunction> fun = promise_has_user_defined_reject_handler();
Handle<Object> has_reject_handler;
if (Execution::TryCall(this, fun, promise, 0, NULL)
.ToHandle(&has_reject_handler)) {
return has_reject_handler->IsTrue(this);
}
// If an exception is thrown in the course of execution of this built-in
// function, it indicates either a bug, or a synthetic uncatchable
// exception in the shutdown path. In either case, it's OK to predict either
// way in DevTools.
return false;
}
Handle<Object> Isolate::GetPromiseOnStackOnThrow() { Handle<Object> Isolate::GetPromiseOnStackOnThrow() {
Handle<Object> undefined = factory()->undefined_value(); Handle<Object> undefined = factory()->undefined_value();
...@@ -1713,19 +1726,46 @@ Handle<Object> Isolate::GetPromiseOnStackOnThrow() { ...@@ -1713,19 +1726,46 @@ Handle<Object> Isolate::GetPromiseOnStackOnThrow() {
if (prediction == NOT_CAUGHT || prediction == CAUGHT_BY_EXTERNAL) { if (prediction == NOT_CAUGHT || prediction == CAUGHT_BY_EXTERNAL) {
return undefined; return undefined;
} }
Handle<Object> retval = undefined;
PromiseOnStack* promise_on_stack = tltop->promise_on_stack_;
for (JavaScriptFrameIterator it(this); !it.done(); it.Advance()) { for (JavaScriptFrameIterator it(this); !it.done(); it.Advance()) {
switch (PredictException(it.frame())) { switch (PredictException(it.frame())) {
case HandlerTable::UNCAUGHT: case HandlerTable::UNCAUGHT:
case HandlerTable::ASYNC_AWAIT: continue;
break;
case HandlerTable::CAUGHT: case HandlerTable::CAUGHT:
case HandlerTable::DESUGARING: case HandlerTable::DESUGARING:
return undefined; if (retval->IsJSObject()) {
// Caught the result of an inner async/await invocation.
// Mark the inner promise as caught in the "synchronous case" so
// that Debug::OnException will see. In the synchronous case,
// namely in the code in an async function before the first
// await, the function which has this exception event has not yet
// returned, so the generated Promise has not yet been marked
// by AsyncFunctionAwaitCaught with promiseHandledHintSymbol.
Handle<Symbol> key = factory()->promise_handled_hint_symbol();
JSObject::SetProperty(Handle<JSObject>::cast(retval), key,
factory()->true_value(), STRICT)
.Assert();
}
return retval;
case HandlerTable::PROMISE: case HandlerTable::PROMISE:
return tltop->promise_on_stack_->promise(); return promise_on_stack->promise();
case HandlerTable::ASYNC_AWAIT: {
// If in the initial portion of async/await, continue the loop to pop up
// successive async/await stack frames until an asynchronous one with
// dependents is found, or a non-async stack frame is encountered, in
// order to handle the synchronous async/await catch prediction case:
// assume that async function calls are awaited.
retval = promise_on_stack->promise();
if (PromiseHasUserDefinedRejectHandler(retval)) {
return retval;
}
promise_on_stack = promise_on_stack->prev();
continue;
} }
} }
return undefined; }
return retval;
} }
......
...@@ -673,8 +673,14 @@ class Isolate { ...@@ -673,8 +673,14 @@ class Isolate {
// Push and pop a promise and the current try-catch handler. // Push and pop a promise and the current try-catch handler.
void PushPromise(Handle<JSObject> promise); void PushPromise(Handle<JSObject> promise);
void PopPromise(); void PopPromise();
// Return the relevant Promise that a throw/rejection pertains to, based
// on the contents of the Promise stack
Handle<Object> GetPromiseOnStackOnThrow(); Handle<Object> GetPromiseOnStackOnThrow();
// Heuristically guess whether a Promise is handled by user catch handler
bool PromiseHasUserDefinedRejectHandler(Handle<Object> promise);
class ExceptionScope { class ExceptionScope {
public: public:
// Scope currently can only be used for regular exceptions, // Scope currently can only be used for regular exceptions,
......
...@@ -274,7 +274,7 @@ RUNTIME_FUNCTION(Runtime_ThrowApplyNonFunction) { ...@@ -274,7 +274,7 @@ RUNTIME_FUNCTION(Runtime_ThrowApplyNonFunction) {
namespace { namespace {
void PromiseRejectEvent(Isolate* isolate, Handle<JSObject> promise, void PromiseRejectEvent(Isolate* isolate, Handle<JSObject> promise,
Handle<JSObject> rejected_promise, Handle<Object> value, Handle<Object> rejected_promise, Handle<Object> value,
bool debug_event) { bool debug_event) {
if (isolate->debug()->is_active() && debug_event) { if (isolate->debug()->is_active() && debug_event) {
isolate->debug()->OnPromiseReject(rejected_promise, value); isolate->debug()->OnPromiseReject(rejected_promise, value);
...@@ -306,12 +306,12 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) { ...@@ -306,12 +306,12 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) {
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0); CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1); CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
Handle<JSObject> rejected_promise = promise; Handle<Object> rejected_promise = promise;
if (isolate->debug()->is_active()) { if (isolate->debug()->is_active()) {
Handle<Object> promise_on_stack = isolate->GetPromiseOnStackOnThrow(); // If the Promise.reject call is caught, then this will return
if (promise_on_stack->IsJSObject()) { // undefined, which will be interpreted by PromiseRejectEvent
rejected_promise = Handle<JSObject>::cast(promise_on_stack); // as being a caught exception event.
} rejected_promise = isolate->GetPromiseOnStackOnThrow();
} }
PromiseRejectEvent(isolate, promise, rejected_promise, value, true); PromiseRejectEvent(isolate, promise, rejected_promise, value, true);
return isolate->heap()->undefined_value(); return isolate->heap()->undefined_value();
......
// Copyright 2016 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: --allow-natives-syntax --harmony-async-await --expose-debug-as debug
Debug = debug.Debug
let events = 0;
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Exception) return;
events++;
}
async function thrower() {
throw "a"; // Exception a
}
var reject = () => Promise.reject("b"); // Exception b
async function awaitReturn() { await 1; return; }
async function scalar() { return 1; }
function nothing() { return 1; }
function rejectConstructor() {
return new Promise((resolve, reject) => reject("c")); // Exception c
}
async function argThrower(x = (() => { throw "d"; })()) { } // Exception d
function suppressThrow() {
return thrower();
}
async function caught(producer) {
try {
await producer();
} catch (e) {
}
}
async function uncaught(producer) {
await producer();
}
async function indirectUncaught(producer) {
await uncaught(producer);
}
async function indirectCaught(producer) {
try {
await uncaught(producer);
} catch (e) {
}
}
function dotCatch(producer) {
Promise.resolve(producer()).catch(() => {});
}
function indirectReturnDotCatch(producer) {
(async() => producer())().catch(() => {});
}
function indirectAwaitDotCatch(producer) {
(async() => await producer())().catch(() => {});
}
function nestedDotCatch(producer) {
Promise.resolve(producer()).then().catch(() => {});
}
async function indirectAwaitCatch(producer) {
try {
await (() => producer())();
} catch (e) {
}
}
let catches = [caught, indirectCaught, indirectAwaitCatch];
let noncatches = [uncaught, indirectUncaught];
let lateCatches = [dotCatch,
indirectReturnDotCatch,
indirectAwaitDotCatch,
nestedDotCatch];
let throws = [thrower, reject, argThrower, suppressThrow];
let nonthrows = [awaitReturn, scalar, nothing];
let uncatchable = [rejectConstructor];
let cases = [];
for (let producer of throws) {
for (let consumer of catches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 0, caught: false });
}
}
for (let producer of throws) {
for (let consumer of noncatches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 1, caught: false });
}
}
for (let producer of nonthrows) {
for (let consumer of catches.concat(noncatches, lateCatches)) {
cases.push({ producer, consumer, expectedEvents: 0, caught: true });
cases.push({ producer, consumer, expectedEvents: 0, caught: false });
}
}
for (let producer of uncatchable) {
for (let consumer of catches.concat(noncatches, lateCatches)) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 1, caught: false });
}
}
for (let producer of throws) {
for (let consumer of lateCatches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 1, caught: false });
}
}
for (let {producer, consumer, expectedEvents, caught} of cases) {
Debug.setListener(listener);
if (caught) {
Debug.setBreakOnException();
} else {
Debug.setBreakOnUncaughtException();
}
events = 0;
consumer(producer);
%RunMicrotasks();
Debug.setListener(null);
if (caught) {
Debug.clearBreakOnException();
} else {
Debug.clearBreakOnUncaughtException();
}
if (expectedEvents != events) {
print(`producer ${producer} consumer ${consumer} expectedEvents ` +
`${expectedEvents} caught ${caught} events ${events}`);
quit(1);
}
}
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