зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1564365 - Ignore error after resolving async function promise. r=anba,jimb
Differential Revision: https://phabricator.services.mozilla.com/D38960 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
aba3c8f154
Коммит
405385fc8e
|
@ -3909,6 +3909,19 @@ bool js::IsPromiseForAsync(JSObject* promise) {
|
|||
MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx,
|
||||
Handle<PromiseObject*> resultPromise,
|
||||
HandleValue reason) {
|
||||
if (resultPromise->state() != JS::PromiseState::Pending) {
|
||||
// OOM after resolving promise.
|
||||
// Report a warning and ignore the result.
|
||||
if (!JS_ReportErrorFlagsAndNumberASCII(
|
||||
cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
|
||||
JSMSG_UNHANDLABLE_PROMISE_REJECTION_WARNING)) {
|
||||
if (cx->isExceptionPending()) {
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return RejectPromiseInternal(cx, resultPromise, reason);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
// Throwing error after resolving async function's promise should not
|
||||
// overwrite the promise's state or value/reason.
|
||||
// This situation can happen either with debugger interaction or OOM.
|
||||
|
||||
// This testcase relies on the fact that there's a breakpoint after resolving
|
||||
// the async function's promise, before leaving the async function's frame.
|
||||
// This function searches for the last breakpoint before leaving the frame.
|
||||
//
|
||||
// - `declCode` should declare an async function `f`, and the function should
|
||||
// set global variable `returning` to `true` just before return
|
||||
// - `callCode` should call the function `f` and make sure the function's
|
||||
// execution reaches the last breakpoint
|
||||
function searchLastBreakpointBeforeReturn(declCode, callCode) {
|
||||
const g = newGlobal({ newCompartment: true });
|
||||
const dbg = new Debugger(g);
|
||||
g.eval(declCode);
|
||||
|
||||
let hit = false;
|
||||
let offset = 0;
|
||||
dbg.onEnterFrame = function(frame) {
|
||||
if (frame.callee && frame.callee.name == "f") {
|
||||
frame.onStep = () => {
|
||||
if (!g.returning) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
offset = frame.offset;
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
g.eval(callCode);
|
||||
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(offset != 0, true);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
function testWithoutAwait() {
|
||||
const declCode = `
|
||||
var returning = false;
|
||||
async function f() {
|
||||
return (returning = true, "expected");
|
||||
};
|
||||
`;
|
||||
|
||||
const callCode = `
|
||||
var p = f();
|
||||
`;
|
||||
|
||||
const offset = searchLastBreakpointBeforeReturn(declCode, callCode);
|
||||
|
||||
const g = newGlobal({ newCompartment: true });
|
||||
const dbg = new Debugger(g);
|
||||
g.eval(declCode);
|
||||
|
||||
let onPromiseSettledCount = 0;
|
||||
dbg.onPromiseSettled = function(promise) {
|
||||
onPromiseSettledCount++;
|
||||
// No promise should be rejected.
|
||||
assertEq(promise.promiseState, "fulfilled");
|
||||
|
||||
// Async function's promise should have expected value.
|
||||
if (onPromiseSettledCount == 1) {
|
||||
assertEq(promise.promiseValue, "expected");
|
||||
}
|
||||
};
|
||||
|
||||
let hitBreakpoint = false;
|
||||
dbg.onEnterFrame = function(frame) {
|
||||
if (frame.callee && frame.callee.name == "f") {
|
||||
frame.script.setBreakpoint(offset, {
|
||||
hit() {
|
||||
hitBreakpoint = true;
|
||||
return { throw: "unexpected" };
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
enableLastWarning();
|
||||
|
||||
g.eval(`
|
||||
var fulfilledValue;
|
||||
var rejected = false;
|
||||
`);
|
||||
|
||||
g.eval(callCode);
|
||||
|
||||
// The execution reaches to the last breakpoint without running job queue.
|
||||
assertEq(hitBreakpoint, true);
|
||||
|
||||
const warn = getLastWarning();
|
||||
assertEq(warn.message,
|
||||
"unhandlable error after resolving async function's promise");
|
||||
clearLastWarning();
|
||||
|
||||
// Add reaction handler after resolution.
|
||||
// This handler's job will be enqueued immediately.
|
||||
g.eval(`
|
||||
p.then(x => {
|
||||
fulfilledValue = x;
|
||||
}, e => {
|
||||
rejected = true;
|
||||
});
|
||||
`);
|
||||
|
||||
// Run the above handler.
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(g.fulfilledValue, "expected");
|
||||
assertEq(onPromiseSettledCount >= 1, true);
|
||||
}
|
||||
|
||||
function testWithAwait() {
|
||||
const declCode = `
|
||||
var resolve;
|
||||
var p = new Promise(r => { resolve = r });
|
||||
var returning = false;
|
||||
async function f() {
|
||||
await p;
|
||||
return (returning = true, "expected");
|
||||
};
|
||||
`;
|
||||
|
||||
const callCode = `
|
||||
var p = f();
|
||||
`;
|
||||
|
||||
const resolveCode = `
|
||||
resolve("resolve");
|
||||
`;
|
||||
|
||||
const offset = searchLastBreakpointBeforeReturn(declCode,
|
||||
callCode + resolveCode);
|
||||
|
||||
const g = newGlobal({newCompartment: true});
|
||||
const dbg = new Debugger(g);
|
||||
g.eval(declCode);
|
||||
|
||||
let onPromiseSettledCount = 0;
|
||||
dbg.onPromiseSettled = function(promise) {
|
||||
onPromiseSettledCount++;
|
||||
|
||||
// No promise should be rejected.
|
||||
assertEq(promise.promiseState, "fulfilled");
|
||||
|
||||
// Async function's promise should have expected value.
|
||||
if (onPromiseSettledCount == 3) {
|
||||
assertEq(promise.promiseValue, "expected");
|
||||
}
|
||||
};
|
||||
|
||||
let hitBreakpoint = false;
|
||||
dbg.onEnterFrame = function(frame) {
|
||||
if (frame.callee && frame.callee.name == "f") {
|
||||
frame.script.setBreakpoint(offset, {
|
||||
hit() {
|
||||
hitBreakpoint = true;
|
||||
return { throw: "unexpected" };
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
enableLastWarning();
|
||||
|
||||
g.eval(`
|
||||
var fulfilledValue1;
|
||||
var fulfilledValue2;
|
||||
var rejected = false;
|
||||
`);
|
||||
|
||||
g.eval(callCode);
|
||||
|
||||
assertEq(getLastWarning(), null);
|
||||
|
||||
// Add reaction handler before resolution.
|
||||
// This handler's job will be enqueued when `p` is resolved.
|
||||
g.eval(`
|
||||
p.then(x => {
|
||||
fulfilledValue1 = x;
|
||||
}, e => {
|
||||
rejected = true;
|
||||
});
|
||||
`);
|
||||
|
||||
g.eval(resolveCode);
|
||||
|
||||
// Run the remaining part of async function, and the above handler.
|
||||
drainJobQueue();
|
||||
|
||||
// The execution reaches to the last breakpoint after running job queue for
|
||||
// resolving `p`.
|
||||
assertEq(hitBreakpoint, true);
|
||||
|
||||
const warn = getLastWarning();
|
||||
assertEq(warn.message,
|
||||
"unhandlable error after resolving async function's promise");
|
||||
clearLastWarning();
|
||||
|
||||
assertEq(g.fulfilledValue1, "expected");
|
||||
assertEq(g.rejected, false);
|
||||
|
||||
// Add reaction handler after resolution.
|
||||
// This handler's job will be enqueued immediately.
|
||||
g.eval(`
|
||||
p.then(x => {
|
||||
fulfilledValue2 = x;
|
||||
}, e => {
|
||||
rejected = true;
|
||||
});
|
||||
`);
|
||||
|
||||
// Run the above handler.
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(g.fulfilledValue2, "expected");
|
||||
assertEq(g.rejected, false);
|
||||
assertEq(onPromiseSettledCount >= 3, true);
|
||||
}
|
||||
|
||||
testWithoutAwait();
|
||||
testWithAwait();
|
|
@ -644,6 +644,9 @@ MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "P
|
|||
MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
|
||||
MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
|
||||
|
||||
// Async Function
|
||||
MSG_DEF(JSMSG_UNHANDLABLE_PROMISE_REJECTION_WARNING, 0, JSEXN_WARN, "unhandlable error after resolving async function's promise")
|
||||
|
||||
// Async Iteration
|
||||
MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'")
|
||||
MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator")
|
||||
|
|
Загрузка…
Ссылка в новой задаче