1b414e283a
This patch knits together Promises returned by async/await such that when one async function awaits the result of another one, catch prediction works across the boundaries, whether the exception comes synchronously or asynchronously. Edges are added in three places: - When a locally uncaught await happens, if the value passed into await is a Promise, from the awaited value to the Promise under construction in the broader async function - From a "throwaway" Promise, which may be found on the Promise debug stack, to the Promise under construction in the async function that surrounds it - When a Promise is resolved with another Promise (e.g., when returning a Promise from an async function) In this reland, the caught tests are broken up into four parts to avoid timeouts. BUG=v8:5167 Review-Url: https://codereview.chromium.org/2346363004 Cr-Commit-Position: refs/heads/master@{#39564}
217 lines
5.3 KiB
JavaScript
217 lines
5.3 KiB
JavaScript
// 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
|
|
|
|
async function awaitThrow() {
|
|
await undefined;
|
|
throw "e"; // Exception e
|
|
}
|
|
|
|
function constructorThrow() {
|
|
return new Promise((resolve, reject) =>
|
|
Promise.resolve().then(() =>
|
|
reject("f") // Exception f
|
|
)
|
|
);
|
|
}
|
|
|
|
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) {
|
|
}
|
|
}
|
|
|
|
function switchCatch(producer) {
|
|
let resolve;
|
|
let promise = new Promise(r => resolve = r);
|
|
async function localCaught() {
|
|
try {
|
|
await promise; // force switching to localUncaught and back
|
|
await producer();
|
|
} catch (e) { }
|
|
}
|
|
async function localUncaught() {
|
|
await undefined;
|
|
resolve();
|
|
}
|
|
localCaught();
|
|
localUncaught();
|
|
}
|
|
|
|
function switchDotCatch(producer) {
|
|
let resolve;
|
|
let promise = new Promise(r => resolve = r);
|
|
async function localCaught() {
|
|
await promise; // force switching to localUncaught and back
|
|
await producer();
|
|
}
|
|
async function localUncaught() {
|
|
await undefined;
|
|
resolve();
|
|
}
|
|
localCaught().catch(() => {});
|
|
localUncaught();
|
|
}
|
|
|
|
let catches = [caught,
|
|
indirectCaught,
|
|
indirectAwaitCatch,
|
|
switchCatch,
|
|
switchDotCatch];
|
|
let noncatches = [uncaught, indirectUncaught];
|
|
let lateCatches = [dotCatch,
|
|
indirectReturnDotCatch,
|
|
indirectAwaitDotCatch,
|
|
nestedDotCatch];
|
|
|
|
let throws = [thrower, reject, argThrower, suppressThrow];
|
|
let nonthrows = [awaitReturn, scalar, nothing];
|
|
let lateThrows = [awaitThrow, constructorThrow];
|
|
let uncatchable = [rejectConstructor];
|
|
|
|
let cases = [];
|
|
|
|
for (let producer of throws.concat(lateThrows)) {
|
|
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.concat(lateThrows)) {
|
|
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 lateThrows) {
|
|
for (let consumer of lateCatches) {
|
|
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 lateCatches) {
|
|
cases.push({ producer, consumer, expectedEvents: 1, caught: true });
|
|
cases.push({ producer, consumer, expectedEvents: 1, caught: false });
|
|
}
|
|
}
|
|
|
|
|
|
function runPart(n) {
|
|
let subcases = cases.slice(n * cases.length / 4,
|
|
((n + 1) * cases.length) / 4);
|
|
for (let {producer, consumer, expectedEvents, caught} of subcases) {
|
|
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);
|
|
}
|
|
}
|
|
}
|