mirror of
https://github.com/nodejs/node.git
synced 2026-10-29 02:35:20 +08:00
Signed-off-by: Marco Ippolito <marcoippolito54@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66431 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com>
889 lines
30 KiB
JavaScript
889 lines
30 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayPrototypeFilter,
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeJoin,
|
|
ArrayPrototypeSplice,
|
|
Date,
|
|
DatePrototypeGetDate,
|
|
DatePrototypeGetFullYear,
|
|
DatePrototypeGetHours,
|
|
DatePrototypeGetMinutes,
|
|
DatePrototypeGetMonth,
|
|
DatePrototypeGetSeconds,
|
|
NumberParseInt,
|
|
ObjectDefineProperty,
|
|
ObjectFreeze,
|
|
String,
|
|
globalThis,
|
|
} = primordials;
|
|
|
|
const {
|
|
getOptionValue,
|
|
refreshOptions,
|
|
getEmbedderOptions,
|
|
} = require('internal/options');
|
|
const {
|
|
exposeLazyInterfaces,
|
|
defineReplaceableLazyAttribute,
|
|
setupCoverageHooks,
|
|
emitExperimentalWarning,
|
|
deprecate,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
ERR_MISSING_OPTION,
|
|
ERR_ACCESS_DENIED,
|
|
ERR_VFS_INVALID_TARGET,
|
|
} = require('internal/errors').codes;
|
|
const assert = require('internal/assert');
|
|
const {
|
|
namespace: {
|
|
addSerializeCallback,
|
|
isBuildingSnapshot,
|
|
},
|
|
runDeserializeCallbacks,
|
|
} = require('internal/v8/startup_snapshot');
|
|
|
|
function prepareMainThreadExecution(expandArgv1 = false, initializeModules = true) {
|
|
return prepareExecution({
|
|
expandArgv1,
|
|
initializeModules,
|
|
isMainThread: true,
|
|
shouldSpawnLoaderHookWorker: initializeModules,
|
|
shouldPreloadModules: initializeModules,
|
|
});
|
|
}
|
|
|
|
function prepareTestRunnerMainExecution(loadUserModules = true) {
|
|
return prepareExecution({
|
|
expandArgv1: false,
|
|
initializeModules: true,
|
|
isMainThread: true,
|
|
shouldSpawnLoaderHookWorker: loadUserModules,
|
|
shouldPreloadModules: loadUserModules,
|
|
});
|
|
}
|
|
|
|
function prepareWorkerThreadExecution() {
|
|
prepareExecution({
|
|
expandArgv1: false,
|
|
isMainThread: false,
|
|
// Module loader initialization in workers are delayed until the worker thread
|
|
// is ready for execution.
|
|
initializeModules: false,
|
|
shouldSpawnLoaderHookWorker: false,
|
|
shouldPreloadModules: false,
|
|
});
|
|
}
|
|
|
|
function prepareShadowRealmExecution() {
|
|
// Patch the process object with legacy properties and normalizations.
|
|
// Do not expand argv1 as it is not available in ShadowRealm.
|
|
patchProcessObject(false);
|
|
setupDebugEnv();
|
|
|
|
// Disable custom loaders in ShadowRealm.
|
|
initializeModuleLoaders({ shouldSpawnLoaderHookWorker: false, shouldPreloadModules: false });
|
|
const {
|
|
privateSymbols: {
|
|
host_defined_option_symbol,
|
|
},
|
|
} = internalBinding('util');
|
|
const {
|
|
vm_dynamic_import_default_internal,
|
|
} = internalBinding('symbols');
|
|
|
|
// For ShadowRealm.prototype.importValue(), the referrer name is
|
|
// always null, so the native ImportModuleDynamically() callback would
|
|
// always fallback to look up the host-defined option from the
|
|
// global object using host_defined_option_symbol. Using
|
|
// vm_dynamic_import_default_internal as the host-defined option
|
|
// instructs the JS-land importModuleDynamicallyCallback() to
|
|
// proxy the request to defaultImportModuleDynamically().
|
|
globalThis[host_defined_option_symbol] =
|
|
vm_dynamic_import_default_internal;
|
|
}
|
|
|
|
function prepareExecution(options) {
|
|
const { expandArgv1, initializeModules, isMainThread, shouldSpawnLoaderHookWorker, shouldPreloadModules } = options;
|
|
|
|
refreshRuntimeOptions();
|
|
|
|
// Patch the process object and get the resolved main entry point.
|
|
const mainEntry = patchProcessObject(expandArgv1);
|
|
setupTraceCategoryState();
|
|
setupInspectorHooks();
|
|
setupNetworkInspection();
|
|
setupNavigator();
|
|
setupWarningHandler();
|
|
setupOptionGatedBuiltins();
|
|
setupWebStorage();
|
|
removeWebWorkersIfDisabled();
|
|
setupEventsource();
|
|
setupCodeCoverage();
|
|
setupDebugEnv();
|
|
// Process initial diagnostic reporting configuration, if present.
|
|
initializeReport();
|
|
|
|
setupDiagnosticsChannel(isMainThread);
|
|
|
|
// Load permission system API
|
|
initializePermission();
|
|
|
|
initializeSourceMapsHandlers();
|
|
initializeDeprecations();
|
|
|
|
// internal/dns/utils (and internal/net behind it) is only needed up front
|
|
// to validate an explicit --dns-result-order or to register the resolver's
|
|
// snapshot serialization; otherwise it is loaded with node:dns.
|
|
if (getOptionValue('--dns-result-order') || isBuildingSnapshot()) {
|
|
require('internal/dns/utils').initializeDns();
|
|
}
|
|
|
|
if (isMainThread) {
|
|
assert(internalBinding('worker').isMainThread);
|
|
// Worker threads will get the manifest in the message handler.
|
|
|
|
// Print stack trace on `SIGINT` if option `--trace-sigint` presents.
|
|
setupStacktracePrinterOnSigint();
|
|
initializeReportSignalHandlers(); // Main-thread-only.
|
|
initializeHeapSnapshotSignalHandlers();
|
|
// If the process is spawned with env NODE_CHANNEL_FD, it's probably
|
|
// spawned by our child_process module, then initialize IPC.
|
|
// This attaches some internal event listeners and creates:
|
|
// process.send(), process.channel, process.connected,
|
|
// process.disconnect().
|
|
setupChildProcessIpcChannel();
|
|
// If this is a worker in cluster mode, start up the communication
|
|
// channel. This needs to be done before any user code gets executed
|
|
// (including preload modules).
|
|
initializeClusterIPC();
|
|
} else {
|
|
assert(!internalBinding('worker').isMainThread);
|
|
// The setup should be called in LOAD_SCRIPT message handler.
|
|
assert(!initializeModules);
|
|
}
|
|
runDeserializeCallbacks();
|
|
|
|
const { initializeExtensionFormatMap } = require('internal/modules/esm/get_format');
|
|
initializeExtensionFormatMap();
|
|
|
|
setupVmModules();
|
|
if (initializeModules) {
|
|
initializeModuleLoaders({ shouldSpawnLoaderHookWorker, shouldPreloadModules });
|
|
}
|
|
|
|
// Mount here only when there is no --import: those preloads run later (inside
|
|
// run_main's ESM entry flow), and a preload may registerProvider() before a
|
|
// target's provider is chosen, so with --import the mount is deferred to after
|
|
// that loop via finishVfsMounts(), which is idempotent.
|
|
if (isMainThread && getOptionValue('--import').length === 0) {
|
|
finishVfsMounts();
|
|
}
|
|
|
|
// This has to be done after the user module loader is initialized,
|
|
// in case undici is externalized.
|
|
setupHttpProxy();
|
|
|
|
return mainEntry;
|
|
}
|
|
|
|
function setupVmModules() {
|
|
// Patch the vm module when --experimental-vm-modules is on.
|
|
// Please update the comments in vm.js when this block changes.
|
|
// TODO(joyeecheung): move this to vm.js?
|
|
if (getOptionValue('--experimental-vm-modules')) {
|
|
const {
|
|
Module, SourceTextModule, SyntheticModule,
|
|
} = require('internal/vm/module');
|
|
const vm = require('vm');
|
|
vm.Module = Module;
|
|
vm.SourceTextModule = SourceTextModule;
|
|
vm.SyntheticModule = SyntheticModule;
|
|
}
|
|
}
|
|
|
|
let vfsMounted = false;
|
|
let vfsLoadRoot;
|
|
|
|
// Mounts the --vfs-load source. Called from prepareExecution() when there is
|
|
// no --import, and otherwise from run_main after the --import loop has run; the
|
|
// guard makes the second call a no-op so a provider registered by either a -r
|
|
// or an --import preload is available before the source's provider is chosen.
|
|
function finishVfsMounts() {
|
|
if (vfsMounted) return;
|
|
vfsMounted = true;
|
|
|
|
const source = getOptionValue('--vfs-load');
|
|
if (source === '') return;
|
|
emitExperimentalWarning('--vfs-load');
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { selectProvider } = require('internal/vfs/provider_registry');
|
|
const { VirtualFileSystem, kLoadLayer } = require('internal/vfs/file_system');
|
|
|
|
const resolvedSource = path.resolve(source);
|
|
let stats;
|
|
try {
|
|
stats = fs.statSync(resolvedSource);
|
|
} catch {
|
|
throw new ERR_VFS_INVALID_TARGET(resolvedSource);
|
|
}
|
|
if (!stats.isDirectory() && !stats.isFile()) {
|
|
throw new ERR_VFS_INVALID_TARGET(resolvedSource);
|
|
}
|
|
const provider = selectProvider(resolvedSource, stats);
|
|
if (provider === null) {
|
|
throw new ERR_VFS_INVALID_TARGET(resolvedSource);
|
|
}
|
|
// The source is mounted at the layer reserved for it, so it is at the same
|
|
// reserved path in every thread.
|
|
const vfs = new VirtualFileSystem(provider, {
|
|
__proto__: null,
|
|
emitExperimentalWarning: false,
|
|
[kLoadLayer]: true,
|
|
});
|
|
const mountPoint = vfs.mount();
|
|
// A worker inherits the source but not [vfs_load_set] (see node_worker.cc):
|
|
// it mounts the same source, and so reaches it at the same reserved path,
|
|
// but runs its own entry point.
|
|
//
|
|
// On the main thread the entry is require()d from the mount point, while
|
|
// process.argv[1] reports the source, since the reserved mount point is an
|
|
// opaque implementation detail. The source is spliced in rather than
|
|
// assigned over argv[1]: the entry comes from the mount, so nothing was
|
|
// consumed as an entry point and the first positional argument is the
|
|
// program's own. Overwriting would drop it.
|
|
if (getOptionValue('[vfs_load_set]')) {
|
|
vfsLoadRoot = mountPoint;
|
|
ArrayPrototypeSplice(process.argv, 1, 0, resolvedSource);
|
|
}
|
|
}
|
|
|
|
// The reserved mount point of the --vfs-load entry, available once
|
|
// finishVfsMounts() has run. run_main require()s the entry from here.
|
|
function getVfsLoadRoot() {
|
|
return vfsLoadRoot;
|
|
}
|
|
|
|
function setupHttpProxy() {
|
|
// This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings.
|
|
if (!getOptionValue('--use-env-proxy')) {
|
|
return;
|
|
}
|
|
// Before reading the variables below, which would warn about each removed
|
|
// one without saying that it disables the proxy settings.
|
|
warnAboutRemovedProxyEnvVars();
|
|
if (!process.env.HTTP_PROXY && !process.env.HTTPS_PROXY &&
|
|
!process.env.http_proxy && !process.env.https_proxy) {
|
|
return;
|
|
}
|
|
|
|
const { setGlobalDispatcher, EnvHttpProxyAgent } = require('internal/deps/undici/undici');
|
|
const envHttpProxyAgent = new EnvHttpProxyAgent();
|
|
setGlobalDispatcher(envHttpProxyAgent);
|
|
// For fetch, we need to set the global dispatcher from here.
|
|
// For http/https agents, we'll configure the global agent when they are
|
|
// actually created, in lib/_http_agent.js and lib/https.js.
|
|
// TODO(joyeecheung): This is currently guarded with NODE_USE_ENV_PROXY and --use-env-proxy.
|
|
// Investigate whether it's possible to enable it by default without stepping on other
|
|
// existing libraries that sets the global dispatcher or monkey patches the global agent.
|
|
}
|
|
|
|
// Enforcing the permission model removes the environment variables that
|
|
// --allow-env does not grant access to at startup. The proxy variables are not
|
|
// among the variables it keeps by default, because their values may contain
|
|
// credentials, so --use-env-proxy silently stops applying them unless they
|
|
// are allowed.
|
|
function warnAboutRemovedProxyEnvVars() {
|
|
const permission = require('internal/process/permission');
|
|
if (!permission.isEnabled() || permission.isAuditMode()) {
|
|
return;
|
|
}
|
|
const { takeRemovedEnvVarWarning } = internalBinding('permission');
|
|
const removed = ArrayPrototypeFilter([
|
|
'HTTP_PROXY', 'http_proxy',
|
|
'HTTPS_PROXY', 'https_proxy',
|
|
'NO_PROXY', 'no_proxy',
|
|
], (name) => takeRemovedEnvVarWarning(name));
|
|
if (removed.length === 0) {
|
|
return;
|
|
}
|
|
process.emitWarning(
|
|
`--use-env-proxy is enabled, but the permission model removed ${ArrayPrototypeJoin(removed, ', ')} ` +
|
|
'from the environment at startup, so the proxy settings they define are not applied. ' +
|
|
'Use --allow-env to grant access to them.');
|
|
}
|
|
|
|
function initializeModuleLoaders(options) {
|
|
const { shouldSpawnLoaderHookWorker, shouldPreloadModules } = options;
|
|
// Initialize certain special module.Module properties and the CJS conditions.
|
|
const { initializeCJS } = require('internal/modules/cjs/loader');
|
|
initializeCJS();
|
|
// Initialize the ESM loader and a few module callbacks.
|
|
// If shouldSpawnLoaderHookWorker is true, later when the ESM loader is instantiated on-demand,
|
|
// it will spawn a loader worker thread to handle async custom loader hooks.
|
|
const { initializeESM } = require('internal/modules/esm/utils');
|
|
initializeESM(shouldSpawnLoaderHookWorker);
|
|
|
|
const {
|
|
hasStartedUserCJSExecution,
|
|
hasStartedUserESMExecution,
|
|
} = require('internal/modules/helpers');
|
|
// At this point, no user module has been executed yet.
|
|
assert(!hasStartedUserCJSExecution());
|
|
assert(!hasStartedUserESMExecution());
|
|
|
|
if (getEmbedderOptions().hasEmbedderPreload) {
|
|
runEmbedderPreload();
|
|
}
|
|
// Do not enable preload modules if custom loaders are disabled.
|
|
// For example, loader workers are responsible for doing this themselves.
|
|
// And preload modules are not supported in ShadowRealm as well.
|
|
if (shouldPreloadModules) {
|
|
loadPreloadModules();
|
|
}
|
|
// Need to be done after --require setup.
|
|
initializeFrozenIntrinsics();
|
|
}
|
|
|
|
function refreshRuntimeOptions() {
|
|
refreshOptions();
|
|
}
|
|
|
|
/**
|
|
* Patch the process object with legacy properties and normalizations.
|
|
* Replace `process.argv[0]` with `process.execPath`, preserving the original `argv[0]` value as `process.argv0`.
|
|
* Replace `process.argv[1]` with the resolved absolute file path of the entry point, if found.
|
|
* @param {boolean} expandArgv1 - Whether to replace `process.argv[1]` with the resolved absolute file path of
|
|
* the main entry point.
|
|
* @returns {string}
|
|
*/
|
|
function patchProcessObject(expandArgv1) {
|
|
const binding = internalBinding('process_methods');
|
|
binding.patchProcessObject(process);
|
|
|
|
// Since we replace process.argv[0] below, preserve the original value in case the user needs it.
|
|
ObjectDefineProperty(process, 'argv0', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
// Only set it to true during snapshot building.
|
|
configurable: isBuildingSnapshot(),
|
|
value: process.argv[0],
|
|
});
|
|
|
|
process.exitCode = undefined;
|
|
process._exiting = false;
|
|
process.argv[0] = process.execPath;
|
|
|
|
/** @type {string} */
|
|
let mainEntry;
|
|
// If requested, update process.argv[1] to replace whatever the user provided with the resolved absolute file path of
|
|
// the entry point.
|
|
// Under --vfs-load the entry point comes from the mount, so no positional
|
|
// argument was consumed as one: argv[1] is the program's own first argument
|
|
// and expanding it to a path would corrupt it.
|
|
if (expandArgv1 && !getOptionValue('[vfs_load_set]') &&
|
|
process.argv[1] && process.argv[1][0] !== '-') {
|
|
// Expand process.argv[1] into a full path.
|
|
const path = require('path');
|
|
try {
|
|
mainEntry = path.resolve(process.argv[1]);
|
|
process.argv[1] = mainEntry;
|
|
} catch {
|
|
// Continue regardless of error.
|
|
}
|
|
}
|
|
|
|
// We need to initialize the global console here again with process.stdout
|
|
// and friends for snapshot deserialization.
|
|
const globalConsole = require('internal/console/global');
|
|
const { initializeGlobalConsole } = require('internal/console/constructor');
|
|
initializeGlobalConsole(globalConsole);
|
|
|
|
// TODO(joyeecheung): most of these should be deprecated and removed,
|
|
// except some that we need to be able to mutate during run time.
|
|
addReadOnlyProcessAlias('_eval', '--eval');
|
|
addReadOnlyProcessAlias('_print_eval', '--print');
|
|
addReadOnlyProcessAlias('_syntax_check_only', '--check');
|
|
addReadOnlyProcessAlias('_forceRepl', '--interactive');
|
|
addReadOnlyProcessAlias('_preload_modules', '--require');
|
|
addReadOnlyProcessAlias('noDeprecation', '--no-deprecation');
|
|
addReadOnlyProcessAlias('noProcessWarnings', '--no-warnings');
|
|
addReadOnlyProcessAlias('traceProcessWarnings', '--trace-warnings');
|
|
addReadOnlyProcessAlias('throwDeprecation', '--throw-deprecation');
|
|
addReadOnlyProcessAlias('profProcess', '--prof-process');
|
|
addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation');
|
|
addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false);
|
|
addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false);
|
|
|
|
return mainEntry;
|
|
}
|
|
|
|
function addReadOnlyProcessAlias(name, option, enumerable = true) {
|
|
const value = getOptionValue(option);
|
|
if (value) {
|
|
ObjectDefineProperty(process, name, {
|
|
__proto__: null,
|
|
writable: false,
|
|
configurable: true,
|
|
enumerable,
|
|
value,
|
|
});
|
|
}
|
|
}
|
|
|
|
function setupWarningHandler() {
|
|
const {
|
|
onWarning,
|
|
resetForSerialization,
|
|
} = require('internal/process/warning');
|
|
if (getOptionValue('--warnings') &&
|
|
process.env.NODE_NO_WARNINGS !== '1') {
|
|
process.on('warning', onWarning);
|
|
|
|
// The code above would add the listener back during deserialization,
|
|
// if applicable.
|
|
if (isBuildingSnapshot()) {
|
|
addSerializeCallback(() => {
|
|
process.removeListener('warning', onWarning);
|
|
resetForSerialization();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/workers.html
|
|
function removeWebWorkersIfDisabled() {
|
|
if (!getOptionValue('--experimental-web-worker')) {
|
|
delete globalThis.Worker;
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
function setupEventsource() {
|
|
if (!getOptionValue('--experimental-eventsource')) {
|
|
delete globalThis.EventSource;
|
|
}
|
|
}
|
|
|
|
// TODO(aduh95): move this to internal/bootstrap/web/* when the CLI flag is
|
|
// removed.
|
|
function setupNavigator() {
|
|
if (getEmbedderOptions().noBrowserGlobals ||
|
|
getOptionValue('--no-experimental-global-navigator')) {
|
|
return;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
|
|
exposeLazyInterfaces(globalThis, 'internal/navigator', ['Navigator']);
|
|
defineReplaceableLazyAttribute(globalThis, 'internal/navigator', ['navigator'], false);
|
|
}
|
|
|
|
function setupOptionGatedBuiltins() {
|
|
const { BuiltinModule } = require('internal/bootstrap/realm');
|
|
BuiltinModule.allowOptionGatedBuiltins(getOptionValue);
|
|
}
|
|
|
|
function setupWebStorage() {
|
|
if (getEmbedderOptions().noBrowserGlobals ||
|
|
!getOptionValue('--experimental-webstorage')) {
|
|
return;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/webstorage.html#webstorage
|
|
exposeLazyInterfaces(globalThis, 'internal/webstorage', ['Storage']);
|
|
|
|
// localStorage is non-enumerable when --localstorage-file is not provided
|
|
// to avoid breaking {...globalThis} operations.
|
|
const localStorageFile = getOptionValue('--localstorage-file');
|
|
let lazyLocalStorage;
|
|
ObjectDefineProperty(globalThis, 'localStorage', {
|
|
__proto__: null,
|
|
enumerable: localStorageFile !== '',
|
|
configurable: true,
|
|
get() {
|
|
lazyLocalStorage ??= require('internal/webstorage').localStorage;
|
|
return lazyLocalStorage;
|
|
},
|
|
set(value) {
|
|
lazyLocalStorage = value;
|
|
},
|
|
});
|
|
|
|
defineReplaceableLazyAttribute(globalThis, 'internal/webstorage', [
|
|
'sessionStorage',
|
|
]);
|
|
}
|
|
|
|
function setupCodeCoverage() {
|
|
// Resolve the coverage directory to an absolute path, and
|
|
// overwrite process.env so that the original path gets passed
|
|
// to child processes even when they switch cwd. Don't do anything if the
|
|
// --experimental-test-coverage flag is present, as the test runner will
|
|
// handle coverage.
|
|
if (process.env.NODE_V8_COVERAGE &&
|
|
!getOptionValue('--experimental-test-coverage')) {
|
|
process.env.NODE_V8_COVERAGE =
|
|
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
|
|
}
|
|
}
|
|
|
|
function setupStacktracePrinterOnSigint() {
|
|
if (!getOptionValue('--trace-sigint')) {
|
|
return;
|
|
}
|
|
require('internal/util/trace_sigint').setTraceSigInt(true);
|
|
}
|
|
|
|
function initializeReport() {
|
|
ObjectDefineProperty(process, 'report', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
configurable: true,
|
|
get() {
|
|
const { report } = require('internal/process/report');
|
|
return report;
|
|
},
|
|
});
|
|
}
|
|
|
|
function setupDebugEnv() {
|
|
require('internal/util/debuglog').initializeDebugEnv(process.env.NODE_DEBUG);
|
|
if (getOptionValue('--expose-internals')) {
|
|
require('internal/bootstrap/realm').BuiltinModule.exposeInternals();
|
|
}
|
|
}
|
|
|
|
// This has to be called after initializeReport() is called
|
|
function initializeReportSignalHandlers() {
|
|
if (getOptionValue('--report-on-signal')) {
|
|
const { addSignalHandler } = require('internal/process/report');
|
|
addSignalHandler();
|
|
}
|
|
}
|
|
|
|
function initializeHeapSnapshotSignalHandlers() {
|
|
const signal = getOptionValue('--heapsnapshot-signal');
|
|
const diagnosticDir = getOptionValue('--diagnostic-dir');
|
|
|
|
if (!signal)
|
|
return;
|
|
|
|
require('internal/validators').validateSignalName(signal);
|
|
const { writeHeapSnapshot } = require('v8');
|
|
|
|
function doWriteHeapSnapshot() {
|
|
const heapSnapshotFilename = getHeapSnapshotFilename(diagnosticDir);
|
|
writeHeapSnapshot(heapSnapshotFilename);
|
|
}
|
|
process.on(signal, doWriteHeapSnapshot);
|
|
|
|
// The code above would add the listener back during deserialization,
|
|
// if applicable.
|
|
if (isBuildingSnapshot()) {
|
|
addSerializeCallback(() => {
|
|
process.removeListener(signal, doWriteHeapSnapshot);
|
|
});
|
|
}
|
|
}
|
|
|
|
function setupTraceCategoryState() {
|
|
const { isTraceCategoryEnabled } = internalBinding('trace_events');
|
|
const { toggleTraceCategoryState } = require('internal/process/per_thread');
|
|
toggleTraceCategoryState(isTraceCategoryEnabled('node.async_hooks'));
|
|
}
|
|
|
|
function setupInspectorHooks() {
|
|
// If Debugger.setAsyncCallStackDepth is sent during bootstrap,
|
|
// we cannot immediately call into JS to enable the hooks, which could
|
|
// interrupt the JS execution of bootstrap. So instead we save the
|
|
// notification in the inspector agent if it's sent in the middle of
|
|
// bootstrap, and process the notification later here.
|
|
if (internalBinding('config').hasInspector) {
|
|
const {
|
|
enable,
|
|
disable,
|
|
} = require('internal/inspector_async_hook');
|
|
internalBinding('inspector').registerAsyncHook(enable, disable);
|
|
}
|
|
}
|
|
|
|
function setupNetworkInspection() {
|
|
if (internalBinding('config').hasInspector && getOptionValue('--experimental-network-inspection')) {
|
|
const {
|
|
enable,
|
|
disable,
|
|
} = require('internal/inspector_network_tracking');
|
|
internalBinding('inspector').setupNetworkTracking(enable, disable);
|
|
}
|
|
}
|
|
|
|
// In general deprecations are initialized wherever the APIs are implemented,
|
|
// this is used to deprecate APIs implemented in C++ where the deprecation
|
|
// utilities are not easily accessible.
|
|
function initializeDeprecations() {
|
|
const pendingDeprecation = getOptionValue('--pending-deprecation');
|
|
|
|
// DEP0103: access to `process.binding('util').isX` type checkers
|
|
// TODO(addaleax): Turn into a full runtime deprecation.
|
|
const utilBinding = internalBinding('util');
|
|
const types = require('internal/util/types');
|
|
for (const name of [
|
|
'isArrayBuffer',
|
|
'isArrayBufferView',
|
|
'isAsyncFunction',
|
|
'isDataView',
|
|
'isDate',
|
|
'isExternal',
|
|
'isMap',
|
|
'isMapIterator',
|
|
'isNativeError',
|
|
'isPromise',
|
|
'isRegExp',
|
|
'isSet',
|
|
'isSetIterator',
|
|
'isTypedArray',
|
|
'isUint8Array',
|
|
'isAnyArrayBuffer',
|
|
]) {
|
|
utilBinding[name] = pendingDeprecation ?
|
|
deprecate(types[name],
|
|
'Accessing native typechecking bindings of Node ' +
|
|
'directly is deprecated. ' +
|
|
`Please use \`util.types.${name}\` instead.`,
|
|
'DEP0103') :
|
|
types[name];
|
|
}
|
|
|
|
// TODO(joyeecheung): this is a legacy property exposed to process.
|
|
// Now that we use the config binding to carry this information, remove
|
|
// it from the process. We may consider exposing it properly in
|
|
// process.features.
|
|
const { noBrowserGlobals } = internalBinding('config');
|
|
if (noBrowserGlobals) {
|
|
ObjectDefineProperty(process, '_noBrowserGlobals', {
|
|
__proto__: null,
|
|
writable: false,
|
|
enumerable: true,
|
|
configurable: true,
|
|
value: noBrowserGlobals,
|
|
});
|
|
}
|
|
|
|
if (pendingDeprecation) {
|
|
process.binding = deprecate(process.binding,
|
|
'process.binding() is deprecated. ' +
|
|
'Please use public APIs instead.', 'DEP0111');
|
|
|
|
process._tickCallback = deprecate(process._tickCallback,
|
|
'process._tickCallback() is deprecated',
|
|
'DEP0134');
|
|
}
|
|
}
|
|
|
|
function setupChildProcessIpcChannel() {
|
|
if (process.env.NODE_CHANNEL_FD) {
|
|
const fd = NumberParseInt(process.env.NODE_CHANNEL_FD, 10);
|
|
assert(fd >= 0);
|
|
|
|
// Make sure it's not accidentally inherited by child processes.
|
|
delete process.env.NODE_CHANNEL_FD;
|
|
|
|
const serializationMode =
|
|
process.env.NODE_CHANNEL_SERIALIZATION_MODE || 'json';
|
|
delete process.env.NODE_CHANNEL_SERIALIZATION_MODE;
|
|
|
|
require('child_process')._forkChild(fd, serializationMode);
|
|
assert(process.send);
|
|
}
|
|
}
|
|
|
|
function initializeClusterIPC() {
|
|
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
|
|
const cluster = require('cluster');
|
|
cluster._setupWorker();
|
|
// Make sure it's not accidentally inherited by child processes.
|
|
delete process.env.NODE_UNIQUE_ID;
|
|
}
|
|
}
|
|
|
|
function setupDiagnosticsChannel(isMainThread) {
|
|
// Re-link native channels after snapshot deserialization since
|
|
// JS references are cleared during serialization.
|
|
// Keep this callback in sync with the initial registration in
|
|
// lib/diagnostics_channel.js.
|
|
const dc = require('diagnostics_channel');
|
|
const dc_binding = internalBinding('diagnostics_channel');
|
|
dc_binding.linkNativeChannel((name, index) => {
|
|
const channel = dc.channel(name);
|
|
channel._index = index;
|
|
dc_binding.subscribers[index] =
|
|
(channel._subscribers?.length || 0) +
|
|
(channel._stores?.size || 0);
|
|
return channel;
|
|
});
|
|
if (isMainThread &&
|
|
process.versions.openssl !== undefined &&
|
|
getOptionValue('--enable-fips-indicator-events')) {
|
|
internalBinding('crypto').setupFipsIndicatorChannel();
|
|
}
|
|
}
|
|
|
|
function initializePermission() {
|
|
const permission = getOptionValue('--permission') || getOptionValue('--permission-audit');
|
|
if (permission) {
|
|
process.binding = function binding(_module) {
|
|
throw new ERR_ACCESS_DENIED('process.binding');
|
|
};
|
|
// Guarantee path module isn't monkey-patched to bypass permission model
|
|
ObjectFreeze(require('path'));
|
|
const { has, drop } = require('internal/process/permission');
|
|
const warnFlags = [
|
|
{ flag: '--allow-addons', enabled: true, code: 'PERM0001' },
|
|
{ flag: '--allow-child-process', enabled: true, code: 'PERM0002' },
|
|
{ flag: '--allow-ffi', enabled: process.config.variables.node_use_ffi, code: 'PERM0003' },
|
|
{ flag: '--allow-inspector', enabled: true, code: 'PERM0004' },
|
|
{ flag: '--allow-wasi', enabled: true, code: 'PERM0005' },
|
|
{ flag: '--allow-worker', enabled: true, code: 'PERM0006' },
|
|
{ flag: '--allow-openssl-store', enabled: true, code: 'PERM0007' },
|
|
];
|
|
for (const { flag, enabled, code } of warnFlags) {
|
|
if (enabled && getOptionValue(flag)) {
|
|
process.emitWarning(
|
|
`The flag ${flag} must be used with extreme caution. ` +
|
|
'It could invalidate the permission model.', 'SecurityWarning', code);
|
|
}
|
|
}
|
|
const warnCommaFlags = [
|
|
'--allow-fs-read',
|
|
'--allow-fs-write',
|
|
];
|
|
for (const flag of warnCommaFlags) {
|
|
const value = getOptionValue(flag);
|
|
if (value.length === 1 && value[0].includes(',')) {
|
|
process.emitWarning(
|
|
`The ${flag} CLI flag has changed. ` +
|
|
'Passing a comma-separated list of paths is no longer valid. ' +
|
|
'Documentation can be found at ' +
|
|
'https://nodejs.org/api/permissions.html#file-system-permissions',
|
|
'Warning',
|
|
);
|
|
}
|
|
}
|
|
|
|
const experimentalWarnFlags = [
|
|
'--allow-net',
|
|
];
|
|
for (const flag of experimentalWarnFlags) {
|
|
if (getOptionValue(flag)) {
|
|
process.emitWarning(
|
|
`The flag ${flag} is under experimental phase.`,
|
|
'ExperimentalWarning');
|
|
}
|
|
}
|
|
|
|
ObjectDefineProperty(process, 'permission', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
configurable: false,
|
|
value: {
|
|
has,
|
|
drop,
|
|
},
|
|
});
|
|
} else {
|
|
const { availableFlags } = require('internal/process/permission');
|
|
ArrayPrototypeForEach(availableFlags(), (flag) => {
|
|
const value = getOptionValue(flag);
|
|
if (value === true || value?.length) {
|
|
throw new ERR_MISSING_OPTION('--permission');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function initializeSourceMapsHandlers() {
|
|
const {
|
|
setSourceMapsSupport,
|
|
} = require('internal/source_map/source_map_cache');
|
|
const enabled = getOptionValue('--enable-source-maps');
|
|
setSourceMapsSupport(enabled, {
|
|
__proto__: null,
|
|
// TODO(legendecas): In order to smoothly improve the source map support,
|
|
// skip source maps in node_modules and generated code with
|
|
// `--enable-source-maps` in a semver major version.
|
|
nodeModules: enabled,
|
|
generatedCode: enabled,
|
|
});
|
|
}
|
|
|
|
function initializeFrozenIntrinsics() {
|
|
if (getOptionValue('--frozen-intrinsics')) {
|
|
emitExperimentalWarning('Frozen intristics');
|
|
require('internal/freeze_intrinsics')();
|
|
}
|
|
}
|
|
|
|
function runEmbedderPreload() {
|
|
internalBinding('mksnapshot').runEmbedderPreload(process, require);
|
|
}
|
|
|
|
function loadPreloadModules() {
|
|
// For user code, we preload modules if `-r` is passed
|
|
const preloadModules = getOptionValue('--require');
|
|
if (preloadModules && preloadModules.length > 0) {
|
|
const {
|
|
Module: {
|
|
_preloadModules,
|
|
},
|
|
} = require('internal/modules/cjs/loader');
|
|
_preloadModules(preloadModules);
|
|
}
|
|
}
|
|
|
|
function markBootstrapComplete() {
|
|
internalBinding('performance').markBootstrapComplete();
|
|
}
|
|
|
|
// Sequence number for diagnostic filenames
|
|
let sequenceNumOfheapSnapshot = 0;
|
|
|
|
// To generate the HeapSnapshotFilename while using custom diagnosticDir
|
|
function getHeapSnapshotFilename(diagnosticDir) {
|
|
if (!diagnosticDir) return undefined;
|
|
|
|
const date = new Date();
|
|
|
|
const year = DatePrototypeGetFullYear(date);
|
|
const month = String(DatePrototypeGetMonth(date) + 1).padStart(2, '0');
|
|
const day = String(DatePrototypeGetDate(date)).padStart(2, '0');
|
|
const hours = String(DatePrototypeGetHours(date)).padStart(2, '0');
|
|
const minutes = String(DatePrototypeGetMinutes(date)).padStart(2, '0');
|
|
const seconds = String(DatePrototypeGetSeconds(date)).padStart(2, '0');
|
|
|
|
const dateString = `${year}${month}${day}`;
|
|
const timeString = `${hours}${minutes}${seconds}`;
|
|
const pid = process.pid;
|
|
const threadId = internalBinding('worker').threadId;
|
|
const fileSequence = (++sequenceNumOfheapSnapshot).toString().padStart(3, '0');
|
|
|
|
return `${diagnosticDir}/Heap.${dateString}.${timeString}.${pid}.${threadId}.${fileSequence}.heapsnapshot`;
|
|
}
|
|
|
|
module.exports = {
|
|
finishVfsMounts,
|
|
getVfsLoadRoot,
|
|
initializeModuleLoaders,
|
|
prepareMainThreadExecution,
|
|
prepareWorkerThreadExecution,
|
|
prepareShadowRealmExecution,
|
|
prepareTestRunnerMainExecution,
|
|
markBootstrapComplete,
|
|
loadPreloadModules,
|
|
initializeFrozenIntrinsics,
|
|
};
|