diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/jit-test/tests/async | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/async')
6 files changed, 372 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/async/await-exception-stack-in-finally-1.js b/js/src/jit-test/tests/async/await-exception-stack-in-finally-1.js new file mode 100644 index 0000000000..bfdc586c06 --- /dev/null +++ b/js/src/jit-test/tests/async/await-exception-stack-in-finally-1.js @@ -0,0 +1,25 @@ +load(libdir + "asserts.js"); + +var g = newGlobal({newCompartment: true}) + +// Throw an exception from a different compartment. +var thrower = g.Function(` + throw 0; +`); + +async function throwAndAwait() { + try { + thrower(); + } finally { + // The |finally| block is entered with the exception stack on the function + // stack. The function stack is saved in the generator object when executing + // |await|, so the exception stack, which was created in a different + // compartment, has to be wrapped into the current compartment. + await {}; + } +} +throwAndAwait().then(() => { + throw new Error("missing error"); +}, e => { + assertEq(e, 0); +}); diff --git a/js/src/jit-test/tests/async/await-exception-stack-in-finally-2.js b/js/src/jit-test/tests/async/await-exception-stack-in-finally-2.js new file mode 100644 index 0000000000..93ad6c591f --- /dev/null +++ b/js/src/jit-test/tests/async/await-exception-stack-in-finally-2.js @@ -0,0 +1,17 @@ +ignoreUnhandledRejections(); + +var g = newGlobal({newCompartment: true}) + +// Throw an exception from a different compartment. +var thrower = g.Function(` + throw 0; +`); + +async function justThrow() { + thrower(); +} + +justThrow(); + +// Nuke wrappers and make sure we don't crash. +nukeAllCCWs(); diff --git a/js/src/jit-test/tests/async/await-exception-stack-in-finally-3.js b/js/src/jit-test/tests/async/await-exception-stack-in-finally-3.js new file mode 100644 index 0000000000..f7c672c4da --- /dev/null +++ b/js/src/jit-test/tests/async/await-exception-stack-in-finally-3.js @@ -0,0 +1,21 @@ +ignoreUnhandledRejections(); + +var g = newGlobal({newCompartment: true}) + +// Allow |g| to nuke wrappers from the main compartment. +g.nuke = () => { + // Nuke wrappers and make sure we don't crash. + nukeAllCCWs(); +}; + +// Throw an exception from a different compartment. +var thrower = g.Function(` + nuke(); + throw 0; +`); + +async function justThrow() { + thrower(); +} + +justThrow(); diff --git a/js/src/jit-test/tests/async/bug1773650.js b/js/src/jit-test/tests/async/bug1773650.js new file mode 100644 index 0000000000..8fcc3cf743 --- /dev/null +++ b/js/src/jit-test/tests/async/bug1773650.js @@ -0,0 +1,58 @@ +// This approach is based on async/debugger-reject-after-fulfill.js +function searchLastBreakpointBeforeReturn(declCode, callCode) { + const g = newGlobal({ newCompartment: true }); + const dbg = new Debugger(g); + g.eval(declCode); + + 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; +} + +let declaration = ` + var returning = false; + async function f() { + try { + throw undefined; + } catch (exc) { + try { + return (returning = true, "expected"); + } catch {} + } + }`; +let call = "var p = f();" + +let offset = searchLastBreakpointBeforeReturn(declaration, call); + +let g = newGlobal({ newCompartment: true }); +let dbg = new Debugger(g); +g.eval(declaration); + +dbg.onEnterFrame = function(frame) { + if (frame.callee && frame.callee.name == "f") { + dbg.onEnterFrame = undefined; + frame.script.setBreakpoint(offset, { + hit() { + return { throw: "unexpected" }; + } + }); + } +}; + +try { + g.eval(call); +} catch {} diff --git a/js/src/jit-test/tests/async/debugger-reject-after-fulfill.js b/js/src/jit-test/tests/async/debugger-reject-after-fulfill.js new file mode 100644 index 0000000000..6f147e3705 --- /dev/null +++ b/js/src/jit-test/tests/async/debugger-reject-after-fulfill.js @@ -0,0 +1,229 @@ +// 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") { + dbg.onEnterFrame = undefined; + 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 == 2) { + assertEq(promise.promiseValue, "expected"); + } + }; + + let hitBreakpoint = false; + dbg.onEnterFrame = function(frame) { + if (frame.callee && frame.callee.name == "f") { + dbg.onEnterFrame = undefined; + 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(); diff --git a/js/src/jit-test/tests/async/ecma262-issue-1461.js b/js/src/jit-test/tests/async/ecma262-issue-1461.js new file mode 100644 index 0000000000..7449c303b0 --- /dev/null +++ b/js/src/jit-test/tests/async/ecma262-issue-1461.js @@ -0,0 +1,22 @@ +// <https://github.com/tc39/ecma262/pull/1470> changes a detail of +// error-handling in %AsyncFromSyncIteratorPrototype% methods. This test is +// based on a comment in the thread where the issue was first reported, +// <https://github.com/tc39/ecma262/issues/1461#issuecomment-468602852> + +let log = []; + +{ + async function f() { + var p = Promise.resolve(0); + Object.defineProperty(p, "constructor", {get() { throw "hi" }}); + for await (var x of [p]); + } + Promise.resolve(0) + .then(() => log.push("tick 1")) + .then(() => log.push("tick 2")) + .then(() => log.push("tick 3")); + f().catch(exc => log.push(exc)); +} + +drainJobQueue(); +assertEq(log.join(), "tick 1,tick 2,hi,tick 3"); |