nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
746 lines (692 loc) • 22.2 kB
JavaScript
// 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);