Skip to content

Commit 92ca506

Browse files
joyeecheungtargos
authored andcommitted
lib: save primordials during bootstrap and use it in builtins
This patches changes the `safe_globals` internal module into a script that gets run during bootstrap and saves JavaScript builtins (primordials) into an object that is available for all other builtin modules to access lexically later. PR-URL: #25816 Refs: #18795 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gus Caplan <me@gus.host>
1 parent f63817f commit 92ca506

17 files changed

+185
-105
lines changed

lib/.eslintrc.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ globals:
4646
DCHECK_LT: false
4747
DCHECK_NE: false
4848
internalBinding: false
49+
primordials: false

lib/internal/bootstrap/cache.js

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const cannotBeRequired = [
2222

2323
'internal/test/binding',
2424

25+
'internal/bootstrap/primordials',
2526
'internal/bootstrap/loaders',
2627
'internal/bootstrap/node'
2728
];

lib/internal/bootstrap/loaders.js

+26-50
Original file line numberDiff line numberDiff line change
@@ -40,33 +40,20 @@
4040
'use strict';
4141

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

47-
if (debugBreak)
48-
debugger; // eslint-disable-line no-debugger
49-
50-
const {
51-
apply: ReflectApply,
52-
deleteProperty: ReflectDeleteProperty,
53-
get: ReflectGet,
54-
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
55-
has: ReflectHas,
56-
set: ReflectSet,
57-
} = Reflect;
5847
const {
59-
prototype: {
60-
hasOwnProperty: ObjectHasOwnProperty,
61-
},
62-
create: ObjectCreate,
63-
defineProperty: ObjectDefineProperty,
64-
keys: ObjectKeys,
65-
} = Object;
48+
Reflect,
49+
Object,
50+
ObjectPrototype,
51+
SafeSet
52+
} = primordials;
6653

6754
// Set up process.moduleLoadList.
6855
const moduleLoadList = [];
69-
ObjectDefineProperty(process, 'moduleLoadList', {
56+
Object.defineProperty(process, 'moduleLoadList', {
7057
value: moduleLoadList,
7158
configurable: true,
7259
enumerable: true,
@@ -78,7 +65,7 @@ ObjectDefineProperty(process, 'moduleLoadList', {
7865
// that are whitelisted for access via process.binding()... This is used
7966
// to provide a transition path for modules that are being moved over to
8067
// internalBinding.
81-
const internalBindingWhitelist = [
68+
const internalBindingWhitelist = new SafeSet([
8269
'async_wrap',
8370
'buffer',
8471
'cares_wrap',
@@ -108,20 +95,17 @@ const internalBindingWhitelist = [
10895
'uv',
10996
'v8',
11097
'zlib'
111-
];
112-
// We will use a lazy loaded SafeSet in internalBindingWhitelistHas
113-
// for checking existence in this list.
114-
let internalBindingWhitelistSet;
98+
]);
11599

116100
// Set up process.binding() and process._linkedBinding().
117101
{
118-
const bindingObj = ObjectCreate(null);
102+
const bindingObj = Object.create(null);
119103

120104
process.binding = function binding(module) {
121105
module = String(module);
122106
// Deprecated specific process.binding() modules, but not all, allow
123107
// selective fallback to internalBinding for the deprecated ones.
124-
if (internalBindingWhitelistHas(module)) {
108+
if (internalBindingWhitelist.has(module)) {
125109
return internalBinding(module);
126110
}
127111
let mod = bindingObj[module];
@@ -144,7 +128,7 @@ let internalBindingWhitelistSet;
144128
// Set up internalBinding() in the closure.
145129
let internalBinding;
146130
{
147-
const bindingObj = ObjectCreate(null);
131+
const bindingObj = Object.create(null);
148132
internalBinding = function internalBinding(module) {
149133
let mod = bindingObj[module];
150134
if (typeof mod !== 'object') {
@@ -245,8 +229,8 @@ NativeModule.requireWithFallbackInDeps = function(request) {
245229
};
246230

247231
const getOwn = (target, property, receiver) => {
248-
return ReflectApply(ObjectHasOwnProperty, target, [property]) ?
249-
ReflectGet(target, property, receiver) :
232+
return Reflect.apply(ObjectPrototype.hasOwnProperty, target, [property]) ?
233+
Reflect.get(target, property, receiver) :
250234
undefined;
251235
};
252236

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

260244
const update = (property, value) => {
261245
if (this.reflect !== undefined &&
262-
ReflectApply(ObjectHasOwnProperty,
263-
this.reflect.exports, [property]))
246+
Reflect.apply(ObjectPrototype.hasOwnProperty,
247+
this.reflect.exports, [property]))
264248
this.reflect.exports[property].set(value);
265249
};
266250

@@ -269,12 +253,12 @@ NativeModule.prototype.proxifyExports = function() {
269253
defineProperty: (target, prop, descriptor) => {
270254
// Use `Object.defineProperty` instead of `Reflect.defineProperty`
271255
// to throw the appropriate error if something goes wrong.
272-
ObjectDefineProperty(target, prop, descriptor);
256+
Object.defineProperty(target, prop, descriptor);
273257
if (typeof descriptor.get === 'function' &&
274-
!ReflectHas(handler, 'get')) {
258+
!Reflect.has(handler, 'get')) {
275259
handler.get = (target, prop, receiver) => {
276-
const value = ReflectGet(target, prop, receiver);
277-
if (ReflectApply(ObjectHasOwnProperty, target, [prop]))
260+
const value = Reflect.get(target, prop, receiver);
261+
if (Reflect.apply(ObjectPrototype.hasOwnProperty, target, [prop]))
278262
update(prop, value);
279263
return value;
280264
};
@@ -283,15 +267,15 @@ NativeModule.prototype.proxifyExports = function() {
283267
return true;
284268
},
285269
deleteProperty: (target, prop) => {
286-
if (ReflectDeleteProperty(target, prop)) {
270+
if (Reflect.deleteProperty(target, prop)) {
287271
update(prop, undefined);
288272
return true;
289273
}
290274
return false;
291275
},
292276
set: (target, prop, value, receiver) => {
293-
const descriptor = ReflectGetOwnPropertyDescriptor(target, prop);
294-
if (ReflectSet(target, prop, value, receiver)) {
277+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
278+
if (Reflect.set(target, prop, value, receiver)) {
295279
if (descriptor && typeof descriptor.set === 'function') {
296280
for (const key of this.exportKeys) {
297281
update(key, getOwn(target, key, receiver));
@@ -319,7 +303,7 @@ NativeModule.prototype.compile = function() {
319303
NativeModule.require;
320304

321305
const fn = compileFunction(id);
322-
fn(this.exports, requireFn, this, process, internalBinding);
306+
fn(this.exports, requireFn, this, process, internalBinding, primordials);
323307

324308
if (experimentalModules && this.canBeRequiredByUsers) {
325309
this.proxifyExports();
@@ -344,13 +328,5 @@ if (process.env.NODE_V8_COVERAGE) {
344328
}
345329
}
346330

347-
function internalBindingWhitelistHas(name) {
348-
if (!internalBindingWhitelistSet) {
349-
const { SafeSet } = NativeModule.require('internal/safe_globals');
350-
internalBindingWhitelistSet = new SafeSet(internalBindingWhitelist);
351-
}
352-
return internalBindingWhitelistSet.has(name);
353-
}
354-
355331
// This will be passed to internal/bootstrap/node.js.
356332
return loaderExports;

lib/internal/bootstrap/primordials.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
/* global breakAtBootstrap, 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+
// TODO(joyeecheung): we can restrict access to these globals in builtin
10+
// modules through the JS linter, for example: ban access such as `Object`
11+
// (which falls back to a lookup in the global proxy) in favor of
12+
// `primordials.Object` where `primordials` is a lexical variable passed
13+
// by the native module compiler.
14+
15+
if (breakAtBootstrap) {
16+
debugger; // eslint-disable-line no-debugger
17+
}
18+
19+
function copyProps(src, dest) {
20+
for (const key of Reflect.ownKeys(src)) {
21+
if (!Reflect.getOwnPropertyDescriptor(dest, key)) {
22+
Reflect.defineProperty(
23+
dest,
24+
key,
25+
Reflect.getOwnPropertyDescriptor(src, key));
26+
}
27+
}
28+
}
29+
30+
function makeSafe(unsafe, safe) {
31+
copyProps(unsafe.prototype, safe.prototype);
32+
copyProps(unsafe, safe);
33+
Object.setPrototypeOf(safe.prototype, null);
34+
Object.freeze(safe.prototype);
35+
Object.freeze(safe);
36+
return safe;
37+
}
38+
39+
// Subclass the constructors because we need to use their prototype
40+
// methods later.
41+
primordials.SafeMap = makeSafe(
42+
Map,
43+
class SafeMap extends Map {}
44+
);
45+
primordials.SafeWeakMap = makeSafe(
46+
WeakMap,
47+
class SafeWeakMap extends WeakMap {}
48+
);
49+
primordials.SafeSet = makeSafe(
50+
Set,
51+
class SafeSet extends Set {}
52+
);
53+
primordials.SafePromise = makeSafe(
54+
Promise,
55+
class SafePromise extends Promise {}
56+
);
57+
58+
// Create copies of the namespace objects
59+
[
60+
'JSON',
61+
'Math',
62+
'Reflect'
63+
].forEach((name) => {
64+
const target = primordials[name] = Object.create(null);
65+
copyProps(global[name], target);
66+
});
67+
68+
// Create copies of intrinsic objects
69+
[
70+
'Array',
71+
'Date',
72+
'Function',
73+
'Object',
74+
'RegExp',
75+
'String'
76+
].forEach((name) => {
77+
const target = primordials[name] = Object.create(null);
78+
copyProps(global[name], target);
79+
const proto = primordials[name + 'Prototype'] = Object.create(null);
80+
copyProps(global[name].prototype, proto);
81+
});
82+
83+
Object.setPrototypeOf(primordials, null);
84+
Object.freeze(primordials);

lib/internal/error-serdes.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
const Buffer = require('buffer').Buffer;
44
const { serialize, deserialize } = require('v8');
5-
const { SafeSet } = require('internal/safe_globals');
5+
const {
6+
SafeSet,
7+
Object,
8+
ObjectPrototype,
9+
FunctionPrototype,
10+
ArrayPrototype
11+
} = primordials;
612

713
const kSerializedError = 0;
814
const kSerializedObject = 1;
@@ -14,9 +20,9 @@ const GetOwnPropertyNames = Object.getOwnPropertyNames;
1420
const DefineProperty = Object.defineProperty;
1521
const Assign = Object.assign;
1622
const ObjectPrototypeToString =
17-
Function.prototype.call.bind(Object.prototype.toString);
18-
const ForEach = Function.prototype.call.bind(Array.prototype.forEach);
19-
const Call = Function.prototype.call.bind(Function.prototype.call);
23+
FunctionPrototype.call.bind(ObjectPrototype.toString);
24+
const ForEach = FunctionPrototype.call.bind(ArrayPrototype.forEach);
25+
const Call = FunctionPrototype.call.bind(FunctionPrototype.call);
2026

2127
const errors = {
2228
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError

lib/internal/modules/esm/module_job.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
'use strict';
22

33
const { ModuleWrap } = internalBinding('module_wrap');
4-
const { SafeSet, SafePromise } = require('internal/safe_globals');
4+
const {
5+
SafeSet,
6+
SafePromise
7+
} = primordials;
8+
59
const { decorateErrorStack } = require('internal/util');
610
const assert = require('assert');
711
const resolvedPromise = SafePromise.resolve();

lib/internal/modules/esm/module_map.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use strict';
22

33
const ModuleJob = require('internal/modules/esm/module_job');
4-
const { SafeMap } = require('internal/safe_globals');
4+
const {
5+
SafeMap
6+
} = primordials;
57
const debug = require('util').debuglog('esm');
68
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
79
const { validateString } = require('internal/validators');

lib/internal/modules/esm/translators.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@ const createDynamicModule = require(
1212
'internal/modules/esm/create_dynamic_module');
1313
const fs = require('fs');
1414
const { _makeLong } = require('path');
15-
const { SafeMap } = require('internal/safe_globals');
15+
const {
16+
SafeMap,
17+
JSON,
18+
FunctionPrototype,
19+
StringPrototype
20+
} = primordials;
1621
const { URL } = require('url');
1722
const { debuglog, promisify } = require('util');
1823
const esmLoader = require('internal/process/esm_loader');
1924

2025
const readFileAsync = promisify(fs.readFile);
2126
const readFileSync = fs.readFileSync;
22-
const StringReplace = Function.call.bind(String.prototype.replace);
27+
const StringReplace = FunctionPrototype.call.bind(StringPrototype.replace);
2328
const JsonParse = JSON.parse;
2429

2530
const debug = debuglog('esm');

lib/internal/policy/manifest.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ const {
66
} = require('internal/errors').codes;
77
const debug = require('util').debuglog('policy');
88
const SRI = require('internal/policy/sri');
9-
const { SafeWeakMap } = require('internal/safe_globals');
9+
const {
10+
SafeWeakMap,
11+
FunctionPrototype,
12+
Object,
13+
RegExpPrototype
14+
} = primordials;
1015
const crypto = require('crypto');
1116
const { Buffer } = require('buffer');
1217
const { URL } = require('url');
1318
const { createHash, timingSafeEqual } = crypto;
14-
const HashUpdate = Function.call.bind(crypto.Hash.prototype.update);
15-
const HashDigest = Function.call.bind(crypto.Hash.prototype.digest);
16-
const BufferEquals = Function.call.bind(Buffer.prototype.equals);
17-
const BufferToString = Function.call.bind(Buffer.prototype.toString);
18-
const RegExpTest = Function.call.bind(RegExp.prototype.test);
19+
const HashUpdate = FunctionPrototype.call.bind(crypto.Hash.prototype.update);
20+
const HashDigest = FunctionPrototype.call.bind(crypto.Hash.prototype.digest);
21+
const BufferEquals = FunctionPrototype.call.bind(Buffer.prototype.equals);
22+
const BufferToString = FunctionPrototype.call.bind(Buffer.prototype.toString);
23+
const RegExpTest = FunctionPrototype.call.bind(RegExpPrototype.test);
1924
const { entries } = Object;
2025
const kIntegrities = new SafeWeakMap();
2126
const kReactions = new SafeWeakMap();

lib/internal/safe_globals.js

-25
This file was deleted.

0 commit comments

Comments
 (0)