fix(core): fix APIs not to be affected by Promise.prototype.then modification (#16326)

This commit is contained in:
Kenta Moriuchi 2022-10-29 18:25:23 +09:00 committed by GitHub
parent edaceecec7
commit 59ac110edd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 42 deletions

View file

@ -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 } },

View file

@ -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;
}
},
);

View file

@ -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.

View file

@ -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

View file

@ -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,
]);
}

View file

@ -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,
);

View file

@ -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),

View file

@ -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),