diff --git a/lib/internal/bootstrap/realm.js b/lib/internal/bootstrap/realm.js index 221b1de5424d45..4eb71e3818db85 100644 --- a/lib/internal/bootstrap/realm.js +++ b/lib/internal/bootstrap/realm.js @@ -128,11 +128,15 @@ const legacyWrapperList = new SafeSet([ 'util', ]); +// The code bellow assumes that the two lists must not contain any modules +// beginning with "internal/". // Modules that can only be imported via the node: scheme. const schemelessBlockList = new SafeSet([ 'test', 'test/reporters', ]); +// Modules that will only be enabled at run time. +const experimentalModuleList = new SafeSet(); // Set up process.binding() and process._linkedBinding(). { @@ -199,6 +203,20 @@ const getOwn = (target, property, receiver) => { undefined; }; +const publicBuiltinIds = builtinIds + .filter((id) => + !StringPrototypeStartsWith(id, 'internal/') && + !experimentalModuleList.has(id), + ); +// Do not expose the loaders to user land even with --expose-internals. +const internalBuiltinIds = builtinIds + .filter((id) => StringPrototypeStartsWith(id, 'internal/') && id !== selfId); + +// When --expose-internals is on we'll add the internal builtin ids to these. +const canBeRequiredByUsersList = new SafeSet(publicBuiltinIds); +const canBeRequiredByUsersWithoutSchemeList = + new SafeSet(publicBuiltinIds.filter((id) => !schemelessBlockList.has(id))); + /** * An internal abstraction for the built-in JavaScript modules of Node.js. * Be careful not to expose this to user land unless --expose-internals is @@ -216,7 +234,6 @@ class BuiltinModule { constructor(id) { this.filename = `${id}.js`; this.id = id; - this.canBeRequiredByUsers = !StringPrototypeStartsWith(id, 'internal/'); // The CJS exports object of the module. this.exports = {}; @@ -238,14 +255,23 @@ class BuiltinModule { this.exportKeys = undefined; } + static allowRequireByUsers(id) { + if (id === selfId) { + // No code because this is an assertion against bugs. + // eslint-disable-next-line no-restricted-syntax + throw new Error(`Should not allow ${id}`); + } + canBeRequiredByUsersList.add(id); + if (!schemelessBlockList.has(id)) { + canBeRequiredByUsersWithoutSchemeList.add(id); + } + } + // To be called during pre-execution when --expose-internals is on. // Enables the user-land module loader to access internal modules. static exposeInternals() { - for (const { 0: id, 1: mod } of BuiltinModule.map) { - // Do not expose this to user land even with --expose-internals. - if (id !== selfId) { - mod.canBeRequiredByUsers = true; - } + for (let i = 0; i < internalBuiltinIds.length; ++i) { + BuiltinModule.allowRequireByUsers(internalBuiltinIds[i]); } } @@ -254,14 +280,23 @@ class BuiltinModule { } static canBeRequiredByUsers(id) { - const mod = BuiltinModule.map.get(id); - return mod && mod.canBeRequiredByUsers; + return canBeRequiredByUsersList.has(id); } - // Determine if a core module can be loaded without the node: prefix. This - // function does not validate if the module actually exists. static canBeRequiredWithoutScheme(id) { - return !schemelessBlockList.has(id); + return canBeRequiredByUsersWithoutSchemeList.has(id); + } + + static isBuiltin(id) { + return BuiltinModule.canBeRequiredWithoutScheme(id) || ( + typeof id === 'string' && + StringPrototypeStartsWith(id, 'node:') && + BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(id, 5)) + ); + } + + static getCanBeRequiredByUsersWithoutSchemeList() { + return ArrayFrom(canBeRequiredByUsersWithoutSchemeList); } static getSchemeOnlyModuleNames() { @@ -270,7 +305,7 @@ class BuiltinModule { // Used by user-land module loaders to compile and load builtins. compileForPublicLoader() { - if (!this.canBeRequiredByUsers) { + if (!BuiltinModule.canBeRequiredByUsers(this.id)) { // No code because this is an assertion against bugs // eslint-disable-next-line no-restricted-syntax throw new Error(`Should not compile ${this.id} for public use`); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 7b18f241f4b006..f6ec328e09fb3f 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -34,7 +34,6 @@ const { ArrayPrototypeSplice, ArrayPrototypeUnshift, ArrayPrototypeUnshiftApply, - ArrayPrototypeFlatMap, Boolean, Error, JSONParse, @@ -52,7 +51,6 @@ const { ReflectSet, RegExpPrototypeExec, SafeMap, - SafeSet, SafeWeakMap, String, StringPrototypeCharAt, @@ -82,7 +80,7 @@ const { } = require('internal/source_map/source_map_cache'); const { pathToFileURL, fileURLToPath, isURL } = require('internal/url'); const { - deprecate, + pendingDeprecate, emitExperimentalWarning, kEmptyObject, filterOwnProperties, @@ -310,44 +308,29 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => { debug = fn; }); -const builtinModules = []; +ObjectDefineProperty(Module.prototype, 'parent', { + __proto__: null, + get: pendingDeprecate( + getModuleParent, + 'module.parent is deprecated due to accuracy issues. Please use ' + + 'require.main to find program entry point instead.', + 'DEP0144', + ), + set: pendingDeprecate( + setModuleParent, + 'module.parent is deprecated due to accuracy issues. Please use ' + + 'require.main to find program entry point instead.', + 'DEP0144', + ), +}); +Module._debug = pendingDeprecate(debug, 'Module._debug is deprecated.', 'DEP0077'); +Module.isBuiltin = BuiltinModule.isBuiltin; + // This function is called during pre-execution, before any user code is run. function initializeCJS() { - const pendingDeprecation = getOptionValue('--pending-deprecation'); - ObjectDefineProperty(Module.prototype, 'parent', { - __proto__: null, - get: pendingDeprecation ? deprecate( - getModuleParent, - 'module.parent is deprecated due to accuracy issues. Please use ' + - 'require.main to find program entry point instead.', - 'DEP0144', - ) : getModuleParent, - set: pendingDeprecation ? deprecate( - setModuleParent, - 'module.parent is deprecated due to accuracy issues. Please use ' + - 'require.main to find program entry point instead.', - 'DEP0144', - ) : setModuleParent, - }); - Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077'); - - for (const { 0: id, 1: mod } of BuiltinModule.map) { - if (mod.canBeRequiredByUsers && - BuiltinModule.canBeRequiredWithoutScheme(id)) { - ArrayPrototypePush(builtinModules, id); - } - } - - const allBuiltins = new SafeSet( - ArrayPrototypeFlatMap(builtinModules, (bm) => [bm, `node:${bm}`]), - ); - BuiltinModule.getSchemeOnlyModuleNames().forEach((builtin) => allBuiltins.add(`node:${builtin}`)); - ObjectFreeze(builtinModules); - Module.builtinModules = builtinModules; - - Module.isBuiltin = function isBuiltin(moduleName) { - return allBuiltins.has(moduleName); - }; + // This need to be done at runtime in case --expose-internals is set. + const builtinModules = BuiltinModule.getCanBeRequiredByUsersWithoutSchemeList(); + Module.builtinModules = ObjectFreeze(builtinModules); initializeCjsConditions(); @@ -803,7 +786,6 @@ Module._resolveLookupPaths = function(request, parent) { StringPrototypeStartsWith(request, 'node:') && BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(request, 5)) ) || ( - BuiltinModule.canBeRequiredByUsers(request) && BuiltinModule.canBeRequiredWithoutScheme(request) )) { debug('looking for %j in []', request); @@ -925,11 +907,11 @@ Module._load = function(request, parent, isMain) { // Slice 'node:' prefix const id = StringPrototypeSlice(request, 5); - const module = loadBuiltinModule(id, request); - if (!module?.canBeRequiredByUsers) { + if (!BuiltinModule.canBeRequiredByUsers(id)) { throw new ERR_UNKNOWN_BUILTIN_MODULE(request); } + const module = loadBuiltinModule(id, request); return module.exports; } @@ -947,9 +929,8 @@ Module._load = function(request, parent, isMain) { } } - const mod = loadBuiltinModule(filename, request); - if (mod?.canBeRequiredByUsers && - BuiltinModule.canBeRequiredWithoutScheme(filename)) { + if (BuiltinModule.canBeRequiredWithoutScheme(filename)) { + const mod = loadBuiltinModule(filename, request); return mod.exports; } @@ -1003,7 +984,6 @@ Module._resolveFilename = function(request, parent, isMain, options) { StringPrototypeStartsWith(request, 'node:') && BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(request, 5)) ) || ( - BuiltinModule.canBeRequiredByUsers(request) && BuiltinModule.canBeRequiredWithoutScheme(request) ) ) { @@ -1459,8 +1439,7 @@ Module._preloadModules = function(requests) { Module.syncBuiltinESMExports = function syncBuiltinESMExports() { for (const mod of BuiltinModule.map.values()) { - if (mod.canBeRequiredByUsers && - BuiltinModule.canBeRequiredWithoutScheme(mod.id)) { + if (BuiltinModule.canBeRequiredWithoutScheme(mod.id)) { mod.syncExports(); } } diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index 803e60c23bc9ff..11e85326324f2e 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -207,8 +207,7 @@ class Hooks { globalThis, // Param getBuiltin (builtinName) => { - if (BuiltinModule.canBeRequiredByUsers(builtinName) && - BuiltinModule.canBeRequiredWithoutScheme(builtinName)) { + if (BuiltinModule.canBeRequiredWithoutScheme(builtinName)) { return require(builtinName); } throw new ERR_INVALID_ARG_VALUE('builtinName', builtinName); diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 448782cf96afac..72400a0ec9643c 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -803,8 +803,7 @@ function parsePackageName(specifier, base) { * @returns {resolved: URL, format? : string} */ function packageResolve(specifier, base, conditions) { - if (BuiltinModule.canBeRequiredByUsers(specifier) && - BuiltinModule.canBeRequiredWithoutScheme(specifier)) { + if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) { return new URL('node:' + specifier); } @@ -990,8 +989,7 @@ function checkIfDisallowedImport(specifier, parsed, parsedParentURL) { return { url: parsed.href }; } - if (BuiltinModule.canBeRequiredByUsers(specifier) && - BuiltinModule.canBeRequiredWithoutScheme(specifier)) { + if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) { throw new ERR_NETWORK_IMPORT_DISALLOWED( specifier, parsedParentURL, diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index 1eafceee205623..307a34cb09b512 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -59,13 +59,14 @@ function getCjsConditions() { } function loadBuiltinModule(filename, request) { - const mod = BuiltinModule.map.get(filename); - if (mod?.canBeRequiredByUsers) { - debug('load built-in module %s', request); - // compileForPublicLoader() throws if mod.canBeRequiredByUsers is false: - mod.compileForPublicLoader(); - return mod; + if (!BuiltinModule.canBeRequiredByUsers(filename)) { + return; } + const mod = BuiltinModule.map.get(filename); + debug('load built-in module %s', request); + // compileForPublicLoader() throws if canBeRequiredByUsers is false: + mod.compileForPublicLoader(); + return mod; } let $Module = null; @@ -99,8 +100,9 @@ function makeRequireFunction(mod, redirects) { const { href, protocol } = destination; if (protocol === 'node:') { const specifier = destination.pathname; - const mod = loadBuiltinModule(specifier, href); - if (mod && mod.canBeRequiredByUsers) { + + if (BuiltinModule.canBeRequiredByUsers(specifier)) { + const mod = loadBuiltinModule(specifier, href); return mod.exports; } throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); diff --git a/lib/internal/process/warning.js b/lib/internal/process/warning.js index 9c2a98af058957..3ce00004dab476 100644 --- a/lib/internal/process/warning.js +++ b/lib/internal/process/warning.js @@ -166,8 +166,8 @@ function emitWarning(warning, type, code, ctor) { process.nextTick(doEmitWarning, warning); } -function emitWarningSync(warning) { - process.emit('warning', createWarningObject(warning)); +function emitWarningSync(warning, type, code, ctor) { + process.emit('warning', createWarningObject(warning, type, code, ctor)); } function createWarningObject(warning, type, code, ctor, detail) { diff --git a/lib/internal/util.js b/lib/internal/util.js index be4e60051d8319..d9c292c99a6710 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -63,6 +63,7 @@ const { toUSVString: _toUSVString, } = internalBinding('util'); const { isNativeError } = internalBinding('types'); +const { getOptionValue } = require('internal/options'); const noCrypto = !process.versions.openssl; @@ -106,10 +107,52 @@ const codesWarned = new SafeSet(); let validateString; +function getDeprecationWarningEmitter( + code, msg, deprecated, useEmitSync, + shouldEmitWarning = () => true, +) { + let warned = false; + return function() { + if (!warned && shouldEmitWarning()) { + warned = true; + if (code !== undefined) { + if (!codesWarned.has(code)) { + const emitWarning = useEmitSync ? + require('internal/process/warning').emitWarningSync : + process.emitWarning; + emitWarning(msg, 'DeprecationWarning', code, deprecated); + codesWarned.add(code); + } + } else { + process.emitWarning(msg, 'DeprecationWarning', deprecated); + } + } + }; +} + +function isPendingDeprecation() { + return getOptionValue('--pending-deprecation') && + !getOptionValue('--no-deprecation'); +} + +// Internal deprecator for pending --pending-deprecation. This can be invoked +// at snapshot building time as the warning permission is only queried at +// run time. +function pendingDeprecate(fn, msg, code) { + const emitDeprecationWarning = getDeprecationWarningEmitter( + code, msg, deprecated, false, isPendingDeprecation, + ); + function deprecated(...args) { + emitDeprecationWarning(); + return ReflectApply(fn, this, args); + } + return deprecated; +} + // Mark that a method should not be used. // Returns a modified function which warns once by default. // If --no-deprecation is set, then it is a no-op. -function deprecate(fn, msg, code) { +function deprecate(fn, msg, code, useEmitSync) { if (process.noDeprecation === true) { return fn; } @@ -121,19 +164,12 @@ function deprecate(fn, msg, code) { if (code !== undefined) validateString(code, 'code'); - let warned = false; + const emitDeprecationWarning = getDeprecationWarningEmitter( + code, msg, deprecated, useEmitSync, + ); + function deprecated(...args) { - if (!warned) { - warned = true; - if (code !== undefined) { - if (!codesWarned.has(code)) { - process.emitWarning(msg, 'DeprecationWarning', code, deprecated); - codesWarned.add(code); - } - } else { - process.emitWarning(msg, 'DeprecationWarning', deprecated); - } - } + emitDeprecationWarning(); if (new.target) { return ReflectConstruct(fn, args, new.target); } @@ -784,4 +820,5 @@ module.exports = { kEmptyObject, kEnumerableProperty, setOwnProperty, + pendingDeprecate, }; diff --git a/test/fixtures/errors/force_colors.snapshot b/test/fixtures/errors/force_colors.snapshot index a360fd0a90497b..0a0cfd1040f886 100644 --- a/test/fixtures/errors/force_colors.snapshot +++ b/test/fixtures/errors/force_colors.snapshot @@ -4,10 +4,10 @@ throw new Error('Should include grayed stack trace') Error: Should include grayed stack trace at Object. (/test*force_colors.js:1:7) - at Module._compile (node:internal*modules*cjs*loader:1277:14) - at Module._extensions..js (node:internal*modules*cjs*loader:1331:10) - at Module.load (node:internal*modules*cjs*loader:1135:32) - at Module._load (node:internal*modules*cjs*loader:974:12) + at Module._compile (node:internal*modules*cjs*loader:1257:14) + at Module._extensions..js (node:internal*modules*cjs*loader:1311:10) + at Module.load (node:internal*modules*cjs*loader:1115:32) + at Module._load (node:internal*modules*cjs*loader:955:12)  at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:88:12)  at node:internal*main*run_main_module:23:47