Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: save primordials during bootstrap and use it in builtins #25816

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/.eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ globals:
DCHECK_LT: false
DCHECK_NE: false
internalBinding: false
primordials: false
1 change: 1 addition & 0 deletions lib/internal/bootstrap/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const cannotBeRequired = [

'internal/test/binding',

'internal/bootstrap/primordials',
'internal/bootstrap/loaders',
'internal/bootstrap/node'
];
Expand Down
76 changes: 26 additions & 50 deletions lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,33 +40,20 @@
'use strict';

// This file is compiled as if it's wrapped in a function with arguments
// passed by node::LoadEnvironment()
// passed by node::RunBootstrapping()
/* global process, getBinding, getLinkedBinding, getInternalBinding */
/* global debugBreak, experimentalModules, exposeInternals */
/* global experimentalModules, exposeInternals, primordials */

if (debugBreak)
debugger; // eslint-disable-line no-debugger

const {
apply: ReflectApply,
deleteProperty: ReflectDeleteProperty,
get: ReflectGet,
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
has: ReflectHas,
set: ReflectSet,
} = Reflect;
const {
prototype: {
hasOwnProperty: ObjectHasOwnProperty,
},
create: ObjectCreate,
defineProperty: ObjectDefineProperty,
keys: ObjectKeys,
} = Object;
Reflect,
bmeck marked this conversation as resolved.
Show resolved Hide resolved
Object,
ObjectPrototype,
SafeSet
} = primordials;

// Set up process.moduleLoadList.
const moduleLoadList = [];
ObjectDefineProperty(process, 'moduleLoadList', {
Object.defineProperty(process, 'moduleLoadList', {
value: moduleLoadList,
configurable: true,
enumerable: true,
Expand All @@ -78,7 +65,7 @@ ObjectDefineProperty(process, 'moduleLoadList', {
// that are whitelisted for access via process.binding()... This is used
// to provide a transition path for modules that are being moved over to
// internalBinding.
const internalBindingWhitelist = [
const internalBindingWhitelist = new SafeSet([
'async_wrap',
'buffer',
'cares_wrap',
Expand Down Expand Up @@ -108,20 +95,17 @@ const internalBindingWhitelist = [
'uv',
'v8',
'zlib'
];
// We will use a lazy loaded SafeSet in internalBindingWhitelistHas
// for checking existence in this list.
let internalBindingWhitelistSet;
]);

// Set up process.binding() and process._linkedBinding().
{
const bindingObj = ObjectCreate(null);
const bindingObj = Object.create(null);

process.binding = function binding(module) {
module = String(module);
// Deprecated specific process.binding() modules, but not all, allow
// selective fallback to internalBinding for the deprecated ones.
if (internalBindingWhitelistHas(module)) {
if (internalBindingWhitelist.has(module)) {
return internalBinding(module);
}
let mod = bindingObj[module];
Expand All @@ -144,7 +128,7 @@ let internalBindingWhitelistSet;
// Set up internalBinding() in the closure.
let internalBinding;
{
const bindingObj = ObjectCreate(null);
const bindingObj = Object.create(null);
internalBinding = function internalBinding(module) {
let mod = bindingObj[module];
if (typeof mod !== 'object') {
Expand Down Expand Up @@ -245,8 +229,8 @@ NativeModule.requireWithFallbackInDeps = function(request) {
};

const getOwn = (target, property, receiver) => {
return ReflectApply(ObjectHasOwnProperty, target, [property]) ?
ReflectGet(target, property, receiver) :
return Reflect.apply(ObjectPrototype.hasOwnProperty, target, [property]) ?
bmeck marked this conversation as resolved.
Show resolved Hide resolved
Reflect.get(target, property, receiver) :
undefined;
};

Expand All @@ -255,12 +239,12 @@ const getOwn = (target, property, receiver) => {
// as the entire namespace (module.exports) and wrapped in a proxy such
// that APMs and other behavior are still left intact.
NativeModule.prototype.proxifyExports = function() {
this.exportKeys = ObjectKeys(this.exports);
this.exportKeys = Object.keys(this.exports);

const update = (property, value) => {
if (this.reflect !== undefined &&
ReflectApply(ObjectHasOwnProperty,
this.reflect.exports, [property]))
Reflect.apply(ObjectPrototype.hasOwnProperty,
this.reflect.exports, [property]))
this.reflect.exports[property].set(value);
};

Expand All @@ -269,12 +253,12 @@ NativeModule.prototype.proxifyExports = function() {
defineProperty: (target, prop, descriptor) => {
// Use `Object.defineProperty` instead of `Reflect.defineProperty`
// to throw the appropriate error if something goes wrong.
ObjectDefineProperty(target, prop, descriptor);
Object.defineProperty(target, prop, descriptor);
if (typeof descriptor.get === 'function' &&
!ReflectHas(handler, 'get')) {
!Reflect.has(handler, 'get')) {
handler.get = (target, prop, receiver) => {
const value = ReflectGet(target, prop, receiver);
if (ReflectApply(ObjectHasOwnProperty, target, [prop]))
const value = Reflect.get(target, prop, receiver);
if (Reflect.apply(ObjectPrototype.hasOwnProperty, target, [prop]))
update(prop, value);
return value;
};
Expand All @@ -283,15 +267,15 @@ NativeModule.prototype.proxifyExports = function() {
return true;
},
deleteProperty: (target, prop) => {
if (ReflectDeleteProperty(target, prop)) {
if (Reflect.deleteProperty(target, prop)) {
update(prop, undefined);
return true;
}
return false;
},
set: (target, prop, value, receiver) => {
const descriptor = ReflectGetOwnPropertyDescriptor(target, prop);
if (ReflectSet(target, prop, value, receiver)) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
if (Reflect.set(target, prop, value, receiver)) {
if (descriptor && typeof descriptor.set === 'function') {
for (const key of this.exportKeys) {
update(key, getOwn(target, key, receiver));
Expand Down Expand Up @@ -319,7 +303,7 @@ NativeModule.prototype.compile = function() {
NativeModule.require;

const fn = compileFunction(id);
fn(this.exports, requireFn, this, process, internalBinding);
fn(this.exports, requireFn, this, process, internalBinding, primordials);

if (experimentalModules && this.canBeRequiredByUsers) {
this.proxifyExports();
Expand All @@ -344,13 +328,5 @@ if (process.env.NODE_V8_COVERAGE) {
}
}

function internalBindingWhitelistHas(name) {
if (!internalBindingWhitelistSet) {
const { SafeSet } = NativeModule.require('internal/safe_globals');
internalBindingWhitelistSet = new SafeSet(internalBindingWhitelist);
}
return internalBindingWhitelistSet.has(name);
}

// This will be passed to internal/bootstrap/node.js.
return loaderExports;
84 changes: 84 additions & 0 deletions lib/internal/bootstrap/primordials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

/* global breakAtBootstrap, 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.

if (breakAtBootstrap) {
debugger; // eslint-disable-line no-debugger
}

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 makeSafe(unsafe, safe) {
copyProps(unsafe.prototype, safe.prototype);
copyProps(unsafe, safe);
Object.setPrototypeOf(safe.prototype, null);
Object.freeze(safe.prototype);
Object.freeze(safe);
return safe;
}

// Subclass the constructors because we need to use their prototype
// methods later.
primordials.SafeMap = makeSafe(
Map,
class SafeMap extends Map {}
);
primordials.SafeWeakMap = makeSafe(
WeakMap,
class SafeWeakMap extends WeakMap {}
);
primordials.SafeSet = makeSafe(
Set,
class SafeSet extends Set {}
);
primordials.SafePromise = makeSafe(
Promise,
class SafePromise extends Promise {}
);

// Create copies of the namespace objects
[
'JSON',
'Math',
'Reflect'
].forEach((name) => {
const target = primordials[name] = Object.create(null);
copyProps(global[name], target);
});

// Create copies of intrinsic objects
[
'Array',
'Date',
'Function',
'Object',
'RegExp',
'String'
].forEach((name) => {
const target = primordials[name] = Object.create(null);
copyProps(global[name], target);
const proto = primordials[name + 'Prototype'] = Object.create(null);
copyProps(global[name].prototype, proto);
});

Object.setPrototypeOf(primordials, null);
Object.freeze(primordials);
14 changes: 10 additions & 4 deletions lib/internal/error-serdes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

const Buffer = require('buffer').Buffer;
const { serialize, deserialize } = require('v8');
const { SafeSet } = require('internal/safe_globals');
const {
SafeSet,
Object,
ObjectPrototype,
FunctionPrototype,
ArrayPrototype
} = primordials;

const kSerializedError = 0;
const kSerializedObject = 1;
Expand All @@ -14,9 +20,9 @@ const GetOwnPropertyNames = Object.getOwnPropertyNames;
const DefineProperty = Object.defineProperty;
const Assign = Object.assign;
const ObjectPrototypeToString =
Function.prototype.call.bind(Object.prototype.toString);
const ForEach = Function.prototype.call.bind(Array.prototype.forEach);
const Call = Function.prototype.call.bind(Function.prototype.call);
FunctionPrototype.call.bind(ObjectPrototype.toString);
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
const ForEach = FunctionPrototype.call.bind(ArrayPrototype.forEach);
const Call = FunctionPrototype.call.bind(FunctionPrototype.call);

const errors = {
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError
Expand Down
6 changes: 5 additions & 1 deletion lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
'use strict';

const { ModuleWrap } = internalBinding('module_wrap');
const { SafeSet, SafePromise } = require('internal/safe_globals');
const {
SafeSet,
SafePromise
} = primordials;

const { decorateErrorStack } = require('internal/util');
const assert = require('assert');
const resolvedPromise = SafePromise.resolve();
Expand Down
4 changes: 3 additions & 1 deletion lib/internal/modules/esm/module_map.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

const ModuleJob = require('internal/modules/esm/module_job');
const { SafeMap } = require('internal/safe_globals');
const {
SafeMap
} = primordials;
const debug = require('util').debuglog('esm');
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
const { validateString } = require('internal/validators');
Expand Down
9 changes: 7 additions & 2 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
const fs = require('fs');
const { _makeLong } = require('path');
const { SafeMap } = require('internal/safe_globals');
const {
SafeMap,
JSON,
FunctionPrototype,
StringPrototype
} = primordials;
const { URL } = require('url');
const { debuglog, promisify } = require('util');
const esmLoader = require('internal/process/esm_loader');

const readFileAsync = promisify(fs.readFile);
const readFileSync = fs.readFileSync;
const StringReplace = Function.call.bind(String.prototype.replace);
const StringReplace = FunctionPrototype.call.bind(StringPrototype.replace);
const JsonParse = JSON.parse;

const debug = debuglog('esm');
Expand Down
17 changes: 11 additions & 6 deletions lib/internal/policy/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ const {
} = require('internal/errors').codes;
const debug = require('util').debuglog('policy');
const SRI = require('internal/policy/sri');
const { SafeWeakMap } = require('internal/safe_globals');
const {
SafeWeakMap,
FunctionPrototype,
Object,
RegExpPrototype
} = primordials;
const crypto = require('crypto');
const { Buffer } = require('buffer');
const { URL } = require('url');
const { createHash, timingSafeEqual } = crypto;
const HashUpdate = Function.call.bind(crypto.Hash.prototype.update);
const HashDigest = Function.call.bind(crypto.Hash.prototype.digest);
const BufferEquals = Function.call.bind(Buffer.prototype.equals);
const BufferToString = Function.call.bind(Buffer.prototype.toString);
const RegExpTest = Function.call.bind(RegExp.prototype.test);
const HashUpdate = FunctionPrototype.call.bind(crypto.Hash.prototype.update);
const HashDigest = FunctionPrototype.call.bind(crypto.Hash.prototype.digest);
const BufferEquals = FunctionPrototype.call.bind(Buffer.prototype.equals);
const BufferToString = FunctionPrototype.call.bind(Buffer.prototype.toString);
const RegExpTest = FunctionPrototype.call.bind(RegExpPrototype.test);
const { entries } = Object;
const kIntegrities = new SafeWeakMap();
const kReactions = new SafeWeakMap();
Expand Down
25 changes: 0 additions & 25 deletions lib/internal/safe_globals.js

This file was deleted.

Loading