diff options
Diffstat (limited to 'js/src/jit-test/tests/promise')
38 files changed, 1144 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/promise/bug-1298776.js b/js/src/jit-test/tests/promise/bug-1298776.js new file mode 100644 index 0000000000..bd4f2a66ed --- /dev/null +++ b/js/src/jit-test/tests/promise/bug-1298776.js @@ -0,0 +1,4 @@ +ignoreUnhandledRejections(); + +if (typeof oomTest === 'function') + oomTest(Function(`new Promise(res=>res)`)); diff --git a/js/src/jit-test/tests/promise/bug-1545369.js b/js/src/jit-test/tests/promise/bug-1545369.js new file mode 100644 index 0000000000..cb2adb5da3 --- /dev/null +++ b/js/src/jit-test/tests/promise/bug-1545369.js @@ -0,0 +1,9 @@ +// |jit-test| --no-cgc; allow-oom + +async function f(x) { + await await x; +}; +for (let i = 0; i < 800; ++i) { + f(); +} +gcparam("maxBytes", gcparam("gcBytes")); diff --git a/js/src/jit-test/tests/promise/bug1347984.js b/js/src/jit-test/tests/promise/bug1347984.js new file mode 100644 index 0000000000..10dbae25b2 --- /dev/null +++ b/js/src/jit-test/tests/promise/bug1347984.js @@ -0,0 +1,6 @@ +// |jit-test| error:dead object +var g = newGlobal({newCompartment: true}); +var p = new Promise(() => {}); +g.Promise.prototype.then.call(p, () => void 0); +g.eval("nukeAllCCWs()"); +resolvePromise(p, 9); diff --git a/js/src/jit-test/tests/promise/bug1406463.js b/js/src/jit-test/tests/promise/bug1406463.js new file mode 100644 index 0000000000..4873da395f --- /dev/null +++ b/js/src/jit-test/tests/promise/bug1406463.js @@ -0,0 +1,16 @@ +// |jit-test| error:dead object + +var P = newGlobal({newCompartment: true}).eval(` +(class extends Promise { + static resolve(o) { + return o; + } +}); +`); + +Promise.all.call(P, [{ + then(r) { + nukeAllCCWs(); + r(); + } +}]); diff --git a/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js b/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js new file mode 100644 index 0000000000..48aec08e26 --- /dev/null +++ b/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js @@ -0,0 +1,71 @@ +// Promise.race(...) may add a dummy PromiseReaction which is only used for the +// debugger. Ensure that this dummy reaction can't influence the normal Promise +// resolution behaviour. +// +// See BlockOnPromise when called from PerformPromiseRace for when this dummy +// reaction is created. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var c = 0; +var g_resolve; + +var resolvedValues = []; + +function resolveCapability(v) { + resolvedValues.push(v); +} + +class P extends Promise { + constructor(executor) { + // Only the very first object created through this constructor gets + // special treatment, all other invocations create built-in Promise + // objects. + if (c++ > 1) { + return new Promise(executor); + } + + executor(resolveCapability, neverCalled); + + var {promise, resolve} = newPromiseCapability(); + g_resolve = resolve; + + // Use an async function to create a Promise without resolving functions. + var p = async function(){ await promise; return 456; }(); + + // Ensure the species constructor is not the built-in Promise constructor + // to avoid falling into the fast path. + p.constructor = { + [Symbol.species]: P + }; + + return p; + } +} + +var {promise: alwaysPending} = newPromiseCapability(); + +// The promise returned from race() should never be resolved. +P.race([alwaysPending]).then(neverCalled, neverCalled); + +g_resolve(123); + +drainJobQueue(); + +// Check |resolvedValues| to ensure resolving functions were properly called. +assertEq(resolvedValues.length, 2); +assertEq(resolvedValues[0], alwaysPending); +assertEq(resolvedValues[1], 456); diff --git a/js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js b/js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js new file mode 100644 index 0000000000..ceb067e291 --- /dev/null +++ b/js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js @@ -0,0 +1,15 @@ +function d() { + quit(); +} + +function bt() { + getBacktrace({thisprops: true}); +} + +d.toString = bt.toString = drainJobQueue.toString = function () { + if (this === bt) + return ''; + this(); +} + +bt(); diff --git a/js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js b/js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js new file mode 100644 index 0000000000..476e6c7d01 --- /dev/null +++ b/js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js @@ -0,0 +1,8 @@ +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(_=>getWaitForAllPromise(42), Error); +assertThrowsInstanceOf(_=>getWaitForAllPromise([42]), Error); +assertThrowsInstanceOf(_=>getWaitForAllPromise([{}]), Error); + +// Shouldn't throw. +getWaitForAllPromise([Promise.resolve()]); diff --git a/js/src/jit-test/tests/promise/job-realm.js b/js/src/jit-test/tests/promise/job-realm.js new file mode 100644 index 0000000000..c50c3d8c2a --- /dev/null +++ b/js/src/jit-test/tests/promise/job-realm.js @@ -0,0 +1,363 @@ +// `debugGetQueuedJobs` is available only in debug build. +if (!getBuildConfiguration().debug) { + quit(); +} + +function testOne(func) { + assertEq(debugGetQueuedJobs().length, 0); + + func(); + + drainJobQueue(); + + assertEq(debugGetQueuedJobs().length, 0); + + if (func.length == 1) { + func({sameCompartmentAs: globalThis}); + + drainJobQueue(); + + assertEq(debugGetQueuedJobs().length, 0); + } +} + +function assertGlobal(obj, expectedGlobal) { + const global = objectGlobal(obj); + if (global) { + assertEq(global === expectedGlobal, true); + } else { + // obj is a wrapper. + // expectedGlobal should be other global than this. + assertEq(expectedGlobal !== globalThis, true); + } +} + +testOne(() => { + // Just creating a promise shouldn't enqueue any jobs. + Promise.resolve(10); + assertEq(debugGetQueuedJobs().length, 0); +}); + +testOne(() => { + // Calling then should create a job for each. + Promise.resolve(10).then(() => {}); + Promise.resolve(10).then(() => {}); + Promise.resolve(10).then(() => {}); + + assertEq(debugGetQueuedJobs().length, 3); +}); + +testOne(() => { + // The reaction job should use the function's realm. + Promise.resolve(10).then(() => {}); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the function's realm. + var g = newGlobal(newGlobalOptions); + g.eval(` +Promise.resolve(10).then(() => {}); +`); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the function's realm. + var g = newGlobal(newGlobalOptions); + g.Promise.resolve(10).then(g.eval(`() => {}`)); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the function's realm. + var g = newGlobal(newGlobalOptions); + g.Promise.resolve(10).then(() => {}); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the bound function's target function's realm. + var g = newGlobal(newGlobalOptions); + g.Promise.resolve(10) + .then(Function.prototype.bind.call(g.eval(`() => {}`), this)); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the bound function's target function's realm. + var g = newGlobal(newGlobalOptions); + g.Promise.resolve(10) + .then(g.Function.prototype.bind.call(() => {}, g)); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the bound function's target function's realm, + // recursively + var g = newGlobal(newGlobalOptions); + g.Promise.resolve(10) + .then( + g.Function.prototype.bind.call( + Function.prototype.bind.call( + g.Function.prototype.bind.call( + () => {}, + g), + this), + g) + ); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the bound function's target function's realm, + // recursively + var g = newGlobal(newGlobalOptions); + Promise.resolve(10) + .then( + g.Function.prototype.bind.call( + Function.prototype.bind.call( + g.Function.prototype.bind.call( + Function.prototype.bind.call( + g.eval(`() => {}`), + this), + g), + this), + g) + ); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the proxy's target function's realm. + var g = newGlobal(newGlobalOptions); + g.handler = () => {}; + g.eval(` +Promise.resolve(10).then(new Proxy(handler, {})); +`); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the proxy's target function's realm. + var g = newGlobal(newGlobalOptions); + g.eval(` +var handler = () => {}; +`); + Promise.resolve(10).then(new Proxy(g.handler, {})); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + + +testOne(newGlobalOptions => { + // The reaction job should use the proxy's target function's realm, + // recursively. + var g = newGlobal(newGlobalOptions); + g.handler = () => {}; + g.outerProxy = Proxy; + g.eval(` +Promise.resolve(10).then( + new outerProxy(new Proxy(new outerProxy(new Proxy(handler, {}), {}), {}), {}) +); +`); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The reaction job should use the proxy's target function's realm, + // recursively. + var g = newGlobal(newGlobalOptions); + g.eval(` +var handler = () => {}; +`); + Promise.resolve(10) + .then(new Proxy(new g.Proxy(new Proxy(g.handler, {}), {}), {})); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(() => { + // The thenable job should use the `then` function's realm. + Promise.resolve({ + then: () => {} + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the `then` function's realm. + var g = newGlobal(newGlobalOptions); + Promise.resolve(g.eval(` +({ + then: () => {} +}); +`)); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the `then` function's realm. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: g.eval(`() => {}`), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the bound function's target function's realm. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: Function.prototype.bind.call(g.eval(`() => {}`), this), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the bound function's target function's realm. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: g.Function.prototype.bind.call(() => {}, g), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the bound function's target function's realm, + // recursively. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: Function.prototype.bind.call( + g.Function.prototype.bind.call( + Function.prototype.bind.call( + g.eval(`() => {}`), + this), + g), + this) + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the bound function's target function's realm, + // recursively. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: g.Function.prototype.bind.call( + Function.prototype.bind.call( + g.Function.prototype.bind.call( + () => {}, + g), + this), + g), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the proxy's target function's realm. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: new Proxy(g.eval(`() => {}`), {}), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the proxy's target function's realm. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: new g.Proxy(() => {}, {}), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the proxy's target function's realm, + // recursively. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: new Proxy(new g.Proxy(new Proxy(g.eval(`() => {}`), {}), {}), {}), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], g); +}); + +testOne(newGlobalOptions => { + // The thenable job should use the proxy's target function's realm, + // recursively. + var g = newGlobal(newGlobalOptions); + Promise.resolve({ + then: new g.Proxy(new Proxy(new g.Proxy(() => {}, {}), {}), {}), + }); + + var jobs = debugGetQueuedJobs(); + assertEq(jobs.length, 1); + assertGlobal(jobs[0], globalThis); +}); + +print("ok"); diff --git a/js/src/jit-test/tests/promise/newpromisecapability-error-message.js b/js/src/jit-test/tests/promise/newpromisecapability-error-message.js new file mode 100644 index 0000000000..72b13924a3 --- /dev/null +++ b/js/src/jit-test/tests/promise/newpromisecapability-error-message.js @@ -0,0 +1,20 @@ +load(libdir + "asserts.js"); + +let foo = {}; +for (let method of ["resolve", "reject", "race"]) { + assertErrorMessage( + () => Promise[method].call(foo), + TypeError, + "foo is not a constructor" + ); + assertErrorMessage( + () => Promise[method].call(foo, []), + TypeError, + "foo is not a constructor" + ); + assertErrorMessage( + () => Promise[method].call({}, [], foo), + TypeError, + "({}) is not a constructor" + ); +} diff --git a/js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js b/js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js new file mode 100644 index 0000000000..7db07bdc73 --- /dev/null +++ b/js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js @@ -0,0 +1,10 @@ +let thenCalled = false; +let p1 = new Promise(res => res('result')).then(val => { + Promise.resolve(1).then(_=>{thenCalled = true;}); + // This reentrant call is ignored. + drainJobQueue(); + assertEq(thenCalled, false); +}); + +drainJobQueue(); +assertEq(thenCalled, true); diff --git a/js/src/jit-test/tests/promise/optimized-promise-already-resolved.js b/js/src/jit-test/tests/promise/optimized-promise-already-resolved.js new file mode 100644 index 0000000000..69e7d95fe0 --- /dev/null +++ b/js/src/jit-test/tests/promise/optimized-promise-already-resolved.js @@ -0,0 +1,73 @@ +// Promise created with default resolution functions (without instances) should +// behave in the same way as normal case. + +class MockPromise { + constructor(...args) { + return new Promise(...args); + } +} + +for (const expected of ["resolve", "reject"]) { + for (const unexpected of ["resolve", "reject"]) { + let resolve, reject; + const p = new Promise((a, b) => { + resolve = a; + reject = b; + }); + + // To prevent fast path in Promise.resolve that returns the passed promise, + // modify constructor property. + p.constructor = MockPromise; + + // Given `p` has custom constructor, Promise.resolve creates a new promise + // instead of returning `p` itself. + // + // Newly created promise `optimized` does not have resolution function + // instances. + const optimized = Promise.resolve(p); + + // Newly created promise `normal` has resolution functions. + const normal = new Promise(r => r(p)); + + // These calls should be ignored because [[AlreadyResolved]] == true, + if (unexpected === "resolve") { + resolvePromise(optimized, "unexpected resolve optimized"); + resolvePromise(normal, "unexpected resolve normal"); + } else { + rejectPromise(optimized, "unexpected reject optimized"); + rejectPromise(normal, "unexpected reject normal"); + } + + if (expected === "resolve") { + resolve("resolve"); + } else { + reject("reject"); + } + + let optimized_resolutionValue, optimized_rejectionValue; + optimized.then( + x => { optimized_resolutionValue = x; }, + x => { optimized_rejectionValue = x; } + ); + + let normal_resolutionValue, normal_rejectionValue; + normal.then( + x => { normal_resolutionValue = x; }, + x => { normal_rejectionValue = x; } + ); + + drainJobQueue(); + + if (expected === "resolve") { + assertEq(optimized_resolutionValue, "resolve", + `${expected} + ${unexpected}`); + assertEq(normal_resolutionValue, "resolve", + `${expected} + ${unexpected}`); + } else { + assertEq(optimized_rejectionValue, "reject", + `${expected} + ${unexpected}`); + assertEq(normal_rejectionValue, "reject", + `${expected} + ${unexpected}`); + } + } +} diff --git a/js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js b/js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js new file mode 100644 index 0000000000..4032f34bc4 --- /dev/null +++ b/js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js @@ -0,0 +1,5 @@ +// This just shouldn't crash. +ignoreUnhandledRejections(); + +Promise.resolve = () => 42; +Promise.all([1]); diff --git a/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js b/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js new file mode 100644 index 0000000000..27a178331a --- /dev/null +++ b/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js @@ -0,0 +1,58 @@ +// |jit-test| skip-if: !Promise.any + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var {promise, resolve} = newPromiseCapability(); + +var getterCount = 0; + +class P extends Promise { + constructor(executor) { + var {promise, resolve, reject} = newPromiseCapability(); + + executor(function(v) { + // Resolve the promise. + resolve(v); + + // But then return an object from the resolve function. This object + // must be treated as the resolution value for the otherwise + // skipped promise which gets created when Promise.prototype.then is + // called in PerformPromiseRace. + return { + get then() { + getterCount++; + } + }; + }, neverCalled); + + return promise; + } + + // Default to the standard Promise.resolve function, so we don't create + // another instance of this class when resolving the passed promise objects + // in Promise.race. + static resolve(v) { + return Promise.resolve(v); + } +} + +P.any([promise]); + +resolve(0); + +drainJobQueue(); + +assertEq(getterCount, 1); diff --git a/js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js b/js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js new file mode 100644 index 0000000000..18cd5a3a6d --- /dev/null +++ b/js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js @@ -0,0 +1,10 @@ +ignoreUnhandledRejections(); + +const global = newGlobal(); +const OtherPromise = global.Promise; +class SubPromise extends OtherPromise {} + +assertEq(true, new SubPromise(()=>{}) instanceof OtherPromise); +assertEq(true, SubPromise.resolve({}) instanceof OtherPromise); +assertEq(true, SubPromise.reject({}) instanceof OtherPromise); +assertEq(true, SubPromise.resolve({}).then(()=>{}, ()=>{}) instanceof OtherPromise); diff --git a/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js b/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js new file mode 100644 index 0000000000..815124c46f --- /dev/null +++ b/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js @@ -0,0 +1,54 @@ +function newPromiseCapability() { + let resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var c = 0; +var g_resolve; + +class P extends Promise { + constructor(executor) { + // Only the very first object created through this constructor gets + // special treatment, all other invocations create built-in Promise + // objects. + if (c++ > 1) { + return new Promise(executor); + } + + // Pass a native ResolvePromiseFunction function as the resolve handler. + // (It's okay that the promise of this promise capability is never used.) + executor(newPromiseCapability().resolve, neverCalled); + + let {promise, resolve} = newPromiseCapability(); + g_resolve = resolve; + + // Use an async function to create a Promise without resolving functions. + return async function(){ await promise; return 456; }(); + } + + // Ensure we don't take the (spec) fast path in Promise.resolve and instead + // create a new promise object. (We could not provide an override at all + // and rely on the default behaviour, but giving an explicit definition + // may help to interpret this test case.) + static resolve(v) { + return super.resolve(v); + } +} + +let {promise: alwaysPending} = newPromiseCapability(); + +P.race([alwaysPending]).then(neverCalled, neverCalled); + +g_resolve(123); + +drainJobQueue(); diff --git a/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js b/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js new file mode 100644 index 0000000000..23d4b95bc3 --- /dev/null +++ b/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js @@ -0,0 +1,56 @@ +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var {promise, resolve} = newPromiseCapability(); + +var getterCount = 0; + +class P extends Promise { + constructor(executor) { + var {promise, resolve, reject} = newPromiseCapability(); + + executor(function(v) { + // Resolve the promise. + resolve(v); + + // But then return an object from the resolve function. This object + // must be treated as the resolution value for the otherwise + // skipped promise which gets created when Promise.prototype.then is + // called in PerformPromiseRace. + return { + get then() { + getterCount++; + } + }; + }, neverCalled); + + return promise; + } + + // Default to the standard Promise.resolve function, so we don't create + // another instance of this class when resolving the passed promise objects + // in Promise.race. + static resolve(v) { + return Promise.resolve(v); + } +} + +P.race([promise]); + +resolve(0); + +drainJobQueue(); + +assertEq(getterCount, 1); diff --git a/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js b/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js new file mode 100644 index 0000000000..d69a6d9167 --- /dev/null +++ b/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js @@ -0,0 +1,15 @@ +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +resolve(Promise.resolve(0)); + +// Don't assert when the Promise was already resolved. +resolvePromise(promise, 123); diff --git a/js/src/jit-test/tests/promise/settle-async-generator.js b/js/src/jit-test/tests/promise/settle-async-generator.js new file mode 100644 index 0000000000..bdc9fcb51c --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-async-generator.js @@ -0,0 +1,23 @@ +// Force resolving/rejecting Promises returned by async generator's methods +// should fail. + +load(libdir + "asserts.js"); + +async function* f() { + yield 1; +} + +let p = f().next(); +assertThrowsInstanceOf(() => { + settlePromiseNow(p); +}, Error); + +p = f().next(); +assertThrowsInstanceOf(() => { + resolvePromise(p); +}, Error); + +p = f().next(); +assertThrowsInstanceOf(() => { + rejectPromise(p); +}, Error); diff --git a/js/src/jit-test/tests/promise/settle-now-already-resolved.js b/js/src/jit-test/tests/promise/settle-now-already-resolved.js new file mode 100644 index 0000000000..44186f868f --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-already-resolved.js @@ -0,0 +1,28 @@ +// |jit-test| error:Unhandled rejection + +load(libdir + "asserts.js"); + +// Calling settlePromiseNow on already-resolved promise should throw, and +// unhandled rejection tracking should work. + +assertThrowsInstanceOf(() => { + var promise = new Promise(resolve => { + resolve(10); + }); + settlePromiseNow(promise); +}, Error); + + +assertThrowsInstanceOf(() => { + var promise = new Promise((_, reject) => { + reject(10); + }); + settlePromiseNow(promise); +}, Error); + +assertThrowsInstanceOf(() => { + var promise = new Promise(() => { + throw 10; + }); + settlePromiseNow(promise); +}, Error); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js new file mode 100644 index 0000000000..940adf660d --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js @@ -0,0 +1,21 @@ +// Test we don't assert when the promise is settled after enqueuing a PromiseReactionJob. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var p = Promise.resolve(0); + +// Enqueue a PromiseResolveThenableJob followed by a PromiseReactionJob. +resolve(p); + +// The PromiseReactionJob expects a pending promise, but this settlePromiseNow +// call will already have settled the promise. +settlePromiseNow(promise); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js new file mode 100644 index 0000000000..4d19966cba --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js @@ -0,0 +1,18 @@ +// Don't assert when the promise in the resolving functions is wrapped in a CCW. + +function newPromiseCapability(newTarget) { + var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) { + resolve = r1; + reject = r2; + }], newTarget); + return {promise, resolve, reject}; +} + +var g = newGlobal(); + +var {promise, resolve} = newPromiseCapability(g.Promise); + +g.settlePromiseNow(promise); + +// Don't assert when resolving the promise. +resolve(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js new file mode 100644 index 0000000000..e4887b7267 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js @@ -0,0 +1,18 @@ +// Don't assert when the promise in the resolving functions is wrapped in a CCW. + +function newPromiseCapability(newTarget) { + var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) { + resolve = r1; + reject = r2; + }], newTarget); + return {promise, resolve, reject}; +} + +var g = newGlobal(); + +var {promise, reject} = newPromiseCapability(g.Promise); + +g.settlePromiseNow(promise); + +// Don't assert when rejecting the promise. +reject(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js new file mode 100644 index 0000000000..79129003e3 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js @@ -0,0 +1,27 @@ +// Test we don't assert when the promise is settled and the SpeciesConstructor +// call in Promise.prototype.then throws an exception. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var p = Promise.resolve(0); + +p.constructor = { + [Symbol.species]: function() { + throw new Error(); + } +}; + +// Enqueue a PromiseResolveThenableJob. +resolve(p); + +// Settle the promise after the resolve call. +settlePromiseNow(promise); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js new file mode 100644 index 0000000000..591eba456f --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js @@ -0,0 +1,27 @@ +// Test we don't assert when the promise is settled and the SpeciesConstructor +// call in Promise.prototype.then throws an exception. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var p = Promise.resolve(0); + +p.constructor = { + [Symbol.species]: function() { + // Settle the promise in the SpeciesConstructor call. + settlePromiseNow(promise); + + throw new Error(); + } +}; + +// Enqueue a PromiseResolveThenableJob. +resolve(p); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js new file mode 100644 index 0000000000..a9d147ccaa --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js @@ -0,0 +1,18 @@ +// Test we don't assert when the promise is settled and we then try to call the +// resolving function. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +settlePromiseNow(promise); + +// Don't assert when the promise is already settled. +resolve(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js new file mode 100644 index 0000000000..e5d2ceaafe --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js @@ -0,0 +1,18 @@ +// Test we don't assert when the promise is settled and we then try to call the +// rejecting function. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, reject} = newPromiseCapability(); + +settlePromiseNow(promise); + +// Don't assert when the promise is already settled. +reject(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js new file mode 100644 index 0000000000..d694f849a6 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js @@ -0,0 +1,20 @@ +// Don't assert when a side-effect when getting the "then" property settled the promise. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var thenable = { + get then() { + settlePromiseNow(promise); + } +}; + +resolve(thenable); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js new file mode 100644 index 0000000000..d6920f9b79 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js @@ -0,0 +1,23 @@ +// Don't assert when a side-effect when getting the "then" property settled the promise. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var thenable = { + get then() { + settlePromiseNow(promise); + + // Throw an error to reject the promise. + throw new Error(); + } +}; + +resolve(thenable); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js new file mode 100644 index 0000000000..8b581b7725 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js @@ -0,0 +1,7 @@ +// Don't assert when settlePromiseNow() is called on an async-function promise. + +var promise = async function(){ await 0; }(); + +try { + settlePromiseNow(promise); +} catch {} diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js new file mode 100644 index 0000000000..45413d768c --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js @@ -0,0 +1,15 @@ +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve, reject} = newPromiseCapability(); + +settlePromiseNow(promise); + +assertEq(resolve(0), undefined); +assertEq(reject(0), undefined); diff --git a/js/src/jit-test/tests/promise/species-redefine-getter.js b/js/src/jit-test/tests/promise/species-redefine-getter.js new file mode 100644 index 0000000000..4e30759cf2 --- /dev/null +++ b/js/src/jit-test/tests/promise/species-redefine-getter.js @@ -0,0 +1,20 @@ +// Ensure the HadGetterSetterChange flag is set. +Object.defineProperty(Promise, "foo", {configurable: true, get: function() {}}); +Object.defineProperty(Promise, "foo", {configurable: true, get: function() {}}); + +// Initialize PromiseLookup. +var p = new Promise(() => {}); +for (let i = 0; i < 5; i++) { + Promise.all([p]); +} + +// Redefine the Promise[Symbol.species] getter without changing its attributes/shape. +let count = 0; +Object.defineProperty(Promise, Symbol.species, + {get: function() { count++; }, enumerable: false, configurable: true}); + +// Ensure PromiseLookup now deoptimizes and calls the getter. +for (let i = 0; i < 5; i++) { + Promise.all([p]); +} +assertEq(count, 5); diff --git a/js/src/jit-test/tests/promise/stopdrainingjobqueue.js b/js/src/jit-test/tests/promise/stopdrainingjobqueue.js new file mode 100644 index 0000000000..534cc03039 --- /dev/null +++ b/js/src/jit-test/tests/promise/stopdrainingjobqueue.js @@ -0,0 +1,4 @@ +Promise.resolve() + .then(()=>quit(0)); +Promise.resolve() + .then(()=>crash("Must not run any more promise jobs after quitting"));
\ No newline at end of file diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-dead.js b/js/src/jit-test/tests/promise/unhandled-rejections-dead.js new file mode 100644 index 0000000000..d70a078c73 --- /dev/null +++ b/js/src/jit-test/tests/promise/unhandled-rejections-dead.js @@ -0,0 +1,17 @@ +// |jit-test| error:Unhandled rejection + +// Create the set object for unhandled rejection in this global. +async function fn() { e } +let p = fn(); + +var g = newGlobal(); +g.evaluate(` +async function fn() { e } +fn() +// Create unhandled rejection in another compartment. +// The promise is tracked by the unhandled rejection set with CCW. +P = newGlobal().eval("(class extends Promise { function(){} })") + +// Nuke the CCW to make the entry in unhandled rejection set a dead proxy. +Promise.all.call(P, [{ then() { nukeAllCCWs() } }]) +`); diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js b/js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js new file mode 100644 index 0000000000..866a865e2d --- /dev/null +++ b/js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js @@ -0,0 +1,19 @@ +// |jit-test| error:Unhandled rejection: "some reason" + +// Test JS shell's unhandled rejection tracking. + +var z = newGlobal(); + +Promise.prototype.then = z.Promise.prototype.then; + +// Add unhandled rejection from other realm. +evalcx("var p = (async function() { throw 'some reason' })()", z); + +// Add unhandled rejection from this realm. +var p = (async function f() { throw 'other reason'; })(); + +// Remove unhandled rejection from this realm. +p.then(); + +// Remove unhandled rejection from other realm. +evalcx("p.then()", z); diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-error.js b/js/src/jit-test/tests/promise/unhandled-rejections-error.js new file mode 100644 index 0000000000..48a2eedda2 --- /dev/null +++ b/js/src/jit-test/tests/promise/unhandled-rejections-error.js @@ -0,0 +1,3 @@ +// |jit-test| error:Error while printing unhandled rejection + +Promise.reject({ toSource() { throw "another error"; } }); diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-oom.js b/js/src/jit-test/tests/promise/unhandled-rejections-oom.js new file mode 100644 index 0000000000..706eba0032 --- /dev/null +++ b/js/src/jit-test/tests/promise/unhandled-rejections-oom.js @@ -0,0 +1,3 @@ +// |jit-test| allow-oom; skip-if: !('oomTest' in this) + +oomTest(async function() {}, { keepFailing: true }); diff --git a/js/src/jit-test/tests/promise/unhandled-rejections.js b/js/src/jit-test/tests/promise/unhandled-rejections.js new file mode 100644 index 0000000000..0bafa27fd0 --- /dev/null +++ b/js/src/jit-test/tests/promise/unhandled-rejections.js @@ -0,0 +1,3 @@ +// |jit-test| error:Unhandled rejection: "some reason" + +Promise.reject("some reason"); diff --git a/js/src/jit-test/tests/promise/user-activation-propagation.js b/js/src/jit-test/tests/promise/user-activation-propagation.js new file mode 100644 index 0000000000..5ec28eb3a5 --- /dev/null +++ b/js/src/jit-test/tests/promise/user-activation-propagation.js @@ -0,0 +1,19 @@ +function newPromiseCapability() { + return {}; +} +function neverCalled() {} +function resolveCapability(dIs) {} +class P extends Promise { + constructor(executor) { + executor(resolveCapability, neverCalled); + var p = async function() {}(); + p.constructor = { + [Symbol.species]: P + }; + return p; + } +} +var { + promise: alwaysPending +} = newPromiseCapability(); +P.race([alwaysPending]).then(neverCalled, neverCalled); |