node/lib/internal/bootstrap/node.js
Joyee Cheung fbc52e5729
src: disambiguate terms used to refer to builtins and addons
The term "native module" dates back to some of the oldest code
in the code base. Within the context of Node.js core it usually
refers to modules that are native to Node.js (e.g. fs, http),
but it can cause confusion for people who don't work on this
part of the code base, as "native module" can also refer to
native addons - which is even the case in some of the API
docs and error messages.

This patch tries to make the usage of these terms more consistent.
Now within the context of Node.js core:

- JavaScript scripts that are built-in to Node.js are now referred
  to as "built-in(s)". If they are available as modules,
  they can also be referred to as "built-in module(s)".
- Dynamically-linked shared objects that are loaded into
  the Node.js processes are referred to as "addons".

We will try to avoid using the term "native modules" because it could
be ambiguous.

Changes in this patch:

File names:
- node_native_module.h -> node_builtins.h,
- node_native_module.cc -> node_builtins.cc

C++ binding names:
- `native_module` -> `builtins`

`node::Environment`:
- `native_modules_without_cache` -> `builtins_without_cache`
- `native_modules_with_cache` -> `builtins_with_cache`
- `native_modules_in_snapshot` -> `builtins_in_cache`
- `native_module_require` -> `builtin_module_require`

`node::EnvSerializeInfo`:
- `native_modules` -> `builtins

`node::native_module::NativeModuleLoader`:
- `native_module` namespace -> `builtins` namespace
- `NativeModuleLoader` -> `BuiltinLoader`
- `NativeModuleRecordMap` -> `BuiltinSourceMap`
- `NativeModuleCacheMap` -> `BuiltinCodeCacheMap`
- `ModuleIds` -> `BuiltinIds`
- `ModuleCategories` -> `BuiltinCategories`
- `LoadBuiltinModuleSource` -> `LoadBuiltinSource`

`loader.js`:
- `NativeModule` -> `BuiltinModule` (the `NativeModule` name used in
  `process.moduleLoadList` is kept for compatibility)

And other clarifications in the documentation and comments.

PR-URL: https://github.com/nodejs/node/pull/44135
Backport-PR-URL: https://github.com/nodejs/node/pull/45663
Fixes: https://github.com/nodejs/node/issues/44036
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
2022-12-08 09:55:24 -05:00

573 lines
18 KiB
JavaScript

// Hello, and welcome to hacking node.js!
//
// This file is invoked by `Realm::BootstrapNode()` in `src/node_realm.cc`,
// and is responsible for setting up Node.js core before main scripts
// under `lib/internal/main/` are executed.
//
// By default, Node.js binaries come with an embedded V8 startup snapshot
// that is generated at build-time with a `node_mksnapshot` executable.
// The snapshot generation code can be found in `SnapshotBuilder::Generate()`
// from `src/node_snapshotable.cc`.
// This snapshot captures the V8 heap initialized by scripts under
// `lib/internal/bootstrap/`, including this file. When initializing the main
// thread, Node.js deserializes the heap from the snapshot, instead of actually
// running this script and others in `lib/internal/bootstrap/`. To disable this
// behavior, pass `--no-node-snapshot` when starting the process so that
// Node.js actually runs this script to initialize the heap.
//
// This script is expected not to perform any asynchronous operations itself
// when being executed - those should be done in either
// `lib/internal/process/pre_execution.js` or in main scripts. It should not
// query any run-time states (e.g. command line arguments, environment
// variables) when being executed - functions in this script that are invoked
// at a later time can, however, query those states lazily.
// The majority of the code here focuses on setting up the global object and
// the process object in a synchronous, environment-independent manner.
//
// Scripts run before this file:
// - `lib/internal/per_context/primordials.js`: this saves copies of JavaScript
// builtins that won't be affected by user land monkey-patching for internal
// modules to use.
// - `lib/internal/per_context/domexception.js`: implementation of the
// `DOMException` class.
// - `lib/internal/per_context/messageport.js`: JS-side components of the
// `MessagePort` implementation.
// - `lib/internal/bootstrap/loaders.js`: this sets up internal binding and
// module loaders, including `process.binding()`, `process._linkedBinding()`,
// `internalBinding()` and `BuiltinModule`.
//
// The initialization done in this script is included in both the main thread
// and the worker threads. After this, further initialization is done based
// on the configuration of the Node.js instance by executing the scripts in
// `lib/internal/bootstrap/switches/`.
//
// Then, depending on how the Node.js instance is launched, one of the main
// scripts in `lib/internal/main` will be selected by C++ to start the actual
// execution. They may run additional setups exported by
// `lib/internal/process/pre_execution.js` depending on the run-time states.
'use strict';
// This file is compiled as if it's wrapped in a function with arguments
// passed by `BuiltinLoader::CompileAndCall()`.
/* global process, require, internalBinding, primordials */
setupPrepareStackTrace();
const {
Array,
ArrayPrototypeConcat,
ArrayPrototypeFill,
FunctionPrototypeCall,
JSONParse,
ObjectDefineProperty,
ObjectDefineProperties,
ObjectGetPrototypeOf,
ObjectPreventExtensions,
ObjectSetPrototypeOf,
ReflectGet,
ReflectSet,
SymbolToStringTag,
globalThis,
} = primordials;
const config = internalBinding('config');
const internalTimers = require('internal/timers');
const { deprecate } = require('internal/util');
const {
exiting_aliased_Uint32Array,
getHiddenValue,
} = internalBinding('util');
setupProcessObject();
setupGlobalProxy();
setupBuffer();
process.domain = null;
{
const exitingAliasedUint32Array =
getHiddenValue(process, exiting_aliased_Uint32Array);
ObjectDefineProperty(process, '_exiting', {
__proto__: null,
get() {
return exitingAliasedUint32Array[0] === 1;
},
set(value) {
exitingAliasedUint32Array[0] = value ? 1 : 0;
},
enumerable: true,
configurable: true,
});
}
process._exiting = false;
// process.config is serialized config.gypi
const nativeModule = internalBinding('builtins');
// TODO(@jasnell): Once this has gone through one full major
// release cycle, remove the Proxy and setter and update the
// getter to either return a read-only object or always return
// a freshly parsed version of nativeModule.config.
const deprecationHandler = {
warned: false,
message: 'Setting process.config is deprecated. ' +
'In the future the property will be read-only.',
code: 'DEP0150',
maybeWarn() {
if (!this.warned) {
process.emitWarning(this.message, {
type: 'DeprecationWarning',
code: this.code
});
this.warned = true;
}
},
defineProperty(target, key, descriptor) {
this.maybeWarn();
return ObjectDefineProperty(target, key, descriptor);
},
deleteProperty(target, key) {
this.maybeWarn();
delete target[key];
},
preventExtensions(target) {
this.maybeWarn();
return ObjectPreventExtensions(target);
},
set(target, key, value) {
this.maybeWarn();
return ReflectSet(target, key, value);
},
get(target, key, receiver) {
const val = ReflectGet(target, key, receiver);
if (val != null && typeof val === 'object') {
// eslint-disable-next-line node-core/prefer-primordials
return new Proxy(val, deprecationHandler);
}
return val;
},
setPrototypeOf(target, proto) {
this.maybeWarn();
return ObjectSetPrototypeOf(target, proto);
}
};
// eslint-disable-next-line node-core/prefer-primordials
let processConfig = new Proxy(
JSONParse(nativeModule.config),
deprecationHandler);
ObjectDefineProperty(process, 'config', {
__proto__: null,
enumerable: true,
configurable: true,
get() { return processConfig; },
set(value) {
deprecationHandler.maybeWarn();
processConfig = value;
}
});
require('internal/worker/js_transferable').setup();
// Bootstrappers for all threads, including worker threads and main thread
const perThreadSetup = require('internal/process/per_thread');
const rawMethods = internalBinding('process_methods');
// Set up methods on the process object for all threads
{
process.dlopen = rawMethods.dlopen;
process.uptime = rawMethods.uptime;
// TODO(joyeecheung): either remove them or make them public
process._getActiveRequests = rawMethods._getActiveRequests;
process._getActiveHandles = rawMethods._getActiveHandles;
process.getActiveResourcesInfo = function() {
const timerCounts = internalTimers.getTimerCounts();
return ArrayPrototypeConcat(
rawMethods._getActiveRequestsInfo(),
rawMethods._getActiveHandlesInfo(),
ArrayPrototypeFill(new Array(timerCounts.timeoutCount), 'Timeout'),
ArrayPrototypeFill(new Array(timerCounts.immediateCount), 'Immediate'));
};
// TODO(joyeecheung): remove these
process.reallyExit = rawMethods.reallyExit;
process._kill = rawMethods._kill;
const wrapped = perThreadSetup.wrapProcessMethods(rawMethods);
process._rawDebug = wrapped._rawDebug;
process.cpuUsage = wrapped.cpuUsage;
process.resourceUsage = wrapped.resourceUsage;
process.memoryUsage = wrapped.memoryUsage;
process.kill = wrapped.kill;
process.exit = wrapped.exit;
process.hrtime = perThreadSetup.hrtime;
process.hrtime.bigint = perThreadSetup.hrtimeBigInt;
process.openStdin = function() {
process.stdin.resume();
return process.stdin;
};
}
const credentials = internalBinding('credentials');
if (credentials.implementsPosixCredentials) {
process.getuid = credentials.getuid;
process.geteuid = credentials.geteuid;
process.getgid = credentials.getgid;
process.getegid = credentials.getegid;
process.getgroups = credentials.getgroups;
}
// Setup the callbacks that node::AsyncWrap will call when there are hooks to
// process. They use the same functions as the JS embedder API. These callbacks
// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
// and the cost of doing so is negligible.
const { nativeHooks } = require('internal/async_hooks');
internalBinding('async_wrap').setupHooks(nativeHooks);
const {
setupTaskQueue,
queueMicrotask
} = require('internal/process/task_queues');
if (!config.noBrowserGlobals) {
// Override global console from the one provided by the VM
// to the one implemented by Node.js
// https://console.spec.whatwg.org/#console-namespace
exposeNamespace(globalThis, 'console',
createGlobalConsole(globalThis.console));
const { URL, URLSearchParams } = require('internal/url');
// https://url.spec.whatwg.org/#url
exposeInterface(globalThis, 'URL', URL);
// https://url.spec.whatwg.org/#urlsearchparams
exposeInterface(globalThis, 'URLSearchParams', URLSearchParams);
const {
TextEncoder, TextDecoder
} = require('internal/encoding');
// https://encoding.spec.whatwg.org/#textencoder
exposeInterface(globalThis, 'TextEncoder', TextEncoder);
// https://encoding.spec.whatwg.org/#textdecoder
exposeInterface(globalThis, 'TextDecoder', TextDecoder);
const {
AbortController,
AbortSignal,
} = require('internal/abort_controller');
exposeInterface(globalThis, 'AbortController', AbortController);
exposeInterface(globalThis, 'AbortSignal', AbortSignal);
const {
EventTarget,
Event,
} = require('internal/event_target');
exposeInterface(globalThis, 'EventTarget', EventTarget);
exposeInterface(globalThis, 'Event', Event);
const {
MessageChannel,
MessagePort,
MessageEvent,
} = require('internal/worker/io');
exposeInterface(globalThis, 'MessageChannel', MessageChannel);
exposeInterface(globalThis, 'MessagePort', MessagePort);
exposeInterface(globalThis, 'MessageEvent', MessageEvent);
// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope
const timers = require('timers');
defineOperation(globalThis, 'clearInterval', timers.clearInterval);
defineOperation(globalThis, 'clearTimeout', timers.clearTimeout);
defineOperation(globalThis, 'setInterval', timers.setInterval);
defineOperation(globalThis, 'setTimeout', timers.setTimeout);
defineOperation(globalThis, 'queueMicrotask', queueMicrotask);
// https://www.w3.org/TR/hr-time-2/#the-performance-attribute
defineReplacableAttribute(globalThis, 'performance',
require('perf_hooks').performance);
// Non-standard extensions:
defineOperation(globalThis, 'clearImmediate', timers.clearImmediate);
defineOperation(globalThis, 'setImmediate', timers.setImmediate);
}
// Set the per-Environment callback that will be called
// when the TrackingTraceStateObserver updates trace state.
// Note that when NODE_USE_V8_PLATFORM is true, the observer is
// attached to the per-process TracingController.
const { setTraceCategoryStateUpdateHandler } = internalBinding('trace_events');
setTraceCategoryStateUpdateHandler(perThreadSetup.toggleTraceCategoryState);
// process.allowedNodeEnvironmentFlags
ObjectDefineProperty(process, 'allowedNodeEnvironmentFlags', {
__proto__: null,
get() {
const flags = perThreadSetup.buildAllowedFlags();
process.allowedNodeEnvironmentFlags = flags;
return process.allowedNodeEnvironmentFlags;
},
// If the user tries to set this to another value, override
// this completely to that value.
set(value) {
ObjectDefineProperty(this, 'allowedNodeEnvironmentFlags', {
__proto__: null,
value,
configurable: true,
enumerable: true,
writable: true
});
},
enumerable: true,
configurable: true
});
// process.assert
process.assert = deprecate(
perThreadSetup.assert,
'process.assert() is deprecated. Please use the `assert` module instead.',
'DEP0100');
// TODO(joyeecheung): this property has not been well-maintained, should we
// deprecate it in favor of a better API?
const { isDebugBuild, hasOpenSSL, hasInspector } = config;
const features = {
inspector: hasInspector,
debug: isDebugBuild,
uv: true,
ipv6: true, // TODO(bnoordhuis) ping libuv
tls_alpn: hasOpenSSL,
tls_sni: hasOpenSSL,
tls_ocsp: hasOpenSSL,
tls: hasOpenSSL,
// This needs to be dynamic because snapshot is built without code cache.
// TODO(joyeecheung): https://github.com/nodejs/node/issues/31074
// Make it possible to build snapshot with code cache
get cached_builtins() {
return nativeModule.hasCachedBuiltins();
}
};
ObjectDefineProperty(process, 'features', {
__proto__: null,
enumerable: true,
writable: false,
configurable: false,
value: features
});
{
const {
onGlobalUncaughtException,
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
} = require('internal/process/execution');
// For legacy reasons this is still called `_fatalException`, even
// though it is now a global uncaught exception handler.
// The C++ land node::errors::TriggerUncaughtException grabs it
// from the process object because it has been monkey-patchable.
// TODO(joyeecheung): investigate whether process._fatalException
// can be deprecated.
process._fatalException = onGlobalUncaughtException;
process.setUncaughtExceptionCaptureCallback =
setUncaughtExceptionCaptureCallback;
process.hasUncaughtExceptionCaptureCallback =
hasUncaughtExceptionCaptureCallback;
}
const { emitWarning } = require('internal/process/warning');
process.emitWarning = emitWarning;
// We initialize the tick callbacks and the timer callbacks last during
// bootstrap to make sure that any operation done before this are synchronous.
// If any ticks or timers are scheduled before this they are unlikely to work.
{
const { nextTick, runNextTicks } = setupTaskQueue();
process.nextTick = nextTick;
// Used to emulate a tick manually in the JS land.
// A better name for this function would be `runNextTicks` but
// it has been exposed to the process object so we keep this legacy name
// TODO(joyeecheung): either remove it or make it public
process._tickCallback = runNextTicks;
const { setupTimers } = internalBinding('timers');
const {
processImmediate,
processTimers,
} = internalTimers.getTimerCallbacks(runNextTicks);
// Sets two per-Environment callbacks that will be run from libuv:
// - processImmediate will be run in the callback of the per-Environment
// check handle.
// - processTimers will be run in the callback of the per-Environment timer.
setupTimers(processImmediate, processTimers);
// Note: only after this point are the timers effective
}
// Preload modules so that they are included in the builtin snapshot.
require('fs');
require('v8');
require('vm');
require('url');
require('internal/options');
function setupPrepareStackTrace() {
const {
setEnhanceStackForFatalException,
setPrepareStackTraceCallback
} = internalBinding('errors');
const {
prepareStackTrace,
fatalExceptionStackEnhancers: {
beforeInspector,
afterInspector
}
} = require('internal/errors');
// Tell our PrepareStackTraceCallback passed to the V8 API
// to call prepareStackTrace().
setPrepareStackTraceCallback(prepareStackTrace);
// Set the function used to enhance the error stack for printing
setEnhanceStackForFatalException(beforeInspector, afterInspector);
}
function setupProcessObject() {
const EventEmitter = require('events');
const origProcProto = ObjectGetPrototypeOf(process);
ObjectSetPrototypeOf(origProcProto, EventEmitter.prototype);
FunctionPrototypeCall(EventEmitter, process);
ObjectDefineProperty(process, SymbolToStringTag, {
__proto__: null,
enumerable: false,
writable: true,
configurable: false,
value: 'process'
});
// Make process globally available to users by putting it on the global proxy
ObjectDefineProperty(globalThis, 'process', {
__proto__: null,
value: process,
enumerable: false,
writable: true,
configurable: true
});
}
function setupGlobalProxy() {
ObjectDefineProperty(globalThis, SymbolToStringTag, {
__proto__: null,
value: 'global',
writable: false,
enumerable: false,
configurable: true
});
globalThis.global = globalThis;
}
function setupBuffer() {
const {
Buffer,
atob,
btoa,
} = require('buffer');
const bufferBinding = internalBinding('buffer');
// Only after this point can C++ use Buffer::New()
bufferBinding.setBufferPrototype(Buffer.prototype);
delete bufferBinding.setBufferPrototype;
delete bufferBinding.zeroFill;
ObjectDefineProperties(globalThis, {
'Buffer': {
__proto__: null,
value: Buffer,
enumerable: false,
writable: true,
configurable: true,
},
'atob': {
__proto__: null,
value: atob,
enumerable: false,
writable: true,
configurable: true,
},
'btoa': {
__proto__: null,
value: btoa,
enumerable: false,
writable: true,
configurable: true,
},
});
}
function createGlobalConsole(consoleFromVM) {
const consoleFromNode =
require('internal/console/global');
if (config.hasInspector) {
const inspector = require('internal/util/inspector');
// This will be exposed by `require('inspector').console` later.
inspector.consoleFromVM = consoleFromVM;
// TODO(joyeecheung): postpone this until the first time inspector
// is activated.
inspector.wrapConsole(consoleFromNode, consoleFromVM);
const { setConsoleExtensionInstaller } = internalBinding('inspector');
// Setup inspector command line API.
setConsoleExtensionInstaller(inspector.installConsoleExtensions);
}
return consoleFromNode;
}
// https://heycam.github.io/webidl/#es-namespaces
function exposeNamespace(target, name, namespaceObject) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: false,
configurable: true,
value: namespaceObject
});
}
// https://heycam.github.io/webidl/#es-interfaces
function exposeInterface(target, name, interfaceObject) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: false,
configurable: true,
value: interfaceObject
});
}
// https://heycam.github.io/webidl/#define-the-operations
function defineOperation(target, name, method) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: true,
configurable: true,
value: method
});
}
// https://heycam.github.io/webidl/#Replaceable
function defineReplacableAttribute(target, name, value) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: true,
configurable: true,
value,
});
}