1# Usage of primordials in core 2 3The file `lib/internal/per_context/primordials.js` subclasses and stores the JS 4built-ins that come from the VM so that Node.js built-in modules do not need to 5later look these up from the global proxy, which can be mutated by users. 6 7Usage of primordials should be preferred for any new code, but replacing current 8code with primordials should be 9[done with care](#primordials-with-known-performance-issues). It is highly 10recommended to ping the relevant team when reviewing a pull request that touches 11one of the subsystems they "own". 12 13## Accessing primordials 14 15The primordials are meant for internal use only, and are only accessible for 16internal core modules. User code cannot use or rely on primordials. It is 17usually fine to rely on ECMAScript built-ins and assume that it will behave as 18specified. 19 20If you would like to access the `primordials` object to help you with Node.js 21core development or for tinkering, you can expose it on the global scope using 22this combination of CLI flags: 23 24```bash 25node --expose-internals -r internal/test/binding 26``` 27 28## Contents of primordials 29 30### Properties of the global object 31 32Objects and functions on the global object can be deleted or replaced. Using 33them from primordials makes the code more reliable: 34 35```js 36globalThis.Array === primordials.Array; // true 37 38globalThis.Array = function() { 39 return [1, 2, 3]; 40}; 41globalThis.Array === primordials.Array; // false 42 43primordials.Array(0); // [] 44globalThis.Array(0); // [1,2,3] 45``` 46 47### Prototype methods 48 49ECMAScript provides a group of methods available on built-in objects that are 50used to interact with JavaScript objects. 51 52```js 53const array = [1, 2, 3]; 54array.push(4); // Here `push` refers to %Array.prototype.push%. 55console.log(JSON.stringify(array)); // [1,2,3,4] 56 57// %Array.prototype%.push is modified in userland. 58Array.prototype.push = function push(val) { 59 return this.unshift(val); 60}; 61 62array.push(5); // Now `push` refers to the modified method. 63console.log(JSON.stringify(array)); // [5,1,2,3,4] 64``` 65 66Primordials wrap the original prototype functions with new functions that take 67the `this` value as the first argument: 68 69```js 70const { 71 ArrayPrototypePush, 72} = primordials; 73 74const array = [1, 2, 3]; 75ArrayPrototypePush(array, 4); 76console.log(JSON.stringify(array)); // [1,2,3,4] 77 78Array.prototype.push = function push(val) { 79 return this.unshift(val); 80}; 81 82ArrayPrototypePush(array, 5); 83console.log(JSON.stringify(array)); // [1,2,3,4,5] 84``` 85 86### Safe classes 87 88Safe classes are classes that provide the same API as their equivalent class, 89but whose implementation aims to avoid any reliance on user-mutable code. 90Safe classes should not be exposed to user-land; use unsafe equivalent when 91dealing with objects that are accessible from user-land. 92 93### Variadic functions 94 95There are some built-in functions that accept a variable number of arguments 96(e.g.: `Math.max`, `%Array.prototype.push%`). It is sometimes useful to provide 97the list of arguments as an array. You can use primordial function with the 98suffix `Apply` (e.g.: `MathMaxApply`, `ArrayPrototypePushApply`) to do that. 99 100## Primordials with known performance issues 101 102One of the reasons why the current Node.js API is not completely tamper-proof is 103performance: sometimes the use of primordials can cause performance regressions 104with V8, which when in a hot code path, could significantly decrease the 105performance of code in Node.js. 106 107* Methods that mutate the internal state of arrays: 108 * `ArrayPrototypePush` 109 * `ArrayPrototypePop` 110 * `ArrayPrototypeShift` 111 * `ArrayPrototypeUnshift` 112* Methods of the function prototype: 113 * `FunctionPrototypeBind` 114 * `FunctionPrototypeCall`: creates performance issues when used to invoke 115 super constructors. 116 * `FunctionPrototype`: use `() => {}` instead when referencing a no-op 117 function. 118* `SafeArrayIterator` 119* `SafeStringIterator` 120* `SafePromiseAll` 121* `SafePromiseAllSettled` 122* `SafePromiseAny` 123* `SafePromiseRace` 124* `SafePromisePrototypeFinally`: use `try {} finally {}` block instead. 125 126In general, when sending or reviewing a PR that makes changes in a hot code 127path, use extra caution and run extensive benchmarks. 128 129## Implicit use of user-mutable methods 130 131### Unsafe array iteration 132 133There are many usual practices in JavaScript that rely on iteration. It's useful 134to be aware of them when dealing with arrays (or `TypedArray`s) in core as array 135iteration typically calls several user-mutable methods. This sections lists the 136most common patterns in which ECMAScript code relies non-explicitly on array 137iteration and how to avoid it. 138 139<details> 140 141<summary>Avoid for-of loops on arrays</summary> 142 143```js 144for (const item of array) { 145 console.log(item); 146} 147``` 148 149This code is internally expanded into something that looks like: 150 151```js 152{ 153 // 1. Lookup @@iterator property on `array` (user-mutable if user-provided). 154 // 2. Lookup @@iterator property on %Array.prototype% (user-mutable). 155 // 3. Call that function. 156 const iterator = array[Symbol.iterator](); 157 // 1. Lookup `next` property on `iterator` (doesn't exist). 158 // 2. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). 159 // 3. Call that function. 160 let { done, value: item } = iterator.next(); 161 while (!done) { 162 console.log(item); 163 // Repeat. 164 ({ done, value: item } = iterator.next()); 165 } 166} 167``` 168 169Instead of utilizing iterators, you can use the more traditional but still very 170performant `for` loop: 171 172```js 173for (let i = 0; i < array.length; i++) { 174 console.log(array[i]); 175} 176``` 177 178The following code snippet illustrates how user-land code could impact the 179behavior of internal modules: 180 181```js 182// User-land 183Array.prototype[Symbol.iterator] = () => ({ 184 next: () => ({ done: true }), 185}); 186 187// Core 188let forOfLoopBlockExecuted = false; 189let forLoopBlockExecuted = false; 190const array = [1, 2, 3]; 191for (const item of array) { 192 forOfLoopBlockExecuted = true; 193} 194for (let i = 0; i < array.length; i++) { 195 forLoopBlockExecuted = true; 196} 197console.log(forOfLoopBlockExecuted); // false 198console.log(forLoopBlockExecuted); // true 199``` 200 201This only applies if you are working with a genuine array (or array-like 202object). If you are instead expecting an iterator, a for-of loop may be a better 203choice. 204 205</details> 206 207<details> 208 209<summary>Avoid array destructuring assignment on arrays</summary> 210 211```js 212const [first, second] = array; 213``` 214 215This is roughly equivalent to: 216 217```js 218// 1. Lookup @@iterator property on `array` (user-mutable if user-provided). 219// 2. Lookup @@iterator property on %Array.prototype% (user-mutable). 220// 3. Call that function. 221const iterator = array[Symbol.iterator](); 222// 1. Lookup `next` property on `iterator` (doesn't exist). 223// 2. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). 224// 3. Call that function. 225const first = iterator.next().value; 226// Repeat. 227const second = iterator.next().value; 228``` 229 230Instead you can use object destructuring: 231 232```js 233const { 0: first, 1: second } = array; 234``` 235 236or 237 238```js 239const first = array[0]; 240const second = array[1]; 241``` 242 243This only applies if you are working with a genuine array (or array-like 244object). If you are instead expecting an iterator, array destructuring is the 245best choice. 246 247</details> 248 249<details> 250 251<summary>Avoid spread operator on arrays</summary> 252 253```js 254// 1. Lookup @@iterator property on `array` (user-mutable if user-provided). 255// 2. Lookup @@iterator property on %Array.prototype% (user-mutable). 256// 3. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). 257const arrayCopy = [...array]; 258func(...array); 259``` 260 261Instead you can use other ECMAScript features to achieve the same result: 262 263```js 264const arrayCopy = ArrayPrototypeSlice(array); 265ReflectApply(func, null, array); 266``` 267 268</details> 269 270<details> 271 272<summary><code>%Array.prototype.concat%</code> looks up 273 <code>@@isConcatSpreadable</code> property of the passed 274 arguments and the <code>this</code> value.</summary> 275 276```js 277{ 278 // Unsafe code example: 279 // 1. Lookup @@isConcatSpreadable property on `array` (user-mutable if 280 // user-provided). 281 // 2. Lookup @@isConcatSpreadable property on `%Array.prototype% 282 // (user-mutable). 283 // 2. Lookup @@isConcatSpreadable property on `%Object.prototype% 284 // (user-mutable). 285 const array = []; 286 ArrayPrototypeConcat(array); 287} 288``` 289 290```js 291// User-land 292Object.defineProperty(Object.prototype, Symbol.isConcatSpreadable, { 293 get() { 294 this.push(5); 295 return true; 296 }, 297}); 298 299// Core 300{ 301 // Using ArrayPrototypeConcat does not produce the expected result: 302 const a = [1, 2]; 303 const b = [3, 4]; 304 console.log(ArrayPrototypeConcat(a, b)); // [1, 2, 5, 3, 4, 5] 305} 306{ 307 // Concatenating two arrays can be achieved safely, e.g.: 308 const a = [1, 2]; 309 const b = [3, 4]; 310 // Using %Array.prototype.push% and `SafeArrayIterator` to get the expected 311 // outcome: 312 const concatArray = []; 313 ArrayPrototypePush(concatArray, ...new SafeArrayIterator(a), 314 ...new SafeArrayIterator(b)); 315 console.log(concatArray); // [1, 2, 3, 4] 316 317 // Or using `ArrayPrototypePushApply` if it's OK to mutate the first array: 318 ArrayPrototypePushApply(a, b); 319 console.log(a); // [1, 2, 3, 4] 320} 321``` 322 323</details> 324 325<details> 326 327<summary><code>%Object.fromEntries%</code> iterate over an array</summary> 328 329```js 330{ 331 // Unsafe code example: 332 // 1. Lookup @@iterator property on `array` (user-mutable if user-provided). 333 // 2. Lookup @@iterator property on %Array.prototype% (user-mutable). 334 // 3. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). 335 const obj = ObjectFromEntries(array); 336} 337 338{ 339 // Safe example using `SafeArrayIterator`: 340 const obj = ObjectFromEntries(new SafeArrayIterator(array)); 341} 342 343{ 344 // Safe example without using `SafeArrayIterator`: 345 const obj = {}; 346 for (let i = 0; i < array.length; i++) { 347 obj[array[i][0]] = array[i][1]; 348 } 349 // In a hot code path, this would be the preferred method. 350} 351``` 352 353</details> 354 355<details> 356 357<summary><code>%Promise.all%</code>, 358 <code>%Promise.allSettled%</code>, 359 <code>%Promise.any%</code>, and 360 <code>%Promise.race%</code> iterate over an array</summary> 361 362```js 363// 1. Lookup @@iterator property on `array` (user-mutable if user-provided). 364// 2. Lookup @@iterator property on %Array.prototype% (user-mutable). 365// 3. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). 366// 4. Lookup `then` property on %Array.Prototype% (user-mutable). 367// 5. Lookup `then` property on %Object.Prototype% (user-mutable). 368PromiseAll([]); // unsafe 369 370// 1. Lookup `then` property on %Array.Prototype% (user-mutable). 371// 2. Lookup `then` property on %Object.Prototype% (user-mutable). 372PromiseAll(new SafeArrayIterator([])); // still unsafe 373SafePromiseAll([]); // still unsafe 374 375SafePromiseAllReturnVoid([]); // safe 376SafePromiseAllReturnArrayLike([]); // safe 377 378const array = [promise]; 379const set = new SafeSet().add(promise); 380// When running one of these functions on a non-empty iterable, it will also: 381// 1. Lookup `then` property on `promise` (user-mutable if user-provided). 382// 2. Lookup `then` property on `%Promise.prototype%` (user-mutable). 383// 3. Lookup `then` property on %Array.Prototype% (user-mutable). 384// 4. Lookup `then` property on %Object.Prototype% (user-mutable). 385PromiseAll(new SafeArrayIterator(array)); // unsafe 386PromiseAll(set); // unsafe 387 388SafePromiseAllReturnVoid(array); // safe 389SafePromiseAllReturnArrayLike(array); // safe 390 391// Some key differences between `SafePromise[...]` and `Promise[...]` methods: 392 393// 1. SafePromiseAll, SafePromiseAllSettled, SafePromiseAny, SafePromiseRace, 394// SafePromiseAllReturnArrayLike, SafePromiseAllReturnVoid, and 395// SafePromiseAllSettledReturnVoid support passing a mapperFunction as second 396// argument. 397SafePromiseAll(ArrayPrototypeMap(array, someFunction)); 398SafePromiseAll(array, someFunction); // Same as the above, but more efficient. 399 400// 2. SafePromiseAll, SafePromiseAllSettled, SafePromiseAny, SafePromiseRace, 401// SafePromiseAllReturnArrayLike, SafePromiseAllReturnVoid, and 402// SafePromiseAllSettledReturnVoid only support arrays and array-like 403// objects, not iterables. Use ArrayFrom to convert an iterable to an array. 404SafePromiseAllReturnVoid(set); // ignores set content. 405SafePromiseAllReturnVoid(ArrayFrom(set)); // works 406 407// 3. SafePromiseAllReturnArrayLike is safer than SafePromiseAll, however you 408// should not use them when its return value is passed to the user as it can 409// be surprising for them not to receive a genuine array. 410SafePromiseAllReturnArrayLike(array).then((val) => val instanceof Array); // false 411SafePromiseAll(array).then((val) => val instanceof Array); // true 412``` 413 414</details> 415 416<details> 417 418<summary><code>%Map%</code>, <code>%Set%</code>, <code>%WeakMap%</code>, and 419 <code>%WeakSet%</code> constructors iterate over an array</summary> 420 421```js 422// User-land 423Array.prototype[Symbol.iterator] = () => ({ 424 next: () => ({ done: true }), 425}); 426 427// Core 428 429// 1. Lookup @@iterator property on %Array.prototype% (user-mutable). 430// 2. Lookup `next` property on %ArrayIteratorPrototype% (user-mutable). 431const set = new SafeSet([1, 2, 3]); 432 433console.log(set.size); // 0 434``` 435 436```js 437// User-land 438Array.prototype[Symbol.iterator] = () => ({ 439 next: () => ({ done: true }), 440}); 441 442// Core 443const set = new SafeSet(); 444set.add(1).add(2).add(3); 445console.log(set.size); // 3 446``` 447 448</details> 449 450### Promise objects 451 452<details> 453 454<summary><code>%Promise.prototype.finally%</code> looks up <code>then</code> 455 property of the Promise instance</summary> 456 457```js 458// User-land 459Promise.prototype.then = function then(a, b) { 460 return Promise.resolve(); 461}; 462 463// Core 464let finallyBlockExecuted = false; 465PromisePrototypeFinally(somePromiseThatEventuallySettles, 466 () => { finallyBlockExecuted = true; }); 467process.on('exit', () => console.log(finallyBlockExecuted)); // false 468``` 469 470```js 471// User-land 472Promise.prototype.then = function then(a, b) { 473 return Promise.resolve(); 474}; 475 476// Core 477let finallyBlockExecuted = false; 478(async () => { 479 try { 480 return await somePromiseThatEventuallySettles; 481 } finally { 482 finallyBlockExecuted = true; 483 } 484})(); 485process.on('exit', () => console.log(finallyBlockExecuted)); // true 486``` 487 488</details> 489 490<details> 491 492<summary><code>%Promise.all%</code>, 493 <code>%Promise.allSettled%</code>, 494 <code>%Promise.any%</code>, and 495 <code>%Promise.race%</code> look up <code>then</code> 496 property of the Promise instances</summary> 497 498You can use safe alternatives from primordials that differ slightly from the 499original methods: 500 501* It expects an array (or array-like object) instead of an iterable. 502* It wraps each promise in `SafePromise` objects and wraps the result in a new 503 `Promise` instance – which may come with a performance penalty. 504* It accepts a `mapperFunction` as second argument. 505* Because it doesn't look up `then` property, it may not be the right tool to 506 handle user-provided promises (which may be instances of a subclass of 507 `Promise`). 508 509```js 510// User-land 511Promise.prototype.then = function then(a, b) { 512 return Promise.resolve(); 513}; 514 515// Core 516let thenBlockExecuted = false; 517PromisePrototypeThen( 518 PromiseAll(new SafeArrayIterator([PromiseResolve()])), 519 () => { thenBlockExecuted = true; }, 520); 521process.on('exit', () => console.log(thenBlockExecuted)); // false 522``` 523 524```js 525// User-land 526Promise.prototype.then = function then(a, b) { 527 return Promise.resolve(); 528}; 529 530// Core 531let thenBlockExecuted = false; 532PromisePrototypeThen( 533 SafePromiseAll([PromiseResolve()]), 534 () => { thenBlockExecuted = true; }, 535); 536process.on('exit', () => console.log(thenBlockExecuted)); // true 537``` 538 539A common pattern is to map on the array of `Promise`s to apply some 540transformations, in that case it can be more efficient to pass a second argument 541rather than invoking `%Array.prototype.map%`. 542 543```js 544SafePromiseAll(ArrayPrototypeMap(array, someFunction)); 545SafePromiseAll(array, someFunction); // Same as the above, but more efficient. 546``` 547 548</details> 549 550### (Async) Generator functions 551 552Generators and async generators returned by generator functions and async 553generator functions are relying on user-mutable methods; their use in core 554should be avoided. 555 556<details> 557 558<summary><code>%GeneratorFunction.prototype.prototype%.next</code> is 559 user-mutable</summary> 560 561```js 562// User-land 563Object.getPrototypeOf(function* () {}).prototype.next = function next() { 564 return { done: true }; 565}; 566 567// Core 568function* someGenerator() { 569 yield 1; 570 yield 2; 571 yield 3; 572} 573let loopCodeExecuted = false; 574for (const nb of someGenerator()) { 575 loopCodeExecuted = true; 576} 577console.log(loopCodeExecuted); // false 578``` 579 580</details> 581 582<details> 583 584<summary><code>%AsyncGeneratorFunction.prototype.prototype%.next</code> is 585 user-mutable</summary> 586 587```js 588// User-land 589Object.getPrototypeOf(async function* () {}).prototype.next = function next() { 590 return new Promise(() => {}); 591}; 592 593// Core 594async function* someGenerator() { 595 yield 1; 596 yield 2; 597 yield 3; 598} 599let finallyBlockExecuted = false; 600async () => { 601 try { 602 for await (const nb of someGenerator()) { 603 // some code; 604 } 605 } finally { 606 finallyBlockExecuted = true; 607 } 608}; 609process.on('exit', () => console.log(finallyBlockExecuted)); // false 610``` 611 612</details> 613 614### Text processing 615 616#### Unsafe string methods 617 618| The string method | looks up the property | 619| ----------------------------- | --------------------- | 620| `String.prototype.match` | `Symbol.match` | 621| `String.prototype.matchAll` | `Symbol.matchAll` | 622| `String.prototype.replace` | `Symbol.replace` | 623| `String.prototype.replaceAll` | `Symbol.replace` | 624| `String.prototype.search` | `Symbol.search` | 625| `String.prototype.split` | `Symbol.split` | 626 627```js 628// User-land 629RegExp.prototype[Symbol.replace] = () => 'foo'; 630String.prototype[Symbol.replace] = () => 'baz'; 631 632// Core 633console.log(StringPrototypeReplace('ber', /e/, 'a')); // 'foo' 634console.log(StringPrototypeReplace('ber', 'e', 'a')); // 'baz' 635console.log(RegExpPrototypeSymbolReplace(/e/, 'ber', 'a')); // 'bar' 636``` 637 638#### Unsafe string iteration 639 640As with arrays, iterating over strings calls several user-mutable methods. Avoid 641iterating over strings when possible, or use `SafeStringIterator`. 642 643#### Unsafe `RegExp` methods 644 645Functions that lookup the `exec` property on the prototype chain: 646 647* `RegExp.prototype[Symbol.match]` 648* `RegExp.prototype[Symbol.matchAll]` 649* `RegExp.prototype[Symbol.replace]` 650* `RegExp.prototype[Symbol.search]` 651* `RegExp.prototype[Symbol.split]` 652* `RegExp.prototype.test` 653 654```js 655// User-land 656RegExp.prototype.exec = () => null; 657 658// Core 659console.log(RegExpPrototypeTest(/o/, 'foo')); // false 660console.log(RegExpPrototypeExec(/o/, 'foo') !== null); // true 661 662console.log(RegExpPrototypeSymbolSearch(/o/, 'foo')); // -1 663console.log(SafeStringPrototypeSearch('foo', /o/)); // 1 664``` 665 666#### Don't trust `RegExp` flags 667 668RegExp flags are not own properties of the regex instances, which means flags 669can be reset from user-land. 670 671<details> 672 673<summary>List of <code>RegExp</code> methods that look up properties from 674 mutable getters</summary> 675 676| `RegExp` method | looks up the following flag-related properties | 677| ------------------------------ | ------------------------------------------------------------------ | 678| `get RegExp.prototype.flags` | `global`, `ignoreCase`, `multiline`, `dotAll`, `unicode`, `sticky` | 679| `RegExp.prototype[@@match]` | `global`, `unicode` | 680| `RegExp.prototype[@@matchAll]` | `flags` | 681| `RegExp.prototype[@@replace]` | `global`, `unicode` | 682| `RegExp.prototype[@@split]` | `flags` | 683| `RegExp.prototype.toString` | `flags` | 684 685</details> 686 687```js 688// User-land 689Object.defineProperty(RegExp.prototype, 'global', { value: false }); 690 691// Core 692console.log(RegExpPrototypeSymbolReplace(/o/g, 'foo', 'a')); // 'fao' 693console.log(RegExpPrototypeSymbolReplace(hardenRegExp(/o/g), 'foo', 'a')); // 'faa' 694``` 695 696### Defining object own properties 697 698When defining property descriptor (to add or update an own property to a 699JavaScript object), be sure to always use a null-prototype object to avoid 700prototype pollution. 701 702```js 703// User-land 704Object.prototype.get = function get() {}; 705 706// Core 707try { 708 ObjectDefineProperty({}, 'someProperty', { value: 0 }); 709} catch (err) { 710 console.log(err); // TypeError: Invalid property descriptor. 711} 712``` 713 714```js 715// User-land 716Object.prototype.get = function get() {}; 717 718// Core 719ObjectDefineProperty({}, 'someProperty', { __proto__: null, value: 0 }); 720console.log('no errors'); // no errors. 721``` 722 723Same applies when trying to modify an existing property, e.g. trying to make a 724read-only property enumerable: 725 726```js 727// User-land 728Object.prototype.value = 'Unrelated user-provided data'; 729 730// Core 731class SomeClass { 732 get readOnlyProperty() { return 'genuine data'; } 733} 734ObjectDefineProperty(SomeClass.prototype, 'readOnlyProperty', { enumerable: true }); 735console.log(new SomeClass().readOnlyProperty); // Unrelated user-provided data 736``` 737 738```js 739// User-land 740Object.prototype.value = 'Unrelated user-provided data'; 741 742// Core 743const kEnumerableProperty = { __proto__: null, enumerable: true }; 744// In core, use const {kEnumerableProperty} = require('internal/util'); 745class SomeClass { 746 get readOnlyProperty() { return 'genuine data'; } 747} 748ObjectDefineProperty(SomeClass.prototype, 'readOnlyProperty', kEnumerableProperty); 749console.log(new SomeClass().readOnlyProperty); // genuine data 750``` 751 752### Defining a `Proxy` handler 753 754When defining a `Proxy`, the handler object could be at risk of prototype 755pollution when using a plain object literal: 756 757```js 758// User-land 759Object.prototype.get = () => 'Unrelated user-provided data'; 760 761// Core 762const objectToProxy = { someProperty: 'genuine value' }; 763 764const proxyWithPlainObjectLiteral = new Proxy(objectToProxy, { 765 has() { return false; }, 766}); 767console.log(proxyWithPlainObjectLiteral.someProperty); // Unrelated user-provided data 768 769const proxyWithNullPrototypeObject = new Proxy(objectToProxy, { 770 __proto__: null, 771 has() { return false; }, 772}); 773console.log(proxyWithNullPrototypeObject.someProperty); // genuine value 774``` 775