node/lib/internal/per_context/primordials.js
ExE Boss 1a0c5d57aa lib: simplify primordials.uncurryThis
PR-URL: https://github.com/nodejs/node/pull/36866
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Pooja D P <Pooja.D.P@ibm.com>
2021-01-12 14:47:37 +01:00

270 lines
7.7 KiB
JavaScript

'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 `bind.bind(call)` to avoid using `Function.prototype.bind`
// and `Function.prototype.call` after it may have been mutated by users.
const { bind, call } = Function.prototype;
const uncurryThis = 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.
// 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
}
);
// 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) },
{ 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.SafeArrayIterator = createSafeIterator(
primordials.ArrayPrototypeSymbolIterator,
primordials.ArrayIteratorPrototypeNext
);
primordials.SafeStringIterator = createSafeIterator(
primordials.StringPrototypeSymbolIterator,
primordials.StringIteratorPrototypeNext
);
Object.setPrototypeOf(primordials, null);
Object.freeze(primordials);