UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

746 lines (692 loc) 22.2 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/per_context/primordials.js /* eslint-disable node-core/prefer-primordials */ // This file subclasses and stores the JS builtins that come from the VM // so that Node.js's builtin modules do not need to later look these up from // the global proxy, which can be mutated by users. // Use of primordials have sometimes a dramatic impact on performance, please // benchmark all changes made in performance-sensitive areas of the codebase. // See: https://github.com/nodejs/node/pull/38248 const { defineProperty: ReflectDefineProperty, getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor, ownKeys: ReflectOwnKeys, } = Reflect; // `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`. // It is using `bind.bind(call)` to avoid using `Function.prototype.bind` // and `Function.prototype.call` after it may have been mutated by users. const { apply, bind, call } = Function.prototype; const uncurryThis = bind.bind(call); primordials.uncurryThis = uncurryThis; // `applyBind` is equivalent to `func => Function.prototype.apply.bind(func)`. // It is using `bind.bind(apply)` to avoid using `Function.prototype.bind` // and `Function.prototype.apply` after it may have been mutated by users. const applyBind = bind.bind(apply); primordials.applyBind = applyBind; // Methods that accept a variable number of arguments, and thus it's useful to // also create `${prefix}${key}Apply`, which uses `Function.prototype.apply`, // instead of `Function.prototype.call`, and thus doesn't require iterator // destructuring. const varargsMethods = [ // 'ArrayPrototypeConcat' is omitted, because it performs the spread // on its own for arrays and array-likes with a truthy // @@isConcatSpreadable symbol property. "ArrayOf", "ArrayPrototypePush", "ArrayPrototypeUnshift", // 'FunctionPrototypeCall' is omitted, since there's 'ReflectApply' // and 'FunctionPrototypeApply'. "MathHypot", "MathMax", "MathMin", "StringFromCharCode", "StringFromCodePoint", "StringPrototypeConcat", "TypedArrayOf", ]; function getNewKey(key) { return typeof key === "symbol" ? `Symbol${key.description[7].toUpperCase()}${key.description.slice(8)}` : `${key[0].toUpperCase()}${key.slice(1)}`; } function copyAccessor(dest, prefix, key, { enumerable, get, set }) { Reflect.defineProperty(dest, `${prefix}Get${key}`, { __proto__: null, value: uncurryThis(get), enumerable, }); if (set !== undefined) { Reflect.defineProperty(dest, `${prefix}Set${key}`, { __proto__: null, value: uncurryThis(set), enumerable, }); } } function copyPropsRenamed(src, dest, prefix) { for (const key of Reflect.ownKeys(src)) { const newKey = getNewKey(key); const desc = Reflect.getOwnPropertyDescriptor(src, key); if ("get" in desc) { copyAccessor(dest, prefix, newKey, desc); } else { const name = `${prefix}${newKey}`; Reflect.defineProperty(dest, name, { __proto__: null, ...desc }); if (varargsMethods.includes(name)) { Reflect.defineProperty(dest, `${name}Apply`, { __proto__: null, // `src` is bound as the `this` so that the static `this` points // to the object it was defined on, // e.g.: `ArrayOfApply` gets a `this` of `Array`: value: applyBind(desc.value, src), }); } } } } function copyPropsRenamedBound(src, dest, prefix) { for (const key of Reflect.ownKeys(src)) { const newKey = getNewKey(key); const desc = Reflect.getOwnPropertyDescriptor(src, key); if ("get" in desc) { copyAccessor(dest, prefix, newKey, desc); } else { const { value } = desc; if (typeof value === "function") { desc.value = value.bind(src); } const name = `${prefix}${newKey}`; Reflect.defineProperty(dest, name, { __proto__: null, ...desc }); if (varargsMethods.includes(name)) { Reflect.defineProperty(dest, `${name}Apply`, { __proto__: null, value: applyBind(value, src), }); } } } } function copyPrototype(src, dest, prefix) { for (const key of Reflect.ownKeys(src)) { const newKey = getNewKey(key); const desc = Reflect.getOwnPropertyDescriptor(src, key); if ("get" in desc) { copyAccessor(dest, prefix, newKey, desc); } else { const { value } = desc; if (typeof value === "function") { desc.value = uncurryThis(value); } const name = `${prefix}${newKey}`; Reflect.defineProperty(dest, name, { __proto__: null, ...desc }); if (varargsMethods.includes(name)) { Reflect.defineProperty(dest, `${name}Apply`, { __proto__: null, value: applyBind(value), }); } } } } // Create copies of configurable value properties of the global object ["Proxy", "globalThis"].forEach((name) => { // eslint-disable-next-line no-restricted-globals primordials[name] = globalThis[name]; }); // Create copies of URI handling functions [decodeURI, decodeURIComponent, encodeURI, encodeURIComponent].forEach((fn) => { primordials[fn.name] = fn; }); // Create copies of legacy functions [escape, eval, unescape].forEach((fn) => { primordials[fn.name] = fn; }); // Create copies of the namespace objects ["Atomics", "JSON", "Math", "Proxy", "Reflect"].forEach((name) => { // eslint-disable-next-line no-restricted-globals copyPropsRenamed(globalThis[name], primordials, name); }); // Create copies of intrinsic objects [ "AggregateError", "Array", "ArrayBuffer", "BigInt", "BigInt64Array", "BigUint64Array", "Boolean", "DataView", "Date", "Error", "EvalError", "FinalizationRegistry", "Float32Array", "Float64Array", "Function", "Int16Array", "Int32Array", "Int8Array", "Map", "Number", "Object", "RangeError", "ReferenceError", "RegExp", "Set", "String", "Symbol", "SyntaxError", "TypeError", "URIError", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", "WeakMap", "WeakRef", "WeakSet", ].forEach((name) => { // eslint-disable-next-line no-restricted-globals const original = globalThis[name]; primordials[name] = original; copyPropsRenamed(original, primordials, name); copyPrototype(original.prototype, primordials, `${name}Prototype`); }); // Define Symbol.dispose and Symbol.asyncDispose // Until these are defined by the environment. // TODO(MoLow): Remove this polyfill once Symbol.dispose and Symbol.asyncDispose are available in V8. primordials.SymbolDispose ??= primordials.SymbolFor("nodejs.dispose"); primordials.SymbolAsyncDispose ??= primordials.SymbolFor("nodejs.asyncDispose"); // Create copies of intrinsic objects that require a valid `this` to call // static methods. // Refs: https://www.ecma-international.org/ecma-262/#sec-promise.all ["Promise"].forEach((name) => { // eslint-disable-next-line no-restricted-globals const original = globalThis[name]; primordials[name] = original; copyPropsRenamedBound(original, primordials, name); copyPrototype(original.prototype, primordials, `${name}Prototype`); }); // Create copies of abstract intrinsic objects that are not directly exposed // on the global object. // Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object [ { name: "TypedArray", original: Reflect.getPrototypeOf(Uint8Array) }, { name: "ArrayIterator", original: { prototype: Reflect.getPrototypeOf(Array.prototype[Symbol.iterator]()), }, }, { name: "StringIterator", original: { prototype: Reflect.getPrototypeOf(String.prototype[Symbol.iterator]()), }, }, ].forEach(({ name, original }) => { primordials[name] = original; // The static %TypedArray% methods require a valid `this`, but can't be bound, // as they need a subclass constructor as the receiver: copyPrototype(original, primordials, name); copyPrototype(original.prototype, primordials, `${name}Prototype`); }); primordials.IteratorPrototype = Reflect.getPrototypeOf( primordials.ArrayIteratorPrototype, ); /* eslint-enable node-core/prefer-primordials */ /** * Creates a class that can be safely iterated over. * * Because these functions are used by `makeSafe`, which is exposed on the * `primordials` object, it's important to use const references to the * primordials that they use. * @template {Iterable} T * @template {*} TReturn * @template {*} TNext * @param {(self: T) => IterableIterator<T>} factory * @param {(...args: [] | [TNext]) => IteratorResult<T, TReturn>} next * @returns {Iterator<T, TReturn, TNext>} */ const createSafeIterator = (factory, next) => { class SafeIterator { constructor(iterable) { this._iterator = factory(iterable); } next() { return next(this._iterator); } [Symbol.iterator]() { return this; } } Object.setPrototypeOf(SafeIterator.prototype, null); Object.freeze(SafeIterator.prototype); Object.freeze(SafeIterator); return SafeIterator; }; primordials.SafeArrayIterator = createSafeIterator( primordials.ArrayPrototypeSymbolIterator, primordials.ArrayIteratorPrototypeNext, ); primordials.SafeStringIterator = createSafeIterator( primordials.StringPrototypeSymbolIterator, primordials.StringIteratorPrototypeNext, ); const copyProps = (src, dest) => { Array.prototype.forEach.call(Reflect.ownKeys(src), (key) => { if (!Reflect.getOwnPropertyDescriptor(dest, key)) { Reflect.defineProperty(dest, key, { __proto__: null, ...Reflect.getOwnPropertyDescriptor(src, key), }); } }); }; /** * @type {typeof primordials.makeSafe} */ const makeSafe = (unsafe, safe) => { if (Symbol.iterator in unsafe.prototype) { const dummy = new unsafe(); let next; // We can reuse the same `next` method. Array.prototype.forEach.call(Reflect.ownKeys(unsafe.prototype), (key) => { if (!Reflect.getOwnPropertyDescriptor(safe.prototype, key)) { const desc = Reflect.getOwnPropertyDescriptor(unsafe.prototype, key); if ( typeof desc.value === "function" && desc.value.length === 0 && Symbol.iterator in (Function.prototype.call.call(desc.value, dummy) ?? {}) ) { const createIterator = uncurryThis(desc.value); next ??= uncurryThis(createIterator(dummy).next); const SafeIterator = createSafeIterator(createIterator, next); desc.value = function () { return new SafeIterator(this); }; } Reflect.defineProperty(safe.prototype, key, { __proto__: null, ...desc, }); } }); } else { copyProps(unsafe.prototype, safe.prototype); } copyProps(unsafe, safe); Object.setPrototypeOf(safe.prototype, null); Object.freeze(safe.prototype); Object.freeze(safe); return safe; }; primordials.makeSafe = makeSafe; // Subclass the constructors because we need to use their prototype // methods later. // Defining the `constructor` is necessary here to avoid the default // constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`. primordials.SafeMap = makeSafe( Map, class SafeMap extends Map { constructor(i) { super(i); } // eslint-disable-line no-useless-constructor }, ); primordials.SafeWeakMap = makeSafe( WeakMap, class SafeWeakMap extends WeakMap { constructor(i) { super(i); } // eslint-disable-line no-useless-constructor }, ); primordials.SafeSet = makeSafe( Set, class SafeSet extends Set { constructor(i) { super(i); } // eslint-disable-line no-useless-constructor }, ); primordials.SafeWeakSet = makeSafe( WeakSet, class SafeWeakSet extends WeakSet { constructor(i) { super(i); } // eslint-disable-line no-useless-constructor }, ); primordials.SafeFinalizationRegistry = makeSafe( FinalizationRegistry, class SafeFinalizationRegistry extends FinalizationRegistry { // eslint-disable-next-line no-useless-constructor constructor(cleanupCallback) { super(cleanupCallback); } }, ); primordials.SafeWeakRef = makeSafe( WeakRef, class SafeWeakRef extends WeakRef { // eslint-disable-next-line no-useless-constructor constructor(target) { super(target); } }, ); const SafePromise = makeSafe( Promise, class SafePromise extends Promise { // eslint-disable-next-line no-useless-constructor constructor(executor) { super(executor); } }, ); /** * Attaches a callback that is invoked when the Promise is settled (fulfilled or * rejected). The resolved value cannot be modified from the callback. * Prefer using async functions when possible. * @param {Promise<any>} thisPromise * @param {() => void) | undefined | null} onFinally The callback to execute * when the Promise is settled (fulfilled or rejected). * @returns {Promise} A Promise for the completion of the callback. */ primordials.SafePromisePrototypeFinally = (thisPromise, onFinally) => // Wrapping on a new Promise is necessary to not expose the SafePromise // prototype to user-land. new Promise((a, b) => new Promise((a, b) => Promise.prototype.then.call(thisPromise, a, b)) .finally(onFinally) .then(a, b), ); primordials.AsyncIteratorPrototype = primordials.ReflectGetPrototypeOf( primordials.ReflectGetPrototypeOf(async function* () {}).prototype, ); const arrayToSafePromiseIterable = (promises, mapFn) => new primordials.SafeArrayIterator( Array.prototype.map.call( promises, (promise, i) => new Promise((a, b) => Promise.prototype.then.call( mapFn == null ? promise : mapFn(promise, i), a, b, ), ), ), ); /** * @template T,U * @param {Array<T | PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<Awaited<U>[]>} */ primordials.SafePromiseAll = (promises, mapFn) => // Wrapping on a new Promise is necessary to not expose the SafePromise // prototype to user-land. new Promise((a, b) => SafePromise.all(arrayToSafePromiseIterable(promises, mapFn)).then(a, b), ); /** * Should only be used for internal functions, this would produce similar * results as `Promise.all` but without prototype pollution, and the return * value is not a genuine Array but an array-like object. * @template T,U * @param {ArrayLike<T | PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<ArrayLike<Awaited<U>>>} */ primordials.SafePromiseAllReturnArrayLike = (promises, mapFn) => new Promise((resolve, reject) => { const { length } = promises; const returnVal = Array.constructor(length); Object.setPrototypeOf(returnVal, null); if (length === 0) resolve(returnVal); let pendingPromises = length; for (let i = 0; i < length; i++) { const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; Promise.prototype.then.call( Promise.resolve(promise), (result) => { returnVal[i] = result; if (--pendingPromises === 0) resolve(returnVal); }, reject, ); } }); /** * Should only be used when we only care about waiting for all the promises to * resolve, not what value they resolve to. * @template T,U * @param {ArrayLike<T | PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<void>} */ primordials.SafePromiseAllReturnVoid = (promises, mapFn) => new Promise((resolve, reject) => { let pendingPromises = promises.length; if (pendingPromises === 0) resolve(); const onFulfilled = () => { if (--pendingPromises === 0) { resolve(); } }; for (let i = 0; i < promises.length; i++) { const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; Promise.prototype.then.call( Promise.resolve(promise), onFulfilled, reject, ); } }); /** * @template T,U * @param {Array<T|PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<PromiseSettledResult<any>[]>} */ primordials.SafePromiseAllSettled = (promises, mapFn) => // Wrapping on a new Promise is necessary to not expose the SafePromise // prototype to user-land. new Promise((a, b) => SafePromise.allSettled(arrayToSafePromiseIterable(promises, mapFn)).then( a, b, ), ); /** * Should only be used when we only care about waiting for all the promises to * settle, not what value they resolve or reject to. * @template T,U * @param {ArrayLike<T|PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<void>} */ primordials.SafePromiseAllSettledReturnVoid = (promises, mapFn) => new Promise((resolve) => { let pendingPromises = promises.length; if (pendingPromises === 0) resolve(); const onSettle = () => { if (--pendingPromises === 0) resolve(); }; for (let i = 0; i < promises.length; i++) { const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; Promise.prototype.then.call(Promise.resolve(promise), onSettle, onSettle); } }); /** * @template T,U * @param {Array<T|PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<Awaited<U>>} */ primordials.SafePromiseAny = (promises, mapFn) => // Wrapping on a new Promise is necessary to not expose the SafePromise // prototype to user-land. new Promise((a, b) => SafePromise.any(arrayToSafePromiseIterable(promises, mapFn)).then(a, b), ); /** * @template T,U * @param {Array<T|PromiseLike<T>>} promises * @param {(v: T|PromiseLike<T>, k: number) => U|PromiseLike<U>} [mapFn] * @returns {Promise<Awaited<U>>} */ primordials.SafePromiseRace = (promises, mapFn) => // Wrapping on a new Promise is necessary to not expose the SafePromise // prototype to user-land. new Promise((a, b) => SafePromise.race(arrayToSafePromiseIterable(promises, mapFn)).then(a, b), ); const { exec: OriginalRegExpPrototypeExec, [Symbol.match]: OriginalRegExpPrototypeSymbolMatch, [Symbol.matchAll]: OriginalRegExpPrototypeSymbolMatchAll, [Symbol.replace]: OriginalRegExpPrototypeSymbolReplace, [Symbol.search]: OriginalRegExpPrototypeSymbolSearch, [Symbol.split]: OriginalRegExpPrototypeSymbolSplit, } = RegExpPrototype; class RegExpLikeForStringSplitting { #regex; constructor() { this.#regex = Reflect.construct(RegExp, arguments); } get lastIndex() { return ReflectGet(this.#regex, "lastIndex"); } set lastIndex(value) { ReflectSet(this.#regex, "lastIndex", value); } exec() { return ReflectApply(OriginalRegExpPrototypeExec, this.#regex, arguments); } } Object.setPrototypeOf(RegExpLikeForStringSplitting.prototype, null); /** * @param {RegExp} pattern * @returns {RegExp} */ primordials.hardenRegExp = function hardenRegExp(pattern) { Object.defineProperties(pattern, { [Symbol.match]: { __proto__: null, configurable: true, value: OriginalRegExpPrototypeSymbolMatch, }, [Symbol.matchAll]: { __proto__: null, configurable: true, value: OriginalRegExpPrototypeSymbolMatchAll, }, [Symbol.replace]: { __proto__: null, configurable: true, value: OriginalRegExpPrototypeSymbolReplace, }, [Symbol.search]: { __proto__: null, configurable: true, value: OriginalRegExpPrototypeSymbolSearch, }, [Symbol.split]: { __proto__: null, configurable: true, value: OriginalRegExpPrototypeSymbolSplit, }, constructor: { __proto__: null, configurable: true, value: { [Symbol.species]: RegExpLikeForStringSplitting, }, }, dotAll: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "dotAll").get( pattern, ), }, exec: { __proto__: null, configurable: true, value: OriginalRegExpPrototypeExec, }, global: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "global").get( pattern, ), }, hasIndices: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor( RegExp.prototype, "hasIndices", ).get(pattern), }, ignoreCase: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor( RegExp.prototype, "ignoreCase", ).get(pattern), }, multiline: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "multiline").get( pattern, ), }, source: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "source").get( pattern, ), }, sticky: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "sticky").get( pattern, ), }, unicode: { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "unicode").get( pattern, ), }, }); Object.defineProperty(pattern, "flags", { __proto__: null, configurable: true, value: Object.getOwnPropertyDescriptor(RegExp.prototype, "flags").get( pattern, ), }); return pattern; }; /** * @param {string} str * @param {RegExp} regexp * @returns {number} */ primordials.SafeStringPrototypeSearch = (str, regexp) => { regexp.lastIndex = 0; const match = RegExp.prototype.exec.call(regexp, str); return match ? match.index : -1; }; Object.setPrototypeOf(primordials, null); Object.freeze(primordials);