diff options
author | Kenta Moriuchi <moriken@kimamass.com> | 2023-03-13 19:24:31 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-13 19:24:31 +0900 |
commit | e5673f5ed85774831234fe70290d5802bbd47c15 (patch) | |
tree | 59a8deb3e81bc596ec73afcaae01de9a1cb1e845 | |
parent | bcb6ee9d0864f490f6da47cbe2593310b21333ff (diff) |
fix(core): `SafePromiseAll` to be unaffected by `Array#@@iterator` (#17542)
-rw-r--r-- | cli/tests/unit/command_test.ts | 6 | ||||
-rw-r--r-- | cli/tests/unit/flash_test.ts | 6 | ||||
-rw-r--r-- | core/00_primordials.js | 80 | ||||
-rw-r--r-- | core/01_core.js | 3 | ||||
-rw-r--r-- | core/internal.d.ts | 4 | ||||
-rw-r--r-- | ext/http/01_http.js | 3 |
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 { |