• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3/* eslint-disable node-core/prefer-primordials */
4
5// This file subclasses and stores the JS builtins that come from the VM
6// so that Node.js's builtin modules do not need to later look these up from
7// the global proxy, which can be mutated by users.
8
9// Use of primordials have sometimes a dramatic impact on performance, please
10// benchmark all changes made in performance-sensitive areas of the codebase.
11// See: https://github.com/nodejs/node/pull/38248
12
13const {
14  defineProperty: ReflectDefineProperty,
15  getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
16  ownKeys: ReflectOwnKeys,
17} = Reflect;
18
19// `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`.
20// It is using `bind.bind(call)` to avoid using `Function.prototype.bind`
21// and `Function.prototype.call` after it may have been mutated by users.
22const { apply, bind, call } = Function.prototype;
23const uncurryThis = bind.bind(call);
24primordials.uncurryThis = uncurryThis;
25
26// `applyBind` is equivalent to `func => Function.prototype.apply.bind(func)`.
27// It is using `bind.bind(apply)` to avoid using `Function.prototype.bind`
28// and `Function.prototype.apply` after it may have been mutated by users.
29const applyBind = bind.bind(apply);
30primordials.applyBind = applyBind;
31
32// Methods that accept a variable number of arguments, and thus it's useful to
33// also create `${prefix}${key}Apply`, which uses `Function.prototype.apply`,
34// instead of `Function.prototype.call`, and thus doesn't require iterator
35// destructuring.
36const varargsMethods = [
37  // 'ArrayPrototypeConcat' is omitted, because it performs the spread
38  // on its own for arrays and array-likes with a truthy
39  // @@isConcatSpreadable symbol property.
40  'ArrayOf',
41  'ArrayPrototypePush',
42  'ArrayPrototypeUnshift',
43  // 'FunctionPrototypeCall' is omitted, since there's 'ReflectApply'
44  // and 'FunctionPrototypeApply'.
45  'MathHypot',
46  'MathMax',
47  'MathMin',
48  'StringPrototypeConcat',
49  'TypedArrayOf',
50];
51
52function getNewKey(key) {
53  return typeof key === 'symbol' ?
54    `Symbol${key.description[7].toUpperCase()}${key.description.slice(8)}` :
55    `${key[0].toUpperCase()}${key.slice(1)}`;
56}
57
58function copyAccessor(dest, prefix, key, { enumerable, get, set }) {
59  ReflectDefineProperty(dest, `${prefix}Get${key}`, {
60    value: uncurryThis(get),
61    enumerable
62  });
63  if (set !== undefined) {
64    ReflectDefineProperty(dest, `${prefix}Set${key}`, {
65      value: uncurryThis(set),
66      enumerable
67    });
68  }
69}
70
71function copyPropsRenamed(src, dest, prefix) {
72  for (const key of ReflectOwnKeys(src)) {
73    const newKey = getNewKey(key);
74    const desc = ReflectGetOwnPropertyDescriptor(src, key);
75    if ('get' in desc) {
76      copyAccessor(dest, prefix, newKey, desc);
77    } else {
78      const name = `${prefix}${newKey}`;
79      ReflectDefineProperty(dest, name, desc);
80      if (varargsMethods.includes(name)) {
81        ReflectDefineProperty(dest, `${name}Apply`, {
82          // `src` is bound as the `this` so that the static `this` points
83          // to the object it was defined on,
84          // e.g.: `ArrayOfApply` gets a `this` of `Array`:
85          value: applyBind(desc.value, src),
86        });
87      }
88    }
89  }
90}
91
92function copyPropsRenamedBound(src, dest, prefix) {
93  for (const key of ReflectOwnKeys(src)) {
94    const newKey = getNewKey(key);
95    const desc = ReflectGetOwnPropertyDescriptor(src, key);
96    if ('get' in desc) {
97      copyAccessor(dest, prefix, newKey, desc);
98    } else {
99      const { value } = desc;
100      if (typeof value === 'function') {
101        desc.value = value.bind(src);
102      }
103
104      const name = `${prefix}${newKey}`;
105      ReflectDefineProperty(dest, name, desc);
106      if (varargsMethods.includes(name)) {
107        ReflectDefineProperty(dest, `${name}Apply`, {
108          value: applyBind(value, src),
109        });
110      }
111    }
112  }
113}
114
115function copyPrototype(src, dest, prefix) {
116  for (const key of ReflectOwnKeys(src)) {
117    const newKey = getNewKey(key);
118    const desc = ReflectGetOwnPropertyDescriptor(src, key);
119    if ('get' in desc) {
120      copyAccessor(dest, prefix, newKey, desc);
121    } else {
122      const { value } = desc;
123      if (typeof value === 'function') {
124        desc.value = uncurryThis(value);
125      }
126
127      const name = `${prefix}${newKey}`;
128      ReflectDefineProperty(dest, name, desc);
129      if (varargsMethods.includes(name)) {
130        ReflectDefineProperty(dest, `${name}Apply`, {
131          value: applyBind(value),
132        });
133      }
134    }
135  }
136}
137
138// Create copies of configurable value properties of the global object
139[
140  'Proxy',
141  'globalThis',
142].forEach((name) => {
143  // eslint-disable-next-line no-restricted-globals
144  primordials[name] = globalThis[name];
145});
146
147// Create copies of URI handling functions
148[
149  decodeURI,
150  decodeURIComponent,
151  encodeURI,
152  encodeURIComponent,
153].forEach((fn) => {
154  primordials[fn.name] = fn;
155});
156
157// Create copies of the namespace objects
158[
159  'JSON',
160  'Math',
161  'Proxy',
162  'Reflect',
163].forEach((name) => {
164  // eslint-disable-next-line no-restricted-globals
165  copyPropsRenamed(global[name], primordials, name);
166});
167
168// Create copies of intrinsic objects
169[
170  'Array',
171  'ArrayBuffer',
172  'BigInt',
173  'BigInt64Array',
174  'BigUint64Array',
175  'Boolean',
176  'DataView',
177  'Date',
178  'Error',
179  'EvalError',
180  'Float32Array',
181  'Float64Array',
182  'Function',
183  'Int16Array',
184  'Int32Array',
185  'Int8Array',
186  'Map',
187  'Number',
188  'Object',
189  'RangeError',
190  'ReferenceError',
191  'RegExp',
192  'Set',
193  'String',
194  'Symbol',
195  'SyntaxError',
196  'TypeError',
197  'URIError',
198  'Uint16Array',
199  'Uint32Array',
200  'Uint8Array',
201  'Uint8ClampedArray',
202  'WeakMap',
203  'WeakSet',
204].forEach((name) => {
205  // eslint-disable-next-line no-restricted-globals
206  const original = global[name];
207  primordials[name] = original;
208  copyPropsRenamed(original, primordials, name);
209  copyPrototype(original.prototype, primordials, `${name}Prototype`);
210});
211
212// Create copies of intrinsic objects that require a valid `this` to call
213// static methods.
214// Refs: https://www.ecma-international.org/ecma-262/#sec-promise.all
215[
216  'Promise',
217].forEach((name) => {
218  // eslint-disable-next-line no-restricted-globals
219  const original = global[name];
220  primordials[name] = original;
221  copyPropsRenamedBound(original, primordials, name);
222  copyPrototype(original.prototype, primordials, `${name}Prototype`);
223});
224
225// Create copies of abstract intrinsic objects that are not directly exposed
226// on the global object.
227// Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object
228[
229  { name: 'TypedArray', original: Reflect.getPrototypeOf(Uint8Array) },
230  { name: 'ArrayIterator', original: {
231    prototype: Reflect.getPrototypeOf(Array.prototype[Symbol.iterator]()),
232  } },
233  { name: 'StringIterator', original: {
234    prototype: Reflect.getPrototypeOf(String.prototype[Symbol.iterator]()),
235  } },
236].forEach(({ name, original }) => {
237  primordials[name] = original;
238  // The static %TypedArray% methods require a valid `this`, but can't be bound,
239  // as they need a subclass constructor as the receiver:
240  copyPrototype(original, primordials, name);
241  copyPrototype(original.prototype, primordials, `${name}Prototype`);
242});
243
244/* eslint-enable node-core/prefer-primordials */
245
246const {
247  ArrayPrototypeForEach,
248  FunctionPrototypeCall,
249  Map,
250  ObjectFreeze,
251  ObjectSetPrototypeOf,
252  Set,
253  SymbolIterator,
254  WeakMap,
255  WeakSet,
256} = primordials;
257
258// Because these functions are used by `makeSafe`, which is exposed
259// on the `primordials` object, it's important to use const references
260// to the primordials that they use:
261const createSafeIterator = (factory, next) => {
262  class SafeIterator {
263    constructor(iterable) {
264      this._iterator = factory(iterable);
265    }
266    next() {
267      return next(this._iterator);
268    }
269    [SymbolIterator]() {
270      return this;
271    }
272  }
273  ObjectSetPrototypeOf(SafeIterator.prototype, null);
274  ObjectFreeze(SafeIterator.prototype);
275  ObjectFreeze(SafeIterator);
276  return SafeIterator;
277};
278
279primordials.SafeArrayIterator = createSafeIterator(
280  primordials.ArrayPrototypeSymbolIterator,
281  primordials.ArrayIteratorPrototypeNext
282);
283primordials.SafeStringIterator = createSafeIterator(
284  primordials.StringPrototypeSymbolIterator,
285  primordials.StringIteratorPrototypeNext
286);
287
288const copyProps = (src, dest) => {
289  ArrayPrototypeForEach(ReflectOwnKeys(src), (key) => {
290    if (!ReflectGetOwnPropertyDescriptor(dest, key)) {
291      ReflectDefineProperty(
292        dest,
293        key,
294        ReflectGetOwnPropertyDescriptor(src, key));
295    }
296  });
297};
298
299const makeSafe = (unsafe, safe) => {
300  if (SymbolIterator in unsafe.prototype) {
301    const dummy = new unsafe();
302    let next; // We can reuse the same `next` method.
303
304    ArrayPrototypeForEach(ReflectOwnKeys(unsafe.prototype), (key) => {
305      if (!ReflectGetOwnPropertyDescriptor(safe.prototype, key)) {
306        const desc = ReflectGetOwnPropertyDescriptor(unsafe.prototype, key);
307        if (
308          typeof desc.value === 'function' &&
309          desc.value.length === 0 &&
310          SymbolIterator in (FunctionPrototypeCall(desc.value, dummy) ?? {})
311        ) {
312          const createIterator = uncurryThis(desc.value);
313          next = next ?? uncurryThis(createIterator(dummy).next);
314          const SafeIterator = createSafeIterator(createIterator, next);
315          desc.value = function() {
316            return new SafeIterator(this);
317          };
318        }
319        ReflectDefineProperty(safe.prototype, key, desc);
320      }
321    });
322  } else {
323    copyProps(unsafe.prototype, safe.prototype);
324  }
325  copyProps(unsafe, safe);
326
327  ObjectSetPrototypeOf(safe.prototype, null);
328  ObjectFreeze(safe.prototype);
329  ObjectFreeze(safe);
330  return safe;
331};
332primordials.makeSafe = makeSafe;
333
334// Subclass the constructors because we need to use their prototype
335// methods later.
336// Defining the `constructor` is necessary here to avoid the default
337// constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`.
338primordials.SafeMap = makeSafe(
339  Map,
340  class SafeMap extends Map {
341    constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
342  }
343);
344primordials.SafeWeakMap = makeSafe(
345  WeakMap,
346  class SafeWeakMap extends WeakMap {
347    constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
348  }
349);
350primordials.SafeSet = makeSafe(
351  Set,
352  class SafeSet extends Set {
353    constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
354  }
355);
356primordials.SafeWeakSet = makeSafe(
357  WeakSet,
358  class SafeWeakSet extends WeakSet {
359    constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
360  }
361);
362
363ObjectSetPrototypeOf(primordials, null);
364ObjectFreeze(primordials);
365