diff --git a/cli/tests/unit/flash_test.ts b/cli/tests/unit/flash_test.ts index 375fdb8f366eb..e2e64dfe3ff49 100644 --- a/cli/tests/unit/flash_test.ts +++ b/cli/tests/unit/flash_test.ts @@ -2227,6 +2227,32 @@ Deno.test( }, ); +Deno.test( + { permissions: { net: true } }, + async function serveWithPromisePrototypeThenOverride() { + const originalThen = Promise.prototype.then; + try { + Promise.prototype.then = () => { + throw new Error(); + }; + const ac = new AbortController(); + const listeningPromise = deferred(); + const server = Deno.serve({ + handler: (_req) => new Response("ok"), + hostname: "localhost", + port: 4501, + signal: ac.signal, + onListen: onListen(listeningPromise), + onError: createOnErrorCb(ac), + }); + ac.abort(); + await server; + } finally { + Promise.prototype.then = originalThen; + } + }, +); + // https://github.com/denoland/deno/issues/15549 Deno.test( { permissions: { net: true } }, diff --git a/cli/tests/unit/spawn_test.ts b/cli/tests/unit/spawn_test.ts index 149886a1ceff7..10095be95ea00 100644 --- a/cli/tests/unit/spawn_test.ts +++ b/cli/tests/unit/spawn_test.ts @@ -812,3 +812,20 @@ Deno.test( assertStringIncludes(stdoutText, "typescript"); }, ); + +Deno.test( + { permissions: { read: true, run: true } }, + async function spawnWithPromisePrototypeThenOverride() { + const originalThen = Promise.prototype.then; + try { + Promise.prototype.then = () => { + throw new Error(); + }; + await Deno.spawn(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }); + } finally { + Promise.prototype.then = originalThen; + } + }, +); diff --git a/core/00_primordials.js b/core/00_primordials.js index 843eb8b294e13..d48dfde79e2c7 100644 --- a/core/00_primordials.js +++ b/core/00_primordials.js @@ -275,12 +275,15 @@ const { ArrayPrototypeForEach, + ArrayPrototypeMap, FunctionPrototypeCall, Map, ObjectDefineProperty, ObjectFreeze, + ObjectPrototypeIsPrototypeOf, ObjectSetPrototypeOf, Promise, + PromisePrototype, PromisePrototypeThen, Set, SymbolIterator, @@ -436,6 +439,29 @@ primordials.PromisePrototypeCatch = (thisPromise, onRejected) => PromisePrototypeThen(thisPromise, undefined, onRejected); + /** + * Creates a Promise that is resolved with an array of results when all of the + * provided Promises resolve, or rejected when any Promise is rejected. + * @param {unknown[]} values An array of Promises. + * @returns A new Promise. + */ + primordials.SafePromiseAll = (values) => + // Wrapping on a new Promise is necessary to not expose the SafePromise + // prototype to user-land. + new Promise((a, b) => + SafePromise.all( + ArrayPrototypeMap( + values, + (p) => { + if (ObjectPrototypeIsPrototypeOf(PromisePrototype, p)) { + return new SafePromise((c, d) => PromisePrototypeThen(p, c, d)); + } + return p; + }, + ), + ).then(a, b) + ); + /** * Attaches a callback that is invoked when the Promise is settled (fulfilled or * rejected). The resolved value cannot be modified from the callback. diff --git a/ext/fetch/26_fetch.js b/ext/fetch/26_fetch.js index 5c824898de8cd..e522079bf75d2 100644 --- a/ext/fetch/26_fetch.js +++ b/ext/fetch/26_fetch.js @@ -529,14 +529,15 @@ // 2.6. // Rather than consuming the body as an ArrayBuffer, this passes each // chunk to the feed as soon as it's available. - (async () => { - const reader = res.body.getReader(); - while (true) { - const { value: chunk, done } = await reader.read(); - if (done) break; - ops.op_wasm_streaming_feed(rid, chunk); - } - })().then( + PromisePrototypeThen( + (async () => { + const reader = res.body.getReader(); + while (true) { + const { value: chunk, done } = await reader.read(); + if (done) break; + ops.op_wasm_streaming_feed(rid, chunk); + } + })(), // 2.7 () => core.close(rid), // 2.8 diff --git a/ext/flash/01_http.js b/ext/flash/01_http.js index 5e6cb69aa357e..df013ce6578cc 100644 --- a/ext/flash/01_http.js +++ b/ext/flash/01_http.js @@ -29,11 +29,13 @@ const { Function, ObjectPrototypeIsPrototypeOf, - PromiseAll, + Promise, + PromisePrototypeCatch, + PromisePrototypeThen, + SafePromiseAll, TypedArrayPrototypeSubarray, TypeError, Uint8Array, - Promise, Uint8ArrayPrototype, } = window.__bootstrap.primordials; @@ -342,24 +344,27 @@ } const reader = respBody.getReader(); // Aquire JS lock. try { - core.opAsync( - "op_flash_write_resource", - http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - 0, // Content-Length will be set by the op. - null, - true, + PromisePrototypeThen( + core.opAsync( + "op_flash_write_resource", + http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + 0, // Content-Length will be set by the op. + null, + true, + ), + serverId, + i, + resourceBacking.rid, + resourceBacking.autoClose, ), - serverId, - i, - resourceBacking.rid, - resourceBacking.autoClose, - ).then(() => { - // Release JS lock. - readableStreamClose(respBody); - }); + () => { + // Release JS lock. + readableStreamClose(respBody); + }, + ); } catch (error) { await reader.cancel(error); throw error; @@ -486,10 +491,16 @@ const serverId = core.ops.op_flash_serve(listenOpts); const serverPromise = core.opAsync("op_flash_drive_server", serverId); - core.opAsync("op_flash_wait_for_listening", serverId).then((port) => { - onListen({ hostname: listenOpts.hostname, port }); - }).catch(() => {}); - const finishedPromise = serverPromise.catch(() => {}); + PromisePrototypeCatch( + PromisePrototypeThen( + core.opAsync("op_flash_wait_for_listening", serverId), + (port) => { + onListen({ hostname: listenOpts.hostname, port }); + }, + ), + () => {}, + ); + const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); const server = { id: serverId, @@ -554,7 +565,27 @@ let resp; try { resp = handler(req); - if (resp instanceof Promise || typeof resp?.then === "function") { + if (resp instanceof Promise) { + PromisePrototypeCatch( + PromisePrototypeThen( + resp, + (resp) => + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + ), + ), + onError, + ); + continue; + } else if (typeof resp?.then === "function") { resp.then((resp) => handleResponse( req, @@ -595,7 +626,7 @@ signal?.addEventListener("abort", () => { clearInterval(dateInterval); - server.close().then(() => {}, () => {}); + PromisePrototypeThen(server.close(), () => {}, () => {}); }, { once: true, }); @@ -638,8 +669,8 @@ }, 1000); } - await PromiseAll([ - server.serve().catch(console.error), + await SafePromiseAll([ + PromisePrototypeCatch(server.serve(), console.error), serverPromise, ]); } diff --git a/ext/web/06_streams.js b/ext/web/06_streams.js index 6dbf69951e2ad..752ef3ad6ce73 100644 --- a/ext/web/06_streams.js +++ b/ext/web/06_streams.js @@ -35,7 +35,6 @@ ObjectPrototypeIsPrototypeOf, ObjectSetPrototypeOf, Promise, - PromiseAll, PromisePrototypeCatch, PromisePrototypeThen, PromiseReject, @@ -43,6 +42,7 @@ queueMicrotask, RangeError, ReflectHas, + SafePromiseAll, SharedArrayBuffer, Symbol, SymbolAsyncIterator, @@ -2317,7 +2317,8 @@ }); } shutdownWithAction( - () => PromiseAll(ArrayPrototypeMap(actions, (action) => action())), + () => + SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())), true, error, ); diff --git a/ext/webgpu/src/01_webgpu.js b/ext/webgpu/src/01_webgpu.js index caa103e625274..f4d15e2dd8432 100644 --- a/ext/webgpu/src/01_webgpu.js +++ b/ext/webgpu/src/01_webgpu.js @@ -27,12 +27,12 @@ ObjectDefineProperty, ObjectPrototypeIsPrototypeOf, Promise, - PromiseAll, PromisePrototypeCatch, PromisePrototypeThen, PromiseReject, PromiseResolve, SafeArrayIterator, + SafePromiseAll, Set, SetPrototypeEntries, SetPrototypeForEach, @@ -1517,7 +1517,7 @@ "OperationError", ); } - const operations = PromiseAll(scope.operations); + const operations = SafePromiseAll(scope.operations); return PromisePrototypeThen( operations, () => PromiseResolve(null), diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js index a9a968ba32949..a927d619ed4c2 100644 --- a/runtime/js/40_spawn.js +++ b/runtime/js/40_spawn.js @@ -13,7 +13,8 @@ String, TypeError, Uint8Array, - PromiseAll, + PromisePrototypeThen, + SafePromiseAll, SymbolFor, } = window.__bootstrap.primordials; const { @@ -155,7 +156,7 @@ const waitPromise = core.opAsync("op_spawn_wait", this.#rid); this.#waitPromiseId = waitPromise[promiseIdSymbol]; - this.#status = waitPromise.then((res) => { + this.#status = PromisePrototypeThen(waitPromise, (res) => { this.#rid = null; signal?.[remove](onAbort); return res; @@ -179,7 +180,7 @@ ); } - const [status, stdout, stderr] = await PromiseAll([ + const [status, stdout, stderr] = await SafePromiseAll([ this.#status, collectOutput(this.#stdout), collectOutput(this.#stderr),