summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenta Moriuchi <moriken@kimamass.com>2023-03-13 19:24:31 +0900
committerGitHub <noreply@github.com>2023-03-13 19:24:31 +0900
commite5673f5ed85774831234fe70290d5802bbd47c15 (patch)
tree59a8deb3e81bc596ec73afcaae01de9a1cb1e845
parentbcb6ee9d0864f490f6da47cbe2593310b21333ff (diff)
fix(core): `SafePromiseAll` to be unaffected by `Array#@@iterator` (#17542)
-rw-r--r--cli/tests/unit/command_test.ts6
-rw-r--r--cli/tests/unit/flash_test.ts6
-rw-r--r--core/00_primordials.js80
-rw-r--r--core/01_core.js3
-rw-r--r--core/internal.d.ts4
-rw-r--r--ext/http/01_http.js3
6 files changed, 82 insertions, 20 deletions
diff --git a/cli/tests/unit/command_test.ts b/cli/tests/unit/command_test.ts
index b9fa8295b..0763a7ac6 100644
--- a/cli/tests/unit/command_test.ts
+++ b/cli/tests/unit/command_test.ts
@@ -851,10 +851,11 @@ Deno.test(
Deno.test(
{ permissions: { read: true, run: true } },
- async function commandWithPromisePrototypeThenOverride() {
+ async function commandWithPrototypePollution() {
const originalThen = Promise.prototype.then;
+ const originalSymbolIterator = Array.prototype[Symbol.iterator];
try {
- Promise.prototype.then = () => {
+ Promise.prototype.then = Array.prototype[Symbol.iterator] = () => {
throw new Error();
};
await new Deno.Command(Deno.execPath(), {
@@ -862,6 +863,7 @@ Deno.test(
}).output();
} finally {
Promise.prototype.then = originalThen;
+ Array.prototype[Symbol.iterator] = originalSymbolIterator;
}
},
);
diff --git a/cli/tests/unit/flash_test.ts b/cli/tests/unit/flash_test.ts
index ebee7a024..d32e0a54f 100644
--- a/cli/tests/unit/flash_test.ts
+++ b/cli/tests/unit/flash_test.ts
@@ -2272,10 +2272,11 @@ Deno.test(
Deno.test(
{ permissions: { net: true } },
- async function serveWithPromisePrototypeThenOverride() {
+ async function serveWithPrototypePollution() {
const originalThen = Promise.prototype.then;
+ const originalSymbolIterator = Array.prototype[Symbol.iterator];
try {
- Promise.prototype.then = () => {
+ Promise.prototype.then = Array.prototype[Symbol.iterator] = () => {
throw new Error();
};
const ac = new AbortController();
@@ -2292,6 +2293,7 @@ Deno.test(
await server;
} finally {
Promise.prototype.then = originalThen;
+ Array.prototype[Symbol.iterator] = originalSymbolIterator;
}
},
);
diff --git a/core/00_primordials.js b/core/00_primordials.js
index 243f40e88..f49a11de4 100644
--- a/core/00_primordials.js
+++ b/core/00_primordials.js
@@ -329,10 +329,11 @@
return SafeIterator;
};
- primordials.SafeArrayIterator = createSafeIterator(
+ const SafeArrayIterator = createSafeIterator(
primordials.ArrayPrototypeSymbolIterator,
primordials.ArrayIteratorPrototypeNext,
);
+ primordials.SafeArrayIterator = SafeArrayIterator;
primordials.SafeSetIterator = createSafeIterator(
primordials.SetPrototypeSymbolIterator,
primordials.SetIteratorPrototypeNext,
@@ -479,29 +480,80 @@
primordials.PromisePrototypeCatch = (thisPromise, onRejected) =>
PromisePrototypeThen(thisPromise, undefined, onRejected);
+ const arrayToSafePromiseIterable = (array) =>
+ new SafeArrayIterator(
+ ArrayPrototypeMap(
+ array,
+ (p) => {
+ if (ObjectPrototypeIsPrototypeOf(PromisePrototype, p)) {
+ return new SafePromise((c, d) => PromisePrototypeThen(p, c, d));
+ }
+ return p;
+ },
+ ),
+ );
+
/**
* 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.
+ * @template T
+ * @param {Array<T | PromiseLike<T>>} values
+ * @returns {Promise<Awaited<T>[]>}
*/
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)
+ SafePromise.all(arrayToSafePromiseIterable(values)).then(a, b)
);
+ // NOTE: Uncomment the following functions when you need to use them
+
+ // /**
+ // * Creates a Promise that is resolved with an array of results when all
+ // * of the provided Promises resolve or reject.
+ // * @template T
+ // * @param {Array<T | PromiseLike<T>>} values
+ // * @returns {Promise<PromiseSettledResult<T>[]>}
+ // */
+ // primordials.SafePromiseAllSettled = (values) =>
+ // // Wrapping on a new Promise is necessary to not expose the SafePromise
+ // // prototype to user-land.
+ // new Promise((a, b) =>
+ // SafePromise.allSettled(arrayToSafePromiseIterable(values)).then(a, b)
+ // );
+
+ // /**
+ // * The any function returns a promise that is fulfilled by the first given
+ // * promise to be fulfilled, or rejected with an AggregateError containing
+ // * an array of rejection reasons if all of the given promises are rejected.
+ // * It resolves all elements of the passed iterable to promises as it runs
+ // * this algorithm.
+ // * @template T
+ // * @param {T} values
+ // * @returns {Promise<Awaited<T[number]>>}
+ // */
+ // primordials.SafePromiseAny = (values) =>
+ // // Wrapping on a new Promise is necessary to not expose the SafePromise
+ // // prototype to user-land.
+ // new Promise((a, b) =>
+ // SafePromise.any(arrayToSafePromiseIterable(values)).then(a, b)
+ // );
+
+ // /**
+ // * Creates a Promise that is resolved or rejected when any of the provided
+ // * Promises are resolved or rejected.
+ // * @template T
+ // * @param {T} values
+ // * @returns {Promise<Awaited<T[number]>>}
+ // */
+ // primordials.SafePromiseRace = (values) =>
+ // // Wrapping on a new Promise is necessary to not expose the SafePromise
+ // // prototype to user-land.
+ // new Promise((a, b) =>
+ // SafePromise.race(arrayToSafePromiseIterable(values)).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/core/01_core.js b/core/01_core.js
index 07ab758f1..a13bdc8dd 100644
--- a/core/01_core.js
+++ b/core/01_core.js
@@ -21,6 +21,7 @@
PromisePrototypeThen,
RangeError,
ReferenceError,
+ SafeArrayIterator,
SafePromisePrototypeFinally,
setQueueMicrotask,
StringPrototypeSlice,
@@ -198,7 +199,7 @@
const id = rollPromiseId();
let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult);
try {
- ops[name](id, ...args);
+ ops[name](id, ...new SafeArrayIterator(args));
} catch (err) {
// Cleanup the just-created promise
getPromise(id);
diff --git a/core/internal.d.ts b/core/internal.d.ts
index a91ac6244..e34cfbe71 100644
--- a/core/internal.d.ts
+++ b/core/internal.d.ts
@@ -75,6 +75,10 @@ declare namespace __bootstrap {
typeof globalThis.FinalizationRegistry;
export const SafeWeakRef: typeof globalThis.WeakRef;
export const SafePromiseAll: typeof Promise.all;
+ // NOTE: Uncomment the following functions when you need to use them
+ // export const SafePromiseAllSettled: typeof Promise.allSettled;
+ // export const SafePromiseAny: typeof Promise.any;
+ // export const SafePromiseRace: typeof Promise.race;
export const SafePromisePrototypeFinally: UncurryThis<
Promise.prototype.finally
>;
diff --git a/ext/http/01_http.js b/ext/http/01_http.js
index 1a8f8b853..fee30f7f0 100644
--- a/ext/http/01_http.js
+++ b/ext/http/01_http.js
@@ -510,7 +510,8 @@ function buildCaseInsensitiveCommaValueFinder(checkText) {
/** @param value {string} */
function hasWord(value) {
- for (const [cLower, cUpper] of charCodes) {
+ for (let j = 0; j < charCodes.length; ++j) {
+ const { 0: cLower, 1: cUpper } = charCodes[j];
if (cLower === char || cUpper === char) {
char = StringPrototypeCharCodeAt(value, ++i);
} else {