mirror of
https://github.com/nodejs/node.git
synced 2025-05-02 18:26:52 +00:00

Instead of calling into C++ each time we need to check the value of a command line option, cache the option map in a new `internal/options` module for faster access to the values in JS land. PR-URL: https://github.com/nodejs/node/pull/24091 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Refael Ackermann <refack@gmail.com>
430 lines
15 KiB
JavaScript
430 lines
15 KiB
JavaScript
// This file creates the internal module & binding loaders used by built-in
|
|
// modules. In contrast, user land modules are loaded using
|
|
// lib/internal/modules/cjs/loader.js (CommonJS Modules) or
|
|
// lib/internal/modules/esm/* (ES Modules).
|
|
//
|
|
// This file is compiled and run by node.cc before bootstrap/node.js
|
|
// was called, therefore the loaders are bootstraped before we start to
|
|
// actually bootstrap Node.js. It creates the following objects:
|
|
//
|
|
// C++ binding loaders:
|
|
// - process.binding(): the legacy C++ binding loader, accessible from user land
|
|
// because it is an object attached to the global process object.
|
|
// These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE()
|
|
// and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees
|
|
// about the stability of these bindings, but still have to take care of
|
|
// compatibility issues caused by them from time to time.
|
|
// - process._linkedBinding(): intended to be used by embedders to add
|
|
// additional C++ bindings in their applications. These C++ bindings
|
|
// can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag
|
|
// NM_F_LINKED.
|
|
// - internalBinding(): the private internal C++ binding loader, inaccessible
|
|
// from user land because they are only available from NativeModule.require()
|
|
// These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL()
|
|
// and have their nm_flags set to NM_F_INTERNAL.
|
|
//
|
|
// Internal JavaScript module loader:
|
|
// - NativeModule: a minimal module system used to load the JavaScript core
|
|
// modules found in lib/**/*.js and deps/**/*.js. All core modules are
|
|
// compiled into the node binary via node_javascript.cc generated by js2c.py,
|
|
// so they can be loaded faster without the cost of I/O. This class makes the
|
|
// lib/internal/*, deps/internal/* modules and internalBinding() available by
|
|
// default to core modules, and lets the core modules require itself via
|
|
// require('internal/bootstrap/loaders') even when this file is not written in
|
|
// CommonJS style.
|
|
//
|
|
// Other objects:
|
|
// - process.moduleLoadList: an array recording the bindings and the modules
|
|
// loaded in the process and the order in which they are loaded.
|
|
|
|
'use strict';
|
|
|
|
(function bootstrapInternalLoaders(process, getBinding, getLinkedBinding,
|
|
getInternalBinding, debugBreak) {
|
|
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;
|
|
|
|
// Set up process.moduleLoadList
|
|
const moduleLoadList = [];
|
|
ObjectDefineProperty(process, 'moduleLoadList', {
|
|
value: moduleLoadList,
|
|
configurable: true,
|
|
enumerable: true,
|
|
writable: false
|
|
});
|
|
|
|
// internalBindingWhitelist contains the name of internalBinding modules
|
|
// 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 = [
|
|
'cares_wrap',
|
|
'fs_event_wrap',
|
|
'icu',
|
|
'udp_wrap',
|
|
'uv',
|
|
'pipe_wrap',
|
|
'http_parser',
|
|
'process_wrap',
|
|
'v8',
|
|
'tty_wrap',
|
|
'stream_wrap',
|
|
'signal_wrap',
|
|
'crypto',
|
|
'contextify',
|
|
'tcp_wrap',
|
|
'tls_wrap',
|
|
'util',
|
|
'async_wrap',
|
|
'url',
|
|
'spawn_sync',
|
|
'js_stream',
|
|
'zlib',
|
|
'buffer',
|
|
'natives',
|
|
'constants'
|
|
];
|
|
// 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);
|
|
|
|
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)) {
|
|
return internalBinding(module);
|
|
}
|
|
let mod = bindingObj[module];
|
|
if (typeof mod !== 'object') {
|
|
mod = bindingObj[module] = getBinding(module);
|
|
moduleLoadList.push(`Binding ${module}`);
|
|
}
|
|
return mod;
|
|
};
|
|
|
|
process._linkedBinding = function _linkedBinding(module) {
|
|
module = String(module);
|
|
let mod = bindingObj[module];
|
|
if (typeof mod !== 'object')
|
|
mod = bindingObj[module] = getLinkedBinding(module);
|
|
return mod;
|
|
};
|
|
}
|
|
|
|
// Set up internalBinding() in the closure
|
|
let internalBinding;
|
|
{
|
|
const bindingObj = ObjectCreate(null);
|
|
internalBinding = function internalBinding(module) {
|
|
let mod = bindingObj[module];
|
|
if (typeof mod !== 'object') {
|
|
mod = bindingObj[module] = getInternalBinding(module);
|
|
moduleLoadList.push(`Internal Binding ${module}`);
|
|
}
|
|
return mod;
|
|
};
|
|
}
|
|
|
|
// Create this WeakMap in js-land because V8 has no C++ API for WeakMap
|
|
internalBinding('module_wrap').callbackMap = new WeakMap();
|
|
const { ContextifyScript } = internalBinding('contextify');
|
|
|
|
// Set up NativeModule
|
|
function NativeModule(id) {
|
|
this.filename = `${id}.js`;
|
|
this.id = id;
|
|
this.exports = {};
|
|
this.reflect = undefined;
|
|
this.exportKeys = undefined;
|
|
this.loaded = false;
|
|
this.loading = false;
|
|
this.script = null; // The ContextifyScript of the module
|
|
}
|
|
|
|
NativeModule._source = getInternalBinding('natives');
|
|
NativeModule._cache = {};
|
|
|
|
const config = getBinding('config');
|
|
|
|
const codeCache = getInternalBinding('code_cache');
|
|
const codeCacheHash = getInternalBinding('code_cache_hash');
|
|
const sourceHash = getInternalBinding('natives_hash');
|
|
const compiledWithoutCache = NativeModule.compiledWithoutCache = [];
|
|
const compiledWithCache = NativeModule.compiledWithCache = [];
|
|
|
|
// Think of this as module.exports in this file even though it is not
|
|
// written in CommonJS style.
|
|
const loaderExports = { internalBinding, NativeModule };
|
|
const loaderId = 'internal/bootstrap/loaders';
|
|
|
|
NativeModule.require = function(id) {
|
|
if (id === loaderId) {
|
|
return loaderExports;
|
|
}
|
|
|
|
const cached = NativeModule.getCached(id);
|
|
if (cached && (cached.loaded || cached.loading)) {
|
|
return cached.exports;
|
|
}
|
|
|
|
if (!NativeModule.exists(id)) {
|
|
// Model the error off the internal/errors.js model, but
|
|
// do not use that module given that it could actually be
|
|
// the one causing the error if there's a bug in Node.js
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
const err = new Error(`No such built-in module: ${id}`);
|
|
err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
|
|
err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]';
|
|
throw err;
|
|
}
|
|
|
|
moduleLoadList.push(`NativeModule ${id}`);
|
|
|
|
const nativeModule = new NativeModule(id);
|
|
|
|
nativeModule.cache();
|
|
nativeModule.compile();
|
|
|
|
return nativeModule.exports;
|
|
};
|
|
|
|
NativeModule.isDepsModule = function(id) {
|
|
return id.startsWith('node-inspect/') || id.startsWith('v8/');
|
|
};
|
|
|
|
NativeModule.requireForDeps = function(id) {
|
|
if (!NativeModule.exists(id) ||
|
|
// TODO(TimothyGu): remove when DEP0084 reaches end of life.
|
|
NativeModule.isDepsModule(id)) {
|
|
id = `internal/deps/${id}`;
|
|
}
|
|
return NativeModule.require(id);
|
|
};
|
|
|
|
NativeModule.getCached = function(id) {
|
|
return NativeModule._cache[id];
|
|
};
|
|
|
|
NativeModule.exists = function(id) {
|
|
return NativeModule._source.hasOwnProperty(id);
|
|
};
|
|
|
|
if (config.exposeInternals) {
|
|
NativeModule.nonInternalExists = function(id) {
|
|
// Do not expose this to user land even with --expose-internals
|
|
if (id === loaderId) {
|
|
return false;
|
|
}
|
|
return NativeModule.exists(id);
|
|
};
|
|
|
|
NativeModule.isInternal = function(id) {
|
|
// Do not expose this to user land even with --expose-internals
|
|
return id === loaderId;
|
|
};
|
|
} else {
|
|
NativeModule.nonInternalExists = function(id) {
|
|
return NativeModule.exists(id) && !NativeModule.isInternal(id);
|
|
};
|
|
|
|
NativeModule.isInternal = function(id) {
|
|
return id.startsWith('internal/') ||
|
|
(id === 'worker_threads' && !config.experimentalWorker);
|
|
};
|
|
}
|
|
|
|
NativeModule.getSource = function(id) {
|
|
return NativeModule._source[id];
|
|
};
|
|
|
|
NativeModule.wrap = function(script) {
|
|
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
|
|
};
|
|
|
|
NativeModule.wrapper = [
|
|
'(function (exports, require, module, process, internalBinding) {',
|
|
'\n});'
|
|
];
|
|
|
|
const getOwn = (target, property, receiver) => {
|
|
return ReflectApply(ObjectHasOwnProperty, target, [property]) ?
|
|
ReflectGet(target, property, receiver) :
|
|
undefined;
|
|
};
|
|
|
|
// Provide named exports for all builtin libraries so that the libraries
|
|
// may be imported in a nicer way for esm users. The default export is left
|
|
// 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);
|
|
|
|
const update = (property, value) => {
|
|
if (this.reflect !== undefined &&
|
|
ReflectApply(ObjectHasOwnProperty,
|
|
this.reflect.exports, [property]))
|
|
this.reflect.exports[property].set(value);
|
|
};
|
|
|
|
const handler = {
|
|
__proto__: null,
|
|
defineProperty: (target, prop, descriptor) => {
|
|
// Use `Object.defineProperty` instead of `Reflect.defineProperty`
|
|
// to throw the appropriate error if something goes wrong.
|
|
ObjectDefineProperty(target, prop, descriptor);
|
|
if (typeof descriptor.get === 'function' &&
|
|
!ReflectHas(handler, 'get')) {
|
|
handler.get = (target, prop, receiver) => {
|
|
const value = ReflectGet(target, prop, receiver);
|
|
if (ReflectApply(ObjectHasOwnProperty, target, [prop]))
|
|
update(prop, value);
|
|
return value;
|
|
};
|
|
}
|
|
update(prop, getOwn(target, prop));
|
|
return true;
|
|
},
|
|
deleteProperty: (target, prop) => {
|
|
if (ReflectDeleteProperty(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)) {
|
|
if (descriptor && typeof descriptor.set === 'function') {
|
|
for (const key of this.exportKeys) {
|
|
update(key, getOwn(target, key, receiver));
|
|
}
|
|
} else {
|
|
update(prop, getOwn(target, prop, receiver));
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
this.exports = new Proxy(this.exports, handler);
|
|
};
|
|
|
|
NativeModule.prototype.compile = function() {
|
|
const id = this.id;
|
|
let source = NativeModule.getSource(id);
|
|
source = NativeModule.wrap(source);
|
|
|
|
this.loading = true;
|
|
|
|
try {
|
|
// Currently V8 only checks that the length of the source code is the
|
|
// same as the code used to generate the hash, so we add an additional
|
|
// check here:
|
|
// 1. During compile time, when generating node_javascript.cc and
|
|
// node_code_cache.cc, we compute and include the hash of the
|
|
// (unwrapped) JavaScript source in both.
|
|
// 2. At runtime, we check that the hash of the code being compiled
|
|
// and the hash of the code used to generate the cache
|
|
// (inside the wrapper) is the same.
|
|
// This is based on the assumptions:
|
|
// 1. `internalBinding('code_cache_hash')` must be in sync with
|
|
// `internalBinding('code_cache')` (same C++ file)
|
|
// 2. `internalBinding('natives_hash')` must be in sync with
|
|
// `internalBinding('natives')` (same C++ file)
|
|
// 3. If `internalBinding('natives_hash')` is in sync with
|
|
// `internalBinding('natives_hash')`, then the (unwrapped)
|
|
// code used to generate `internalBinding('code_cache')`
|
|
// should be in sync with the (unwrapped) code in
|
|
// `internalBinding('natives')`
|
|
// There will be, however, false positives if the wrapper used
|
|
// to generate the cache is different from the one used at run time,
|
|
// and the length of the wrapper somehow stays the same.
|
|
// But that should be rare and can be eased once we make the
|
|
// two bootstrappers cached and checked as well.
|
|
const cache = codeCacheHash[id] &&
|
|
(codeCacheHash[id] === sourceHash[id]) ? codeCache[id] : undefined;
|
|
|
|
// (code, filename, lineOffset, columnOffset
|
|
// cachedData, produceCachedData, parsingContext)
|
|
const script = new ContextifyScript(
|
|
source, this.filename, 0, 0,
|
|
cache, false, undefined
|
|
);
|
|
|
|
// This will be used to create code cache in tools/generate_code_cache.js
|
|
this.script = script;
|
|
|
|
// One of these conditions may be false when any of the inputs
|
|
// of the `node_js2c` target in node.gyp is modified.
|
|
// FIXME(joyeecheung): Figure out how to resolve the dependency issue.
|
|
// When the code cache was introduced we were at a point where refactoring
|
|
// node.gyp may not be worth the effort.
|
|
if (!cache || script.cachedDataRejected) {
|
|
compiledWithoutCache.push(this.id);
|
|
} else {
|
|
compiledWithCache.push(this.id);
|
|
}
|
|
|
|
// Arguments: timeout, displayErrors, breakOnSigint
|
|
const fn = script.runInThisContext(-1, true, false);
|
|
const requireFn = this.id.startsWith('internal/deps/') ?
|
|
NativeModule.requireForDeps :
|
|
NativeModule.require;
|
|
fn(this.exports, requireFn, this, process, internalBinding);
|
|
|
|
if (config.experimentalModules && !NativeModule.isInternal(this.id)) {
|
|
this.proxifyExports();
|
|
}
|
|
|
|
this.loaded = true;
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
};
|
|
|
|
NativeModule.prototype.cache = function() {
|
|
NativeModule._cache[this.id] = this;
|
|
};
|
|
|
|
// coverage must be turned on early, so that we can collect
|
|
// it for Node.js' own internal libraries.
|
|
if (process.env.NODE_V8_COVERAGE) {
|
|
NativeModule.require('internal/process/coverage').setup();
|
|
}
|
|
|
|
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 the bootstrapNodeJSCore function in
|
|
// bootstrap/node.js.
|
|
return loaderExports;
|
|
});
|