summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/async
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/async')
-rw-r--r--js/src/jit-test/tests/async/await-exception-stack-in-finally-1.js25
-rw-r--r--js/src/jit-test/tests/async/await-exception-stack-in-finally-2.js17
-rw-r--r--js/src/jit-test/tests/async/await-exception-stack-in-finally-3.js21
-rw-r--r--js/src/jit-test/tests/async/bug1773650.js58
-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
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");