| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6e30d22 commit 40a5e22
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -363,33 +363,52 @@ Object.defineProperty(Object.prototype, Symbol.isConcatSpreadable, { | |||
| 363 | 363 | // 1. Lookup @@iterator property on `array` (user-mutable if user-provided). | |
| 364 | 364 | // 2. Lookup @@iterator property on %Array.prototype% (user-mutable). | |
| 365 | 365 | // 3. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). | |
| 366 | + // 4. Lookup `then` property on %Array.Prototype% (user-mutable). | ||
| 367 | + // 5. Lookup `then` property on %Object.Prototype% (user-mutable). | ||
| 366 | 368 | PromiseAll([]); // unsafe | |
| 367 | 369 | ||
| 368 | - PromiseAll(new SafeArrayIterator([])); // safe | ||
| 370 | + // 1. Lookup `then` property on %Array.Prototype% (user-mutable). | ||
| 371 | + // 2. Lookup `then` property on %Object.Prototype% (user-mutable). | ||
| 372 | + PromiseAll(new SafeArrayIterator([])); // still unsafe | ||
| 373 | + SafePromiseAll([]); // still unsafe | ||
| 374 | + | ||
| 375 | + SafePromiseAllReturnVoid([]); // safe | ||
| 376 | + SafePromiseAllReturnArrayLike([]); // safe | ||
| 369 | 377 | ||
| 370 | 378 | const array = [promise]; | |
| 371 | 379 | const set = new SafeSet().add(promise); | |
| 372 | 380 | // When running one of these functions on a non-empty iterable, it will also: | |
| 373 | - // 4. Lookup `then` property on `promise` (user-mutable if user-provided). | ||
| 374 | - // 5. Lookup `then` property on `%Promise.prototype%` (user-mutable). | ||
| 381 | + // 1. Lookup `then` property on `promise` (user-mutable if user-provided). | ||
| 382 | + // 2. Lookup `then` property on `%Promise.prototype%` (user-mutable). | ||
| 383 | + // 3. Lookup `then` property on %Array.Prototype% (user-mutable). | ||
| 384 | + // 4. Lookup `then` property on %Object.Prototype% (user-mutable). | ||
| 375 | 385 | PromiseAll(new SafeArrayIterator(array)); // unsafe | |
| 376 | - | ||
| 377 | 386 | PromiseAll(set); // unsafe | |
| 378 | 387 | ||
| 379 | - SafePromiseAll(array); // safe | ||
| 388 | + SafePromiseAllReturnVoid(array); // safe | ||
| 389 | + SafePromiseAllReturnArrayLike(array); // safe | ||
| 380 | 390 | ||
| 381 | 391 | // Some key differences between `SafePromise[...]` and `Promise[...]` methods: | |
| 382 | 392 | ||
| 383 | - // 1. SafePromiseAll, SafePromiseAllSettled, SafePromiseAny, and SafePromiseRace | ||
| 384 | - // support passing a mapperFunction as second argument. | ||
| 393 | + // 1. SafePromiseAll, SafePromiseAllSettled, SafePromiseAny, SafePromiseRace, | ||
| 394 | + // SafePromiseAllReturnArrayLike, SafePromiseAllReturnVoid, and | ||
| 395 | + // SafePromiseAllSettledReturnVoid support passing a mapperFunction as second | ||
| 396 | + // argument. | ||
| 385 | 397 | SafePromiseAll(ArrayPrototypeMap(array, someFunction)); | |
| 386 | 398 | SafePromiseAll(array, someFunction); // Same as the above, but more efficient. | |
| 387 | 399 | ||
| 388 | - // 2. SafePromiseAll, SafePromiseAllSettled, SafePromiseAny, and SafePromiseRace | ||
| 389 | - // only support arrays, not iterables. Use ArrayFrom to convert an iterable | ||
| 390 | - // to an array. | ||
| 391 | - SafePromiseAll(set); // ignores set content. | ||
| 392 | - SafePromiseAll(ArrayFrom(set)); // safe | ||
| 400 | + // 2. SafePromiseAll, SafePromiseAllSettled, SafePromiseAny, SafePromiseRace, | ||
| 401 | + // SafePromiseAllReturnArrayLike, SafePromiseAllReturnVoid, and | ||
| 402 | + // SafePromiseAllSettledReturnVoid only support arrays and array-like | ||
| 403 | + // objects, not iterables. Use ArrayFrom to convert an iterable to an array. | ||
| 404 | + SafePromiseAllReturnVoid(set); // ignores set content. | ||
| 405 | + SafePromiseAllReturnVoid(ArrayFrom(set)); // works | ||
| 406 | + | ||
| 407 | + // 3. SafePromiseAllReturnArrayLike is safer than SafePromiseAll, however you | ||
| 408 | + // should not use them when its return value is passed to the user as it can | ||
| 409 | + // be surprising for them not to receive a genuine array. | ||
| 410 | + SafePromiseAllReturnArrayLike(array).then((val) => val instanceof Array); // false | ||
| 411 | + SafePromiseAll(array).then((val) => val instanceof Array); // true | ||
| 393 | 412 | ``` | |
| 394 | 413 | ||
| 395 | 414 | </details> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,7 +14,7 @@ const { | |||
| 14 | 14 | ObjectDefineProperty, | |
| 15 | 15 | ObjectSetPrototypeOf, | |
| 16 | 16 | RegExpPrototypeExec, | |
| 17 | - SafePromiseAll, | ||
| 17 | + SafePromiseAllReturnArrayLike, | ||
| 18 | 18 | SafeWeakMap, | |
| 19 | 19 | StringPrototypeSlice, | |
| 20 | 20 | StringPrototypeToUpperCase, | |
@@ -516,7 +516,7 @@ class ESMLoader { | |||
| 516 | 516 | .then(({ module }) => module.getNamespace()); | |
| 517 | 517 | } | |
| 518 | 518 | ||
| 519 | - const namespaces = await SafePromiseAll(jobs); | ||
| 519 | + const namespaces = await SafePromiseAllReturnArrayLike(jobs); | ||
| 520 | 520 | ||
| 521 | 521 | if (!wasArr) { return namespaces[0]; } // We can skip the pairing below | |
| 522 | 522 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,7 +12,8 @@ const { | |||
| 12 | 12 | ReflectApply, | |
| 13 | 13 | RegExpPrototypeExec, | |
| 14 | 14 | RegExpPrototypeSymbolReplace, | |
| 15 | - SafePromiseAll, | ||
| 15 | + SafePromiseAllReturnArrayLike, | ||
| 16 | + SafePromiseAllReturnVoid, | ||
| 16 | 17 | SafeSet, | |
| 17 | 18 | StringPrototypeIncludes, | |
| 18 | 19 | StringPrototypeSplit, | |
@@ -80,9 +81,9 @@ class ModuleJob { | |||
| 80 | 81 | }); | |
| 81 | 82 | ||
| 82 | 83 | if (promises !== undefined) | |
| 83 | - await SafePromiseAll(promises); | ||
| 84 | + await SafePromiseAllReturnVoid(promises); | ||
| 84 | 85 | ||
| 85 | - return SafePromiseAll(dependencyJobs); | ||
| 86 | + return SafePromiseAllReturnArrayLike(dependencyJobs); | ||
| 86 | 87 | }; | |
| 87 | 88 | // Promise for the list of all dependencyJobs. | |
| 88 | 89 | this.linked = link(); | |
@@ -110,7 +111,7 @@ class ModuleJob { | |||
| 110 | 111 | } | |
| 111 | 112 | jobsInGraph.add(moduleJob); | |
| 112 | 113 | const dependencyJobs = await moduleJob.linked; | |
| 113 | - return SafePromiseAll(dependencyJobs, addJobsToDependencyGraph); | ||
| 114 | + return SafePromiseAllReturnVoid(dependencyJobs, addJobsToDependencyGraph); | ||
| 114 | 115 | }; | |
| 115 | 116 | await addJobsToDependencyGraph(this); | |
| 116 | 117 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -261,6 +261,7 @@ function copyPrototype(src, dest, prefix) { | |||
| 261 | 261 | /* eslint-enable node-core/prefer-primordials */ | |
| 262 | 262 | ||
| 263 | 263 | const { | |
| 264 | + Array: ArrayConstructor, | ||
| 264 | 265 | ArrayPrototypeForEach, | |
| 265 | 266 | ArrayPrototypeMap, | |
| 266 | 267 | FinalizationRegistry, | |
@@ -272,6 +273,7 @@ const { | |||
| 272 | 273 | ObjectSetPrototypeOf, | |
| 273 | 274 | Promise, | |
| 274 | 275 | PromisePrototypeThen, | |
| 276 | + PromiseResolve, | ||
| 275 | 277 | ReflectApply, | |
| 276 | 278 | ReflectConstruct, | |
| 277 | 279 | ReflectSet, | |
@@ -466,9 +468,10 @@ const arrayToSafePromiseIterable = (promises, mapFn) => | |||
| 466 | 468 | ); | |
| 467 | 469 | ||
| 468 | 470 | /** | |
| 469 | - * @param {Promise<any>[]} promises | ||
| 470 | - * @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn] | ||
| 471 | - * @returns {Promise<any[]>} | ||
| 471 | + * @template T,U | ||
| 472 | + * @param {Array<T | PromiseLike<T>>} promises | ||
| 473 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 474 | + * @returns {Promise<Awaited<U>[]>} | ||
| 472 | 475 | */ | |
| 473 | 476 | primordials.SafePromiseAll = (promises, mapFn) => | |
| 474 | 477 | // Wrapping on a new Promise is necessary to not expose the SafePromise | |
@@ -478,8 +481,56 @@ primordials.SafePromiseAll = (promises, mapFn) => | |||
| 478 | 481 | ); | |
| 479 | 482 | ||
| 480 | 483 | /** | |
| 481 | - * @param {Promise<any>[]} promises | ||
| 482 | - * @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn] | ||
| 484 | + * Should only be used for internal functions, this would produce similar | ||
| 485 | + * results as `Promise.all` but without prototype pollution, and the return | ||
| 486 | + * value is not a genuine Array but an array-like object. | ||
| 487 | + * @template T,U | ||
| 488 | + * @param {ArrayLike<T | PromiseLike<T>>} promises | ||
| 489 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 490 | + * @returns {Promise<ArrayLike<Awaited<U>>>} | ||
| 491 | + */ | ||
| 492 | + primordials.SafePromiseAllReturnArrayLike = (promises, mapFn) => | ||
| 493 | + new Promise((resolve, reject) => { | ||
| 494 | + const { length } = promises; | ||
| 495 | + | ||
| 496 | + const returnVal = ArrayConstructor(length); | ||
| 497 | + ObjectSetPrototypeOf(returnVal, null); | ||
| 498 | + if (length === 0) resolve(returnVal); | ||
| 499 | + | ||
| 500 | + let pendingPromises = length; | ||
| 501 | + for (let i = 0; i < length; i++) { | ||
| 502 | + const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; | ||
| 503 | + PromisePrototypeThen(PromiseResolve(promise), (result) => { | ||
| 504 | + returnVal[i] = result; | ||
| 505 | + if (--pendingPromises === 0) resolve(returnVal); | ||
| 506 | + }, reject); | ||
| 507 | + } | ||
| 508 | + }); | ||
| 509 | + | ||
| 510 | + /** | ||
| 511 | + * Should only be used when we only care about waiting for all the promises to | ||
| 512 | + * resolve, not what value they resolve to. | ||
| 513 | + * @template T,U | ||
| 514 | + * @param {ArrayLike<T | PromiseLike<T>>} promises | ||
| 515 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 516 | + * @returns {Promise<void>} | ||
| 517 | + */ | ||
| 518 | + primordials.SafePromiseAllReturnVoid = (promises, mapFn) => | ||
| 519 | + new Promise((resolve, reject) => { | ||
| 520 | + let pendingPromises = promises.length; | ||
| 521 | + if (pendingPromises === 0) resolve(); | ||
| 522 | + for (let i = 0; i < promises.length; i++) { | ||
| 523 | + const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; | ||
| 524 | + PromisePrototypeThen(PromiseResolve(promise), () => { | ||
| 525 | + if (--pendingPromises === 0) resolve(); | ||
| 526 | + }, reject); | ||
| 527 | + } | ||
| 528 | + }); | ||
| 529 | + | ||
| 530 | + /** | ||
| 531 | + * @template T,U | ||
| 532 | + * @param {Array<T|PromiseLike<T>>} promises | ||
| 533 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 483 | 534 | * @returns {Promise<PromiseSettledResult<any>[]>} | |
| 484 | 535 | */ | |
| 485 | 536 | primordials.SafePromiseAllSettled = (promises, mapFn) => | |
@@ -490,9 +541,28 @@ primordials.SafePromiseAllSettled = (promises, mapFn) => | |||
| 490 | 541 | ); | |
| 491 | 542 | ||
| 492 | 543 | /** | |
| 493 | - * @param {Promise<any>[]} promises | ||
| 494 | - * @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn] | ||
| 495 | - * @returns {Promise<any>} | ||
| 544 | + * Should only be used when we only care about waiting for all the promises to | ||
| 545 | + * settle, not what value they resolve or reject to. | ||
| 546 | + * @template T,U | ||
| 547 | + * @param {ArrayLike<T|PromiseLike<T>>} promises | ||
| 548 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 549 | + * @returns {Promise<void>} | ||
| 550 | + */ | ||
| 551 | + primordials.SafePromiseAllSettledReturnVoid = async (promises, mapFn) => { | ||
| 552 | + for (let i = 0; i < promises.length; i++) { | ||
| 553 | + try { | ||
| 554 | + await (mapFn != null ? mapFn(promises[i], i) : promises[i]); | ||
| 555 | + } catch { | ||
| 556 | + // In all settled, we can ignore errors. | ||
| 557 | + } | ||
| 558 | + } | ||
| 559 | + }; | ||
| 560 | + | ||
| 561 | + /** | ||
| 562 | + * @template T,U | ||
| 563 | + * @param {Array<T|PromiseLike<T>>} promises | ||
| 564 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 565 | + * @returns {Promise<Awaited<U>>} | ||
| 496 | 566 | */ | |
| 497 | 567 | primordials.SafePromiseAny = (promises, mapFn) => | |
| 498 | 568 | // Wrapping on a new Promise is necessary to not expose the SafePromise | |
@@ -502,9 +572,10 @@ primordials.SafePromiseAny = (promises, mapFn) => | |||
| 502 | 572 | ); | |
| 503 | 573 | ||
| 504 | 574 | /** | |
| 505 | - * @param {Promise<any>[]} promises | ||
| 506 | - * @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn] | ||
| 507 | - * @returns {Promise<any>} | ||
| 575 | + * @template T,U | ||
| 576 | + * @param {Array<T|PromiseLike<T>>} promises | ||
| 577 | + * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] | ||
| 578 | + * @returns {Promise<Awaited<U>>} | ||
| 508 | 579 | */ | |
| 509 | 580 | primordials.SafePromiseRace = (promises, mapFn) => | |
| 510 | 581 | // Wrapping on a new Promise is necessary to not expose the SafePromise | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,7 +11,7 @@ const { | |||
| 11 | 11 | ObjectGetPrototypeOf, | |
| 12 | 12 | ObjectSetPrototypeOf, | |
| 13 | 13 | ReflectApply, | |
| 14 | - SafePromiseAll, | ||
| 14 | + SafePromiseAllReturnVoid, | ||
| 15 | 15 | SafeWeakMap, | |
| 16 | 16 | Symbol, | |
| 17 | 17 | SymbolToStringTag, | |
@@ -330,7 +330,7 @@ class SourceTextModule extends Module { | |||
| 330 | 330 | ||
| 331 | 331 | try { | |
| 332 | 332 | if (promises !== undefined) { | |
| 333 | - await SafePromiseAll(promises); | ||
| 333 | + await SafePromiseAllReturnVoid(promises); | ||
| 334 | 334 | } | |
| 335 | 335 | } catch (e) { | |
| 336 | 336 | this.#error = e; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,11 +2,6 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { mustNotCall, mustCall } = require('../common'); | |
| 4 | 4 | ||
| 5 | - Object.defineProperties(Array.prototype, { | ||
| 6 | - // %Promise.all% and %Promise.allSettled% are depending on the value of | ||
| 7 | - // `%Array.prototype%.then`. | ||
| 8 | - then: {}, | ||
| 9 | - }); | ||
| 10 | 5 | Object.defineProperties(Object.prototype, { | |
| 11 | 6 | then: { | |
| 12 | 7 | set: mustNotCall('set %Object.prototype%.then'), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,10 +1,5 @@ | |||
| 1 | 1 | import { mustNotCall, mustCall } from '../common/index.mjs'; | |
| 2 | 2 | ||
| 3 | - Object.defineProperties(Array.prototype, { | ||
| 4 | - // %Promise.all% and %Promise.allSettled% are depending on the value of | ||
| 5 | - // `%Array.prototype%.then`. | ||
| 6 | - then: {}, | ||
| 7 | - }); | ||
| 8 | 3 | Object.defineProperties(Object.prototype, { | |
| 9 | 4 | then: { | |
| 10 | 5 | set: mustNotCall('set %Object.prototype%.then'), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,6 +63,8 @@ new RuleTester({ | |||
| 63 | 63 | 'new Proxy({}, someFactory())', | |
| 64 | 64 | 'new Proxy({}, { __proto__: null })', | |
| 65 | 65 | 'new Proxy({}, { __proto__: null, ...{} })', | |
| 66 | + 'async function name(){return await SafePromiseAll([])}', | ||
| 67 | + 'async function name(){const val = await SafePromiseAll([])}', | ||
| 66 | 68 | ], | |
| 67 | 69 | invalid: [ | |
| 68 | 70 | { | |
@@ -273,6 +275,14 @@ new RuleTester({ | |||
| 273 | 275 | code: 'PromiseAll([])', | |
| 274 | 276 | errors: [{ message: /\bSafePromiseAll\b/ }] | |
| 275 | 277 | }, | |
| 278 | + { | ||
| 279 | + code: 'async function fn(){await SafePromiseAll([])}', | ||
| 280 | + errors: [{ message: /\bSafePromiseAllReturnVoid\b/ }] | ||
| 281 | + }, | ||
| 282 | + { | ||
| 283 | + code: 'async function fn(){await SafePromiseAllSettled([])}', | ||
| 284 | + errors: [{ message: /\bSafePromiseAllSettledReturnVoid\b/ }] | ||
| 285 | + }, | ||
| 276 | 286 | { | |
| 277 | 287 | code: 'PromiseAllSettled([])', | |
| 278 | 288 | errors: [{ message: /\bSafePromiseAllSettled\b/ }] | |
| Back | FazBrowse Home | New Git URL |
0 commit comments