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