diff options
Diffstat (limited to 'core/00_primordials.js')
-rw-r--r-- | core/00_primordials.js | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/core/00_primordials.js b/core/00_primordials.js new file mode 100644 index 000000000..85d924a00 --- /dev/null +++ b/core/00_primordials.js @@ -0,0 +1,462 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// Based on https://github.com/nodejs/node/blob/889ad35d3d41e376870f785b0c1b669cb732013d/lib/internal/per_context/primordials.js +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// 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. + +// Use of primordials have sometimes a dramatic impact on performance, please +// benchmark all changes made in performance-sensitive areas of the codebase. +// See: https://github.com/nodejs/node/pull/38248 + +"use strict"; + +(() => { + const primordials = {}; + + const { + defineProperty: ReflectDefineProperty, + getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor, + ownKeys: ReflectOwnKeys, + } = Reflect; + + // `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 { apply, bind, call } = Function.prototype; + const uncurryThis = bind.bind(call); + primordials.uncurryThis = uncurryThis; + + // `applyBind` is equivalent to `func => Function.prototype.apply.bind(func)`. + // It is using `bind.bind(apply)` to avoid using `Function.prototype.bind` + // and `Function.prototype.apply` after it may have been mutated by users. + const applyBind = bind.bind(apply); + primordials.applyBind = applyBind; + + // Methods that accept a variable number of arguments, and thus it's useful to + // also create `${prefix}${key}Apply`, which uses `Function.prototype.apply`, + // instead of `Function.prototype.call`, and thus doesn't require iterator + // destructuring. + const varargsMethods = [ + // 'ArrayPrototypeConcat' is omitted, because it performs the spread + // on its own for arrays and array-likes with a truthy + // @@isConcatSpreadable symbol property. + "ArrayOf", + "ArrayPrototypePush", + "ArrayPrototypeUnshift", + // 'FunctionPrototypeCall' is omitted, since there's 'ReflectApply' + // and 'FunctionPrototypeApply'. + "MathHypot", + "MathMax", + "MathMin", + "StringPrototypeConcat", + "TypedArrayOf", + ]; + + 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 }) { + ReflectDefineProperty(dest, `${prefix}Get${key}`, { + value: uncurryThis(get), + enumerable, + }); + if (set !== undefined) { + ReflectDefineProperty(dest, `${prefix}Set${key}`, { + value: uncurryThis(set), + enumerable, + }); + } + } + + function copyPropsRenamed(src, dest, prefix) { + for (const key of ReflectOwnKeys(src)) { + const newKey = getNewKey(key); + const desc = ReflectGetOwnPropertyDescriptor(src, key); + if ("get" in desc) { + copyAccessor(dest, prefix, newKey, desc); + } else { + const name = `${prefix}${newKey}`; + ReflectDefineProperty(dest, name, desc); + if (varargsMethods.includes(name)) { + ReflectDefineProperty(dest, `${name}Apply`, { + // `src` is bound as the `this` so that the static `this` points + // to the object it was defined on, + // e.g.: `ArrayOfApply` gets a `this` of `Array`: + value: applyBind(desc.value, src), + }); + } + } + } + } + + function copyPropsRenamedBound(src, dest, prefix) { + for (const key of ReflectOwnKeys(src)) { + const newKey = getNewKey(key); + const desc = ReflectGetOwnPropertyDescriptor(src, key); + if ("get" in desc) { + copyAccessor(dest, prefix, newKey, desc); + } else { + const { value } = desc; + if (typeof value === "function") { + desc.value = value.bind(src); + } + + const name = `${prefix}${newKey}`; + ReflectDefineProperty(dest, name, desc); + if (varargsMethods.includes(name)) { + ReflectDefineProperty(dest, `${name}Apply`, { + value: applyBind(value, src), + }); + } + } + } + } + + function copyPrototype(src, dest, prefix) { + for (const key of ReflectOwnKeys(src)) { + const newKey = getNewKey(key); + const desc = ReflectGetOwnPropertyDescriptor(src, key); + if ("get" in desc) { + copyAccessor(dest, prefix, newKey, desc); + } else { + const { value } = desc; + if (typeof value === "function") { + desc.value = uncurryThis(value); + } + + const name = `${prefix}${newKey}`; + ReflectDefineProperty(dest, name, desc); + if (varargsMethods.includes(name)) { + ReflectDefineProperty(dest, `${name}Apply`, { + value: applyBind(value), + }); + } + } + } + } + + // Create copies of configurable value properties of the global object + [ + "Proxy", + "globalThis", + ].forEach((name) => { + primordials[name] = globalThis[name]; + }); + + // Create copies of URI handling functions + [ + decodeURI, + decodeURIComponent, + encodeURI, + encodeURIComponent, + ].forEach((fn) => { + primordials[fn.name] = fn; + }); + + // Create copies of the namespace objects + [ + "JSON", + "Math", + "Proxy", + "Reflect", + ].forEach((name) => { + copyPropsRenamed(globalThis[name], primordials, name); + }); + + // Create copies of intrinsic objects + [ + "AggregateError", + "Array", + "ArrayBuffer", + "BigInt", + "BigInt64Array", + "BigUint64Array", + "Boolean", + "DataView", + "Date", + "Error", + "EvalError", + // TODO(lucacasonato): not present in snapshots. Why? + // "FinalizationRegistry", + "Float32Array", + "Float64Array", + "Function", + "Int16Array", + "Int32Array", + "Int8Array", + "Map", + "Number", + "Object", + "RangeError", + "ReferenceError", + "RegExp", + "Set", + "String", + "Symbol", + "SyntaxError", + "TypeError", + "URIError", + "Uint16Array", + "Uint32Array", + "Uint8Array", + "Uint8ClampedArray", + "WeakMap", + // TODO(lucacasonato): not present in snapshots. Why? + // "WeakRef", + "WeakSet", + ].forEach((name) => { + const original = globalThis[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 = globalThis[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`); + }); + + const { + ArrayPrototypeForEach, + FunctionPrototypeCall, + Map, + ObjectFreeze, + ObjectSetPrototypeOf, + Promise, + PromisePrototypeThen, + Set, + SymbolIterator, + WeakMap, + WeakSet, + } = primordials; + + // Because these functions are used by `makeSafe`, which is exposed + // on the `primordials` object, it's important to use const references + // to the primordials that they use: + const createSafeIterator = (factory, next) => { + class SafeIterator { + constructor(iterable) { + this._iterator = factory(iterable); + } + next() { + return next(this._iterator); + } + [SymbolIterator]() { + return this; + } + } + ObjectSetPrototypeOf(SafeIterator.prototype, null); + ObjectFreeze(SafeIterator.prototype); + ObjectFreeze(SafeIterator); + return SafeIterator; + }; + + primordials.SafeArrayIterator = createSafeIterator( + primordials.ArrayPrototypeSymbolIterator, + primordials.ArrayIteratorPrototypeNext, + ); + primordials.SafeStringIterator = createSafeIterator( + primordials.StringPrototypeSymbolIterator, + primordials.StringIteratorPrototypeNext, + ); + + const copyProps = (src, dest) => { + ArrayPrototypeForEach(ReflectOwnKeys(src), (key) => { + if (!ReflectGetOwnPropertyDescriptor(dest, key)) { + ReflectDefineProperty( + dest, + key, + ReflectGetOwnPropertyDescriptor(src, key), + ); + } + }); + }; + + /** + * @type {typeof primordials.makeSafe} + */ + const makeSafe = (unsafe, safe) => { + if (SymbolIterator in unsafe.prototype) { + const dummy = new unsafe(); + let next; // We can reuse the same `next` method. + + ArrayPrototypeForEach(ReflectOwnKeys(unsafe.prototype), (key) => { + if (!ReflectGetOwnPropertyDescriptor(safe.prototype, key)) { + const desc = ReflectGetOwnPropertyDescriptor(unsafe.prototype, key); + if ( + typeof desc.value === "function" && + desc.value.length === 0 && + SymbolIterator in (FunctionPrototypeCall(desc.value, dummy) ?? {}) + ) { + const createIterator = uncurryThis(desc.value); + next ??= uncurryThis(createIterator(dummy).next); + const SafeIterator = createSafeIterator(createIterator, next); + desc.value = function () { + return new SafeIterator(this); + }; + } + ReflectDefineProperty(safe.prototype, key, desc); + } + }); + } else { + copyProps(unsafe.prototype, safe.prototype); + } + copyProps(unsafe, safe); + + ObjectSetPrototypeOf(safe.prototype, null); + ObjectFreeze(safe.prototype); + ObjectFreeze(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); + } + }, + ); + primordials.SafeWeakMap = makeSafe( + WeakMap, + class SafeWeakMap extends WeakMap { + constructor(i) { + super(i); + } + }, + ); + + primordials.SafeSet = makeSafe( + Set, + class SafeSet extends Set { + constructor(i) { + super(i); + } + }, + ); + primordials.SafeWeakSet = makeSafe( + WeakSet, + class SafeWeakSet extends WeakSet { + constructor(i) { + super(i); + } + }, + ); + + // TODO(lucacasonato): not present in snapshots. Why? + // primordials.SafeFinalizationRegistry = makeSafe( + // FinalizationRegistry, + // class SafeFinalizationRegistry extends FinalizationRegistry { + // constructor(cleanupCallback) { + // super(cleanupCallback); + // } + // }, + // ); + + // TODO(lucacasonato): not present in snapshots. Why? + // primordials.SafeWeakRef = makeSafe( + // WeakRef, + // class SafeWeakRef extends WeakRef { + // constructor(target) { + // super(target); + // } + // }, + // ); + + const SafePromise = makeSafe( + Promise, + class SafePromise extends Promise { + constructor(executor) { + super(executor); + } + }, + ); + + primordials.PromisePrototypeCatch = (thisPromise, onRejected) => + PromisePrototypeThen(thisPromise, undefined, onRejected); + + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or + * rejected). The resolved value cannot be modified from the callback. + * Prefer using async functions when possible. + * @param {Promise<any>} thisPromise + * @param {() => void) | undefined | null} onFinally The callback to execute + * when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + primordials.SafePromisePrototypeFinally = (thisPromise, onFinally) => + // Wrapping on a new Promise is necessary to not expose the SafePromise + // prototype to user-land. + new Promise((a, b) => + new SafePromise((a, b) => PromisePrototypeThen(thisPromise, a, b)) + .finally(onFinally) + .then(a, b) + ); + + ObjectSetPrototypeOf(primordials, null); + ObjectFreeze(primordials); + + // Provide bootstrap namespace + globalThis.__bootstrap = { primordials }; +})(); |