'use strict'; /* 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. // TODO(joyeecheung): we can restrict access to these globals in builtin // modules through the JS linter, for example: ban access such as `Object` // (which falls back to a lookup in the global proxy) in favor of // `primordials.Object` where `primordials` is a lexical variable passed // by the native module compiler. // `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`. // It is using `call.bind(bind, call)` to avoid using `Function.prototype.bind` // after it may have been mutated by users. const { bind, call } = Function.prototype; const uncurryThis = call.bind(bind, call); primordials.uncurryThis = uncurryThis; function copyProps(src, dest) { for (const key of Reflect.ownKeys(src)) { if (!Reflect.getOwnPropertyDescriptor(dest, key)) { Reflect.defineProperty( dest, key, Reflect.getOwnPropertyDescriptor(src, key)); } } } 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}`, { value: uncurryThis(get), enumerable }); if (set !== undefined) { Reflect.defineProperty(dest, `${prefix}Set${key}`, { 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 { Reflect.defineProperty(dest, `${prefix}${newKey}`, desc); } } } 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 { if (typeof desc.value === 'function') { desc.value = desc.value.bind(src); } Reflect.defineProperty(dest, `${prefix}${newKey}`, desc); } } } 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 { if (typeof desc.value === 'function') { desc.value = uncurryThis(desc.value); } Reflect.defineProperty(dest, `${prefix}${newKey}`, desc); } } } 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; }; function makeSafe(unsafe, safe) { if (Symbol.iterator in unsafe.prototype) { const dummy = new unsafe(); let next; // We can reuse the same `next` method. for (const key of Reflect.ownKeys(unsafe.prototype)) { 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 (desc.value.call(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, 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. primordials.SafeMap = makeSafe( Map, class SafeMap extends Map {} ); primordials.SafeWeakMap = makeSafe( WeakMap, class SafeWeakMap extends WeakMap {} ); primordials.SafeSet = makeSafe( Set, class SafeSet extends Set {} ); primordials.SafeWeakSet = makeSafe( WeakSet, class SafeWeakSet extends WeakSet {} ); // Create copies of the namespace objects [ 'JSON', 'Math', 'Reflect' ].forEach((name) => { copyPropsRenamed(global[name], primordials, name); }); // Create copies of intrinsic objects [ 'Array', 'ArrayBuffer', 'BigInt', 'BigInt64Array', 'BigUint64Array', 'Boolean', 'DataView', 'Date', 'Error', 'EvalError', 'Float32Array', 'Float64Array', 'Function', 'Int16Array', 'Int32Array', 'Int8Array', 'Map', 'Number', 'Object', 'RangeError', 'ReferenceError', 'RegExp', 'Set', 'String', 'Symbol', 'SyntaxError', 'TypeError', 'URIError', 'Uint16Array', 'Uint32Array', 'Uint8Array', 'Uint8ClampedArray', 'WeakMap', 'WeakSet', ].forEach((name) => { const original = global[name]; primordials[name] = original; copyPropsRenamed(original, primordials, name); copyPrototype(original.prototype, primordials, `${name}Prototype`); }); // 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) => { const original = global[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) }, ].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`); }); Object.setPrototypeOf(primordials, null); Object.freeze(primordials);