summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenta Moriuchi <moriken@kimamass.com>2022-10-29 18:25:23 +0900
committerGitHub <noreply@github.com>2022-10-29 18:25:23 +0900
commit59ac110edd1f376bed7fa6bbdbe2ee09c266bf74 (patch)
treeda654d1ecb6141b620141d634d99ca34c6d568db
parentedaceecec771cf0395639175b5a21d20530f6080 (diff)
fix(core): fix APIs not to be affected by `Promise.prototype.then` modification (#16326)
-rw-r--r--cli/tests/unit/flash_test.ts26
-rw-r--r--cli/tests/unit/spawn_test.ts17
-rw-r--r--core/00_primordials.js26
-rw-r--r--ext/fetch/26_fetch.js17
-rw-r--r--ext/flash/01_http.js85
-rw-r--r--ext/web/06_streams.js5
-rw-r--r--ext/webgpu/src/01_webgpu.js4
-rw-r--r--runtime/js/40_spawn.js7
8 files changed, 145 insertions, 42 deletions
diff --git a/cli/tests/unit/flash_test.ts b/cli/tests/unit/flash_test.ts
index 375fdb8f3..e2e64dfe3 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 149886a1c..10095be95 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 843eb8b29..d48dfde79 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,
@@ -437,6 +440,29 @@
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.
* Prefer using async functions when possible.
diff --git a/ext/fetch/26_fetch.js b/ext/fetch/26_fetch.js
index 5c824898d..e522079bf 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 5e6cb69aa..df013ce65 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 06397265c..0b9e00483 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,
@@ -2302,7 +2302,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 caa103e62..f4d15e2dd 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 a9a968ba3..a927d619e 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),