Skip to content

Commit 2a9eb31

Browse files
committed
src: move internal loaders out of bootstrap_node.js
- Moves the creation of `process.binding()`, `process._linkedBinding()` `internalBinding()` and `NativeModule` into a separate file `lib/internal/bootstrap_loaders.js`, and documents them there. This file will be compiled and run before `bootstrap_node.js`, which means we now bootstrap the internal module & binding system before actually bootstrapping Node.js. - Rename the special ID that can be used to require `NativeModule` as `internal/bootstrap_loaders` since it is setup there. Also put `internalBinding` in the object exported by `NativeModule.require` instead of putting it inside the `NativeModule.wrapper` - Use the original `getBinding()` to get the source code of native modules instead of getting it from `process.binding('native')` so that users cannot fake native modules by modifying the binding object. - Names the bootstrapping functions so their names show up in the stack trace. PR-URL: #19112 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Gus Caplan <[email protected]>
1 parent 1a5ec83 commit 2a9eb31

29 files changed

+391
-233
lines changed

.eslintrc.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ module.exports = {
244244
DTRACE_HTTP_SERVER_REQUEST: false,
245245
DTRACE_HTTP_SERVER_RESPONSE: false,
246246
DTRACE_NET_SERVER_CONNECTION: false,
247-
DTRACE_NET_STREAM_END: false,
248-
internalBinding: false,
247+
DTRACE_NET_STREAM_END: false
249248
},
250249
};

lib/assert.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const { openSync, closeSync, readSync } = require('fs');
3636
const { parseExpressionAt } = require('internal/deps/acorn/dist/acorn');
3737
const { inspect } = require('util');
3838
const { EOL } = require('os');
39-
const nativeModule = require('native_module');
39+
const { NativeModule } = require('internal/bootstrap_loaders');
4040

4141
// Escape control characters but not \n and \t to keep the line breaks and
4242
// indentation intact.
@@ -163,7 +163,7 @@ function getErrMessage(call) {
163163
}
164164

165165
// Skip Node.js modules!
166-
if (filename.endsWith('.js') && nativeModule.exists(filename.slice(0, -3))) {
166+
if (filename.endsWith('.js') && NativeModule.exists(filename.slice(0, -3))) {
167167
errorCache.set(identifier, undefined);
168168
return;
169169
}

lib/buffer.js

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const {
4141
// that test/parallel/test-buffer-bindingobj-no-zerofill.js is written.
4242
let isAnyArrayBuffer;
4343
try {
44+
const { internalBinding } = require('internal/bootstrap_loaders');
4445
isAnyArrayBuffer = internalBinding('types').isAnyArrayBuffer;
4546
} catch (e) {
4647
isAnyArrayBuffer = require('util').types.isAnyArrayBuffer;

lib/domain.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
ERR_UNHANDLED_ERROR
3535
} = require('internal/errors').codes;
3636
const { createHook } = require('async_hooks');
37+
const { internalBinding } = require('internal/bootstrap_loaders');
3738

3839
// overwrite process.domain with a getter/setter that will allow for more
3940
// effective optimizations

lib/internal/bootstrap_loaders.js

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// This file creates the internal module & binding loaders used by built-in
2+
// modules. In contrast, user land modules are loaded using
3+
// lib/module.js (CommonJS Modules) or lib/internal/loader/* (ES Modules).
4+
//
5+
// This file is compiled and run by node.cc before bootstrap_node.js
6+
// was called, therefore the loaders are bootstraped before we start to
7+
// actually bootstrap Node.js. It creates the following objects:
8+
//
9+
// C++ binding loaders:
10+
// - process.binding(): the legacy C++ binding loader, accessible from user land
11+
// because it is an object attached to the global process object.
12+
// These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE()
13+
// and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees
14+
// about the stability of these bindings, but still have to take care of
15+
// compatibility issues caused by them from time to time.
16+
// - process._linkedBinding(): intended to be used by embedders to add
17+
// additional C++ bindings in their applications. These C++ bindings
18+
// can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag
19+
// NM_F_LINKED.
20+
// - internalBinding(): the private internal C++ binding loader, inaccessible
21+
// from user land because they are only available from NativeModule.require()
22+
// These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL()
23+
// and have their nm_flags set to NM_F_INTERNAL.
24+
//
25+
// Internal JavaScript module loader:
26+
// - NativeModule: a minimal module system used to load the JavaScript core
27+
// modules found in lib/**/*.js and deps/**/*.js. All core modules are
28+
// compiled into the node binary via node_javascript.cc generated by js2c.py,
29+
// so they can be loaded faster without the cost of I/O. This class makes the
30+
// lib/internal/*, deps/internal/* modules and internalBinding() available by
31+
// default to core modules, and lets the core modules require itself via
32+
// require('internal/bootstrap_loaders') even when this file is not written in
33+
// CommonJS style.
34+
//
35+
// Other objects:
36+
// - process.moduleLoadList: an array recording the bindings and the modules
37+
// loaded in the process and the order in which they are loaded.
38+
39+
'use strict';
40+
41+
(function bootstrapInternalLoaders(process, getBinding, getLinkedBinding,
42+
getInternalBinding) {
43+
44+
// Set up process.moduleLoadList
45+
const moduleLoadList = [];
46+
Object.defineProperty(process, 'moduleLoadList', {
47+
value: moduleLoadList,
48+
configurable: true,
49+
enumerable: true,
50+
writable: false
51+
});
52+
53+
// Set up process.binding() and process._linkedBinding()
54+
{
55+
const bindingObj = Object.create(null);
56+
57+
process.binding = function binding(module) {
58+
module = String(module);
59+
let mod = bindingObj[module];
60+
if (typeof mod !== 'object') {
61+
mod = bindingObj[module] = getBinding(module);
62+
moduleLoadList.push(`Binding ${module}`);
63+
}
64+
return mod;
65+
};
66+
67+
process._linkedBinding = function _linkedBinding(module) {
68+
module = String(module);
69+
let mod = bindingObj[module];
70+
if (typeof mod !== 'object')
71+
mod = bindingObj[module] = getLinkedBinding(module);
72+
return mod;
73+
};
74+
}
75+
76+
// Set up internalBinding() in the closure
77+
let internalBinding;
78+
{
79+
const bindingObj = Object.create(null);
80+
internalBinding = function internalBinding(module) {
81+
let mod = bindingObj[module];
82+
if (typeof mod !== 'object') {
83+
mod = bindingObj[module] = getInternalBinding(module);
84+
moduleLoadList.push(`Internal Binding ${module}`);
85+
}
86+
return mod;
87+
};
88+
}
89+
90+
// Minimal sandbox helper
91+
const ContextifyScript = process.binding('contextify').ContextifyScript;
92+
function runInThisContext(code, options) {
93+
const script = new ContextifyScript(code, options);
94+
return script.runInThisContext();
95+
}
96+
97+
// Set up NativeModule
98+
function NativeModule(id) {
99+
this.filename = `${id}.js`;
100+
this.id = id;
101+
this.exports = {};
102+
this.loaded = false;
103+
this.loading = false;
104+
}
105+
106+
NativeModule._source = getBinding('natives');
107+
NativeModule._cache = {};
108+
109+
const config = getBinding('config');
110+
111+
// Think of this as module.exports in this file even though it is not
112+
// written in CommonJS style.
113+
const loaderExports = { internalBinding, NativeModule };
114+
const loaderId = 'internal/bootstrap_loaders';
115+
NativeModule.require = function(id) {
116+
if (id === loaderId) {
117+
return loaderExports;
118+
}
119+
120+
const cached = NativeModule.getCached(id);
121+
if (cached && (cached.loaded || cached.loading)) {
122+
return cached.exports;
123+
}
124+
125+
if (!NativeModule.exists(id)) {
126+
// Model the error off the internal/errors.js model, but
127+
// do not use that module given that it could actually be
128+
// the one causing the error if there's a bug in Node.js
129+
const err = new Error(`No such built-in module: ${id}`);
130+
err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
131+
err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]';
132+
throw err;
133+
}
134+
135+
moduleLoadList.push(`NativeModule ${id}`);
136+
137+
const nativeModule = new NativeModule(id);
138+
139+
nativeModule.cache();
140+
nativeModule.compile();
141+
142+
return nativeModule.exports;
143+
};
144+
145+
NativeModule.requireForDeps = function(id) {
146+
if (!NativeModule.exists(id) ||
147+
// TODO(TimothyGu): remove when DEP0084 reaches end of life.
148+
id.startsWith('node-inspect/') ||
149+
id.startsWith('v8/')) {
150+
id = `internal/deps/${id}`;
151+
}
152+
return NativeModule.require(id);
153+
};
154+
155+
NativeModule.getCached = function(id) {
156+
return NativeModule._cache[id];
157+
};
158+
159+
NativeModule.exists = function(id) {
160+
return NativeModule._source.hasOwnProperty(id);
161+
};
162+
163+
if (config.exposeInternals) {
164+
NativeModule.nonInternalExists = function(id) {
165+
// Do not expose this to user land even with --expose-internals
166+
if (id === loaderId) {
167+
return false;
168+
}
169+
return NativeModule.exists(id);
170+
};
171+
172+
NativeModule.isInternal = function(id) {
173+
// Do not expose this to user land even with --expose-internals
174+
return id === loaderId;
175+
};
176+
} else {
177+
NativeModule.nonInternalExists = function(id) {
178+
return NativeModule.exists(id) && !NativeModule.isInternal(id);
179+
};
180+
181+
NativeModule.isInternal = function(id) {
182+
return id.startsWith('internal/');
183+
};
184+
}
185+
186+
NativeModule.getSource = function(id) {
187+
return NativeModule._source[id];
188+
};
189+
190+
NativeModule.wrap = function(script) {
191+
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
192+
};
193+
194+
NativeModule.wrapper = [
195+
'(function (exports, require, module, process) {',
196+
'\n});'
197+
];
198+
199+
NativeModule.prototype.compile = function() {
200+
let source = NativeModule.getSource(this.id);
201+
source = NativeModule.wrap(source);
202+
203+
this.loading = true;
204+
205+
try {
206+
const fn = runInThisContext(source, {
207+
filename: this.filename,
208+
lineOffset: 0,
209+
displayErrors: true
210+
});
211+
const requireFn = this.id.startsWith('internal/deps/') ?
212+
NativeModule.requireForDeps :
213+
NativeModule.require;
214+
fn(this.exports, requireFn, this, process);
215+
216+
this.loaded = true;
217+
} finally {
218+
this.loading = false;
219+
}
220+
};
221+
222+
NativeModule.prototype.cache = function() {
223+
NativeModule._cache[this.id] = this;
224+
};
225+
226+
// This will be passed to the bootstrapNodeJSCore function in
227+
// bootstrap_node.js.
228+
return loaderExports;
229+
});

0 commit comments

Comments
 (0)