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 // https://github.com/denoland/deno/issues/15549
Deno.test( Deno.test(
{ permissions: { net: true } }, { permissions: { net: true } },

View file

@ -812,3 +812,20 @@ Deno.test(
assertStringIncludes(stdoutText, "typescript"); 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 { const {
ArrayPrototypeForEach, ArrayPrototypeForEach,
ArrayPrototypeMap,
FunctionPrototypeCall, FunctionPrototypeCall,
Map, Map,
ObjectDefineProperty, ObjectDefineProperty,
ObjectFreeze, ObjectFreeze,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf, ObjectSetPrototypeOf,
Promise, Promise,
PromisePrototype,
PromisePrototypeThen, PromisePrototypeThen,
Set, Set,
SymbolIterator, SymbolIterator,
@ -436,6 +439,29 @@
primordials.PromisePrototypeCatch = (thisPromise, onRejected) => primordials.PromisePrototypeCatch = (thisPromise, onRejected) =>
PromisePrototypeThen(thisPromise, undefined, 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 * Attaches a callback that is invoked when the Promise is settled (fulfilled or
* rejected). The resolved value cannot be modified from the callback. * rejected). The resolved value cannot be modified from the callback.

View file

@ -529,14 +529,15 @@
// 2.6. // 2.6.
// Rather than consuming the body as an ArrayBuffer, this passes each // Rather than consuming the body as an ArrayBuffer, this passes each
// chunk to the feed as soon as it's available. // chunk to the feed as soon as it's available.
(async () => { PromisePrototypeThen(
const reader = res.body.getReader(); (async () => {
while (true) { const reader = res.body.getReader();
const { value: chunk, done } = await reader.read(); while (true) {
if (done) break; const { value: chunk, done } = await reader.read();
ops.op_wasm_streaming_feed(rid, chunk); if (done) break;
} ops.op_wasm_streaming_feed(rid, chunk);
})().then( }
})(),
// 2.7 // 2.7
() => core.close(rid), () => core.close(rid),
// 2.8 // 2.8

View file

@ -29,11 +29,13 @@
const { const {
Function, Function,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
PromiseAll, Promise,
PromisePrototypeCatch,
PromisePrototypeThen,
SafePromiseAll,
TypedArrayPrototypeSubarray, TypedArrayPrototypeSubarray,
TypeError, TypeError,
Uint8Array, Uint8Array,
Promise,
Uint8ArrayPrototype, Uint8ArrayPrototype,
} = window.__bootstrap.primordials; } = window.__bootstrap.primordials;
@ -342,24 +344,27 @@
} }
const reader = respBody.getReader(); // Aquire JS lock. const reader = respBody.getReader(); // Aquire JS lock.
try { try {
core.opAsync( PromisePrototypeThen(
"op_flash_write_resource", core.opAsync(
http1Response( "op_flash_write_resource",
method, http1Response(
innerResp.status ?? 200, method,
innerResp.headerList, innerResp.status ?? 200,
0, // Content-Length will be set by the op. innerResp.headerList,
null, 0, // Content-Length will be set by the op.
true, null,
true,
),
serverId,
i,
resourceBacking.rid,
resourceBacking.autoClose,
), ),
serverId, () => {
i, // Release JS lock.
resourceBacking.rid, readableStreamClose(respBody);
resourceBacking.autoClose, },
).then(() => { );
// Release JS lock.
readableStreamClose(respBody);
});
} catch (error) { } catch (error) {
await reader.cancel(error); await reader.cancel(error);
throw error; throw error;
@ -486,10 +491,16 @@
const serverId = core.ops.op_flash_serve(listenOpts); const serverId = core.ops.op_flash_serve(listenOpts);
const serverPromise = core.opAsync("op_flash_drive_server", serverId); const serverPromise = core.opAsync("op_flash_drive_server", serverId);
core.opAsync("op_flash_wait_for_listening", serverId).then((port) => { PromisePrototypeCatch(
onListen({ hostname: listenOpts.hostname, port }); PromisePrototypeThen(
}).catch(() => {}); core.opAsync("op_flash_wait_for_listening", serverId),
const finishedPromise = serverPromise.catch(() => {}); (port) => {
onListen({ hostname: listenOpts.hostname, port });
},
),
() => {},
);
const finishedPromise = PromisePrototypeCatch(serverPromise, () => {});
const server = { const server = {
id: serverId, id: serverId,
@ -554,7 +565,27 @@
let resp; let resp;
try { try {
resp = handler(req); 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) => resp.then((resp) =>
handleResponse( handleResponse(
req, req,
@ -595,7 +626,7 @@
signal?.addEventListener("abort", () => { signal?.addEventListener("abort", () => {
clearInterval(dateInterval); clearInterval(dateInterval);
server.close().then(() => {}, () => {}); PromisePrototypeThen(server.close(), () => {}, () => {});
}, { }, {
once: true, once: true,
}); });
@ -638,8 +669,8 @@
}, 1000); }, 1000);
} }
await PromiseAll([ await SafePromiseAll([
server.serve().catch(console.error), PromisePrototypeCatch(server.serve(), console.error),
serverPromise, serverPromise,
]); ]);
} }

View file

@ -35,7 +35,6 @@
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf, ObjectSetPrototypeOf,
Promise, Promise,
PromiseAll,
PromisePrototypeCatch, PromisePrototypeCatch,
PromisePrototypeThen, PromisePrototypeThen,
PromiseReject, PromiseReject,
@ -43,6 +42,7 @@
queueMicrotask, queueMicrotask,
RangeError, RangeError,
ReflectHas, ReflectHas,
SafePromiseAll,
SharedArrayBuffer, SharedArrayBuffer,
Symbol, Symbol,
SymbolAsyncIterator, SymbolAsyncIterator,
@ -2302,7 +2302,8 @@
}); });
} }
shutdownWithAction( shutdownWithAction(
() => PromiseAll(ArrayPrototypeMap(actions, (action) => action())), () =>
SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())),
true, true,
error, error,
); );

View file

@ -27,12 +27,12 @@
ObjectDefineProperty, ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
Promise, Promise,
PromiseAll,
PromisePrototypeCatch, PromisePrototypeCatch,
PromisePrototypeThen, PromisePrototypeThen,
PromiseReject, PromiseReject,
PromiseResolve, PromiseResolve,
SafeArrayIterator, SafeArrayIterator,
SafePromiseAll,
Set, Set,
SetPrototypeEntries, SetPrototypeEntries,
SetPrototypeForEach, SetPrototypeForEach,
@ -1517,7 +1517,7 @@
"OperationError", "OperationError",
); );
} }
const operations = PromiseAll(scope.operations); const operations = SafePromiseAll(scope.operations);
return PromisePrototypeThen( return PromisePrototypeThen(
operations, operations,
() => PromiseResolve(null), () => PromiseResolve(null),

View file

@ -13,7 +13,8 @@
String, String,
TypeError, TypeError,
Uint8Array, Uint8Array,
PromiseAll, PromisePrototypeThen,
SafePromiseAll,
SymbolFor, SymbolFor,
} = window.__bootstrap.primordials; } = window.__bootstrap.primordials;
const { const {
@ -155,7 +156,7 @@
const waitPromise = core.opAsync("op_spawn_wait", this.#rid); const waitPromise = core.opAsync("op_spawn_wait", this.#rid);
this.#waitPromiseId = waitPromise[promiseIdSymbol]; this.#waitPromiseId = waitPromise[promiseIdSymbol];
this.#status = waitPromise.then((res) => { this.#status = PromisePrototypeThen(waitPromise, (res) => {
this.#rid = null; this.#rid = null;
signal?.[remove](onAbort); signal?.[remove](onAbort);
return res; return res;
@ -179,7 +180,7 @@
); );
} }
const [status, stdout, stderr] = await PromiseAll([ const [status, stdout, stderr] = await SafePromiseAll([
this.#status, this.#status,
collectOutput(this.#stdout), collectOutput(this.#stdout),
collectOutput(this.#stderr), collectOutput(this.#stderr),