зеркало из 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,
|
MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx,
|
||||||
Handle<PromiseObject*> resultPromise,
|
Handle<PromiseObject*> resultPromise,
|
||||||
HandleValue reason) {
|
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);
|
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_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")
|
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
|
// Async Iteration
|
||||||
MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'")
|
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")
|
MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator")
|
||||||
|
|
Загрузка…
Ссылка в новой задаче