Skip to content

Commit 235bb73

Browse files
joyeecheungtargos
authored andcommitted
module: do not share the internal require function with public loaders
This patch removes `NativeModule.require` and `NativeModule.requireWithFallbackInDeps`. The public loaders now have to use a special method `NativeModule.prototype.compileForPublicLoader()` to compile native modules. In addition this patch moves the decisions of proxifying exports and throwing unknown builtin errors entirely to public loaders, and skip those during internal use - therefore `loaders.js`, which is compiled during bootstrap, no longer needs to be aware of the value of `--experimental-modules`. PR-URL: #26549 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 3c6f12c commit 235bb73

File tree

6 files changed

+65
-63
lines changed

6 files changed

+65
-63
lines changed

lib/internal/bootstrap/loaders.js

+27-30
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag
2020
// NM_F_LINKED.
2121
// - internalBinding(): the private internal C++ binding loader, inaccessible
22-
// from user land because they are only available from NativeModule.require().
22+
// from user land unless through `require('internal/test/binding')`.
2323
// These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL()
2424
// and have their nm_flags set to NM_F_INTERNAL.
2525
//
@@ -42,7 +42,7 @@
4242
// This file is compiled as if it's wrapped in a function with arguments
4343
// passed by node::RunBootstrapping()
4444
/* global process, getLinkedBinding, getInternalBinding */
45-
/* global experimentalModules, exposeInternals, primordials */
45+
/* global exposeInternals, primordials */
4646

4747
const {
4848
Reflect,
@@ -185,27 +185,9 @@ function nativeModuleRequire(id) {
185185
}
186186

187187
const mod = NativeModule.map.get(id);
188-
if (!mod) {
189-
// Model the error off the internal/errors.js model, but
190-
// do not use that module given that it could actually be
191-
// the one causing the error if there's a bug in Node.js.
192-
// eslint-disable-next-line no-restricted-syntax
193-
const err = new Error(`No such built-in module: ${id}`);
194-
err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
195-
err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]';
196-
throw err;
197-
}
198-
199-
if (mod.loaded || mod.loading) {
200-
return mod.exports;
201-
}
202-
203-
moduleLoadList.push(`NativeModule ${id}`);
204-
mod.compile();
205-
return mod.exports;
188+
return mod.compile();
206189
}
207190

208-
NativeModule.require = nativeModuleRequire;
209191
NativeModule.exists = function(id) {
210192
return NativeModule.map.has(id);
211193
};
@@ -217,11 +199,25 @@ NativeModule.canBeRequiredByUsers = function(id) {
217199

218200
// Allow internal modules from dependencies to require
219201
// other modules from dependencies by providing fallbacks.
220-
NativeModule.requireWithFallbackInDeps = function(request) {
202+
function requireWithFallbackInDeps(request) {
221203
if (!NativeModule.map.has(request)) {
222204
request = `internal/deps/${request}`;
223205
}
224-
return NativeModule.require(request);
206+
return nativeModuleRequire(request);
207+
}
208+
209+
// This is exposed for public loaders
210+
NativeModule.prototype.compileForPublicLoader = function(needToProxify) {
211+
if (!this.canBeRequiredByUsers) {
212+
// No code because this is an assertion against bugs
213+
// eslint-disable-next-line no-restricted-syntax
214+
throw new Error(`Should not compile ${this.id} for public use`);
215+
}
216+
this.compile();
217+
if (needToProxify && !this.exportKeys) {
218+
this.proxifyExports();
219+
}
220+
return this.exports;
225221
};
226222

227223
const getOwn = (target, property, receiver) => {
@@ -289,26 +285,27 @@ NativeModule.prototype.proxifyExports = function() {
289285
};
290286

291287
NativeModule.prototype.compile = function() {
292-
const id = this.id;
288+
if (this.loaded || this.loading) {
289+
return this.exports;
290+
}
293291

292+
const id = this.id;
294293
this.loading = true;
295294

296295
try {
297296
const requireFn = this.id.startsWith('internal/deps/') ?
298-
NativeModule.requireWithFallbackInDeps :
299-
NativeModule.require;
297+
requireWithFallbackInDeps : nativeModuleRequire;
300298

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

304-
if (experimentalModules && this.canBeRequiredByUsers) {
305-
this.proxifyExports();
306-
}
307-
308302
this.loaded = true;
309303
} finally {
310304
this.loading = false;
311305
}
306+
307+
moduleLoadList.push(`NativeModule ${id}`);
308+
return this.exports;
312309
};
313310

314311
// This will be passed to internal/bootstrap/node.js.

lib/internal/bootstrap/node.js

+19-21
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,12 @@
3333

3434
// This file is compiled as if it's wrapped in a function with arguments
3535
// passed by node::RunBootstrapping()
36-
/* global process, loaderExports, isMainThread, ownsProcessState */
36+
/* global process, require, internalBinding, isMainThread, ownsProcessState */
3737
/* global primordials */
3838

39-
const { internalBinding, NativeModule } = loaderExports;
4039
const { Object, Symbol } = primordials;
4140
const config = internalBinding('config');
42-
const { deprecate } = NativeModule.require('internal/util');
41+
const { deprecate } = require('internal/util');
4342

4443
setupProcessObject();
4544

@@ -50,17 +49,17 @@ process.domain = null;
5049
process._exiting = false;
5150

5251
// Bootstrappers for all threads, including worker threads and main thread
53-
const perThreadSetup = NativeModule.require('internal/process/per_thread');
52+
const perThreadSetup = require('internal/process/per_thread');
5453
// Bootstrappers for the main thread only
5554
let mainThreadSetup;
5655
// Bootstrappers for the worker threads only
5756
let workerThreadSetup;
5857
if (ownsProcessState) {
59-
mainThreadSetup = NativeModule.require(
58+
mainThreadSetup = require(
6059
'internal/process/main_thread_only'
6160
);
6261
} else {
63-
workerThreadSetup = NativeModule.require(
62+
workerThreadSetup = require(
6463
'internal/process/worker_thread_only'
6564
);
6665
}
@@ -116,14 +115,14 @@ if (isMainThread) {
116115

117116
const {
118117
emitWarning
119-
} = NativeModule.require('internal/process/warning');
118+
} = require('internal/process/warning');
120119

121120
process.emitWarning = emitWarning;
122121

123122
const {
124123
nextTick,
125124
runNextTicks
126-
} = NativeModule.require('internal/process/next_tick').setup();
125+
} = require('internal/process/next_tick').setup();
127126

128127
process.nextTick = nextTick;
129128
// Used to emulate a tick manually in the JS land.
@@ -161,7 +160,7 @@ if (credentials.implementsPosixCredentials) {
161160

162161
if (isMainThread) {
163162
const { getStdout, getStdin, getStderr } =
164-
NativeModule.require('internal/process/stdio').getMainThreadStdio();
163+
require('internal/process/stdio').getMainThreadStdio();
165164
setupProcessStdio(getStdout, getStdin, getStderr);
166165
} else {
167166
const { getStdout, getStdin, getStderr } =
@@ -173,7 +172,7 @@ if (isMainThread) {
173172
// process. They use the same functions as the JS embedder API. These callbacks
174173
// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
175174
// and the cost of doing so is negligible.
176-
const { nativeHooks } = NativeModule.require('internal/async_hooks');
175+
const { nativeHooks } = require('internal/async_hooks');
177176
internalBinding('async_wrap').setupHooks(nativeHooks);
178177

179178
// XXX(joyeecheung): this has to be done after the initial load of
@@ -183,7 +182,7 @@ if (config.hasInspector) {
183182
const {
184183
enable,
185184
disable
186-
} = NativeModule.require('internal/inspector_async_hook');
185+
} = require('internal/inspector_async_hook');
187186
internalBinding('inspector').registerAsyncHook(enable, disable);
188187
}
189188

@@ -193,22 +192,22 @@ if (!config.noBrowserGlobals) {
193192
// https://console.spec.whatwg.org/#console-namespace
194193
exposeNamespace(global, 'console', createGlobalConsole(global.console));
195194

196-
const { URL, URLSearchParams } = NativeModule.require('internal/url');
195+
const { URL, URLSearchParams } = require('internal/url');
197196
// https://url.spec.whatwg.org/#url
198197
exposeInterface(global, 'URL', URL);
199198
// https://url.spec.whatwg.org/#urlsearchparams
200199
exposeInterface(global, 'URLSearchParams', URLSearchParams);
201200

202201
const {
203202
TextEncoder, TextDecoder
204-
} = NativeModule.require('internal/encoding');
203+
} = require('internal/encoding');
205204
// https://encoding.spec.whatwg.org/#textencoder
206205
exposeInterface(global, 'TextEncoder', TextEncoder);
207206
// https://encoding.spec.whatwg.org/#textdecoder
208207
exposeInterface(global, 'TextDecoder', TextDecoder);
209208

210209
// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope
211-
const timers = NativeModule.require('timers');
210+
const timers = require('timers');
212211
defineOperation(global, 'clearInterval', timers.clearInterval);
213212
defineOperation(global, 'clearTimeout', timers.clearTimeout);
214213
defineOperation(global, 'setInterval', timers.setInterval);
@@ -302,7 +301,7 @@ Object.defineProperty(process, 'features', {
302301
fatalException,
303302
setUncaughtExceptionCaptureCallback,
304303
hasUncaughtExceptionCaptureCallback
305-
} = NativeModule.require('internal/process/execution');
304+
} = require('internal/process/execution');
306305

307306
process._fatalException = fatalException;
308307
process.setUncaughtExceptionCaptureCallback =
@@ -312,7 +311,7 @@ Object.defineProperty(process, 'features', {
312311
}
313312

314313
function setupProcessObject() {
315-
const EventEmitter = NativeModule.require('events');
314+
const EventEmitter = require('events');
316315
const origProcProto = Object.getPrototypeOf(process);
317316
Object.setPrototypeOf(origProcProto, EventEmitter.prototype);
318317
EventEmitter.call(process);
@@ -391,7 +390,7 @@ function setupGlobalProxy() {
391390
}
392391

393392
function setupBuffer() {
394-
const { Buffer } = NativeModule.require('buffer');
393+
const { Buffer } = require('buffer');
395394
const bufferBinding = internalBinding('buffer');
396395

397396
// Only after this point can C++ use Buffer::New()
@@ -404,9 +403,9 @@ function setupBuffer() {
404403

405404
function createGlobalConsole(consoleFromVM) {
406405
const consoleFromNode =
407-
NativeModule.require('internal/console/global');
406+
require('internal/console/global');
408407
if (config.hasInspector) {
409-
const inspector = NativeModule.require('internal/util/inspector');
408+
const inspector = require('internal/util/inspector');
410409
// This will be exposed by `require('inspector').console` later.
411410
inspector.consoleFromVM = consoleFromVM;
412411
// TODO(joyeecheung): postpone this until the first time inspector
@@ -424,8 +423,7 @@ function setupQueueMicrotask() {
424423
get() {
425424
process.emitWarning('queueMicrotask() is experimental.',
426425
'ExperimentalWarning');
427-
const { queueMicrotask } =
428-
NativeModule.require('internal/queue_microtask');
426+
const { queueMicrotask } = require('internal/queue_microtask');
429427

430428
Object.defineProperty(global, 'queueMicrotask', {
431429
value: queueMicrotask,

lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ E('ERR_UNHANDLED_ERROR',
10171017
if (err === undefined) return msg;
10181018
return `${msg} (${err})`;
10191019
}, Error);
1020+
E('ERR_UNKNOWN_BUILTIN_MODULE', 'No such built-in module: %s', Error);
10201021
E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error);
10211022
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);
10221023

lib/internal/modules/cjs/loader.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,8 @@ Module._resolveLookupPaths = function(request, parent, newReturn) {
567567

568568
// Check the cache for the requested file.
569569
// 1. If a module already exists in the cache: return its exports object.
570-
// 2. If the module is native: call `NativeModule.require()` with the
571-
// filename and return the result.
570+
// 2. If the module is native: call
571+
// `NativeModule.prototype.compileForPublicLoader()` and return the exports.
572572
// 3. Otherwise, create a new module for the file and save it to the cache.
573573
// Then have it load the file contents before returning its exports
574574
// object.
@@ -585,9 +585,10 @@ Module._load = function(request, parent, isMain) {
585585
return cachedModule.exports;
586586
}
587587

588-
if (NativeModule.canBeRequiredByUsers(filename)) {
588+
const mod = NativeModule.map.get(filename);
589+
if (mod && mod.canBeRequiredByUsers) {
589590
debug('load native module %s', request);
590-
return NativeModule.require(filename);
591+
return mod.compileForPublicLoader(experimentalModules);
591592
}
592593

593594
// Don't call updateChildren(), Module constructor already does.

lib/internal/modules/esm/translators.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ const { URL } = require('url');
2222
const { debuglog } = require('internal/util/debuglog');
2323
const { promisify } = require('internal/util');
2424
const esmLoader = require('internal/process/esm_loader');
25-
25+
const {
26+
ERR_UNKNOWN_BUILTIN_MODULE
27+
} = require('internal/errors').codes;
2628
const readFileAsync = promisify(fs.readFile);
2729
const readFileSync = fs.readFileSync;
2830
const StringReplace = FunctionPrototype.call.bind(StringPrototype.replace);
@@ -86,8 +88,11 @@ translators.set('builtin', async (url) => {
8688
debug(`Translating BuiltinModule ${url}`);
8789
// slice 'node:' scheme
8890
const id = url.slice(5);
89-
NativeModule.require(id);
9091
const module = NativeModule.map.get(id);
92+
if (!module) {
93+
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
94+
}
95+
module.compileForPublicLoader(true);
9196
return createDynamicModule(
9297
[...module.exportKeys, 'default'], url, (reflect) => {
9398
debug(`Loading BuiltinModule ${url}`);

src/node.cc

+6-6
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,6 @@ MaybeLocal<Value> RunBootstrapping(Environment* env) {
298298
env->process_string(),
299299
FIXED_ONE_BYTE_STRING(isolate, "getLinkedBinding"),
300300
FIXED_ONE_BYTE_STRING(isolate, "getInternalBinding"),
301-
// --experimental-modules
302-
FIXED_ONE_BYTE_STRING(isolate, "experimentalModules"),
303301
// --expose-internals
304302
FIXED_ONE_BYTE_STRING(isolate, "exposeInternals"),
305303
env->primordials_string()};
@@ -311,7 +309,6 @@ MaybeLocal<Value> RunBootstrapping(Environment* env) {
311309
env->NewFunctionTemplate(binding::GetInternalBinding)
312310
->GetFunction(context)
313311
.ToLocalChecked(),
314-
Boolean::New(isolate, env->options()->experimental_modules),
315312
Boolean::New(isolate, env->options()->expose_internals),
316313
env->primordials()};
317314

@@ -333,16 +330,19 @@ MaybeLocal<Value> RunBootstrapping(Environment* env) {
333330
loader_exports_obj->Get(context, env->require_string()).ToLocalChecked();
334331
env->set_native_module_require(require.As<Function>());
335332

336-
// process, loaderExports, isMainThread, ownsProcessState, primordials
333+
// process, require, internalBinding, isMainThread,
334+
// ownsProcessState, primordials
337335
std::vector<Local<String>> node_params = {
338336
env->process_string(),
339-
FIXED_ONE_BYTE_STRING(isolate, "loaderExports"),
337+
env->require_string(),
338+
env->internal_binding_string(),
340339
FIXED_ONE_BYTE_STRING(isolate, "isMainThread"),
341340
FIXED_ONE_BYTE_STRING(isolate, "ownsProcessState"),
342341
env->primordials_string()};
343342
std::vector<Local<Value>> node_args = {
344343
process,
345-
loader_exports_obj,
344+
require,
345+
internal_binding_loader,
346346
Boolean::New(isolate, env->is_main_thread()),
347347
Boolean::New(isolate, env->owns_process_state()),
348348
env->primordials()};

0 commit comments

Comments
 (0)