summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/async
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/jit-test/tests/async
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/async')
-rw-r--r--js/src/jit-test/tests/async/debugger-reject-after-fulfill.js229
-rw-r--r--js/src/jit-test/tests/async/ecma262-issue-1461.js22
2 files changed, 251 insertions, 0 deletions
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");