From bd31a5259b35a8cd2b922db5bb421e4879847570 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 15 Jan 2019 23:12:21 +0800 Subject: [PATCH 1/3] process: split execution into main scripts This patch splits the execution mode selection from the environment setup in `lib/internal/bootstrap/node.js`, and split the entry point of different execution mode into main scripts under `lib/internal/main`: - `check_syntax.js`: used when `-c`/`--check` which only checks the syntax of the input instead of executing it. - `eval_stdin.js`: used when `-e` is passed without value and stdin is not a TTY (e.g. something is piped). - `eval_string`: used when `-e` is passed along with a string argument - `inspect.js`: for `node inspect`/`node debug` - `print_bash_completion.js`: for `--completion-bash` - `print_help.js`: for `--help` - `prof_process.js`: for `--prof-process` - `repl.js`: for the REPL - `run_main_module.js`: used when a main module is passed - `run_third_party_main.js`: for the legacy `_third_party_main.js` support - `worker_thread.js`: for workers This makes the entry points easier to navigate and paves the way for customized v8 snapshots (that do not need to deserialize execution mode setup) and better embedder APIs. As an example, after this patch, for the most common case where Node.js executes a user module as an entry point, it essentially goes through: - `lib/internal/per_context.js` to setup the v8 Context (which is also run when setting up contexts for the `vm` module) - `lib/internal/bootstrap/loaders.js` to set up internal binding and builtin module loaders (that are separate from the loaders accessible in the user land). - `lib/internal/bootstrap/node.js`: to set up the rest of the environment, including various globals and the process object - `lib/internal/main/run_main_module.js`: which is selected from C++ to prepare execution of the user module. This patch also removes `NativeModuleLoader::CompileAndCall` and exposes `NativeModuleLoader::LookupAndCompile` directly so that we can handle syntax errors and runtime errors of bootstrap scripts differently. --- lib/internal/bootstrap/cache.js | 19 +- lib/internal/bootstrap/loaders.js | 12 +- lib/internal/bootstrap/node.js | 793 ++++++------------ lib/internal/bootstrap/pre_execution.js | 82 ++ lib/internal/main/.eslintrc.yaml | 2 + lib/internal/main/check_syntax.js | 56 ++ lib/internal/main/eval_stdin.js | 26 + lib/internal/main/eval_string.js | 22 + lib/internal/main/inspect.js | 16 + .../print_bash_completion.js} | 6 +- lib/internal/{ => main}/print_help.js | 6 +- lib/internal/main/prof_process.js | 4 + lib/internal/main/repl.js | 44 + lib/internal/main/run_main_module.js | 27 + lib/internal/main/run_third_party_main.js | 9 + lib/internal/main/worker_thread.js | 39 + lib/internal/process/execution.js | 14 + node.gyp | 16 +- src/env.cc | 13 +- src/env.h | 26 +- src/node.cc | 216 +++-- src/node_internals.h | 5 +- src/node_native_module.cc | 20 - src/node_native_module.h | 24 +- src/node_options.cc | 10 +- src/node_options.h | 2 +- src/node_process_object.cc | 2 +- src/node_worker.cc | 12 +- test/code-cache/test-code-cache.js | 4 +- test/message/assert_throws_stack.out | 1 - test/message/core_line_numbers.out | 2 +- test/message/error_exit.out | 3 +- test/message/eval_messages.out | 12 +- .../events_unhandled_error_common_trace.out | 5 +- .../events_unhandled_error_nexttick.out | 6 +- .../events_unhandled_error_sameline.out | 5 +- test/message/nexttick_throw.out | 3 +- test/message/stdin_messages.out | 17 +- .../unhandled_promise_trace_warnings.out | 4 - test/message/util_inspect_error.out | 5 - 40 files changed, 862 insertions(+), 728 deletions(-) create mode 100644 lib/internal/bootstrap/pre_execution.js create mode 100644 lib/internal/main/.eslintrc.yaml create mode 100644 lib/internal/main/check_syntax.js create mode 100644 lib/internal/main/eval_stdin.js create mode 100644 lib/internal/main/eval_string.js create mode 100644 lib/internal/main/inspect.js rename lib/internal/{bash_completion.js => main/print_bash_completion.js} (91%) rename lib/internal/{ => main}/print_help.js (99%) create mode 100644 lib/internal/main/prof_process.js create mode 100644 lib/internal/main/repl.js create mode 100644 lib/internal/main/run_main_module.js create mode 100644 lib/internal/main/run_third_party_main.js create mode 100644 lib/internal/main/worker_thread.js diff --git a/lib/internal/bootstrap/cache.js b/lib/internal/bootstrap/cache.js index 83494245876dca..10d5c18334485e 100644 --- a/lib/internal/bootstrap/cache.js +++ b/lib/internal/bootstrap/cache.js @@ -13,7 +13,7 @@ const { hasTracing, hasInspector } = process.binding('config'); // Modules with source code compiled in js2c that // cannot be compiled with the code cache. -const cannotUseCache = [ +const cannotBeRequired = [ 'sys', // Deprecated. 'internal/v8_prof_polyfill', 'internal/v8_prof_processor', @@ -21,8 +21,7 @@ const cannotUseCache = [ 'internal/per_context', 'internal/test/binding', - // TODO(joyeecheung): update the C++ side so that - // the code cache is also used when compiling these two files. + 'internal/bootstrap/loaders', 'internal/bootstrap/node' ]; @@ -30,16 +29,16 @@ const cannotUseCache = [ // Skip modules that cannot be required when they are not // built into the binary. if (!hasInspector) { - cannotUseCache.push( + cannotBeRequired.push( 'inspector', 'internal/util/inspector', ); } if (!hasTracing) { - cannotUseCache.push('trace_events'); + cannotBeRequired.push('trace_events'); } if (!process.versions.openssl) { - cannotUseCache.push( + cannotBeRequired.push( 'crypto', 'https', 'http2', @@ -67,10 +66,10 @@ if (!process.versions.openssl) { const cachableBuiltins = []; for (const id of NativeModule.map.keys()) { - if (id.startsWith('internal/deps')) { - cannotUseCache.push(id); + if (id.startsWith('internal/deps') || id.startsWith('internal/main')) { + cannotBeRequired.push(id); } - if (!cannotUseCache.includes(id)) { + if (!cannotBeRequired.includes(id)) { cachableBuiltins.push(id); } } @@ -79,5 +78,5 @@ module.exports = { cachableBuiltins, getCodeCache, compileFunction, - cannotUseCache + cannotBeRequired }; diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index 6eed9eab267fa4..7078707b83adb1 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -160,7 +160,12 @@ internalBinding('module_wrap').callbackMap = new WeakMap(); // Think of this as module.exports in this file even though it is not // written in CommonJS style. -const loaderExports = { internalBinding, NativeModule }; +const loaderExports = { + internalBinding, + NativeModule, + require: nativeModuleRequire +}; + const loaderId = 'internal/bootstrap/loaders'; // Set up NativeModule. @@ -194,7 +199,7 @@ for (var i = 0; i < moduleIds.length; ++i) { NativeModule.map.set(id, mod); } -NativeModule.require = function(id) { +function nativeModuleRequire(id) { if (id === loaderId) { return loaderExports; } @@ -218,8 +223,9 @@ NativeModule.require = function(id) { moduleLoadList.push(`NativeModule ${id}`); mod.compile(); return mod.exports; -}; +} +NativeModule.require = nativeModuleRequire; NativeModule.exists = function(id) { return NativeModule.map.has(id); }; diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 789d8c1754bd75..23d72547ae900f 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -21,546 +21,294 @@ const { internalBinding, NativeModule } = loaderExports; const { getOptionValue } = NativeModule.require('internal/options'); const config = internalBinding('config'); -function startup() { - setupTraceCategoryState(); - - setupProcessObject(); - - // TODO(joyeecheung): this does not have to done so early, any fatal errors - // thrown before user code execution should simply crash the process - // and we do not care about any clean up at that point. We don't care - // about emitting any events if the process crash upon bootstrap either. - { - const { - fatalException, - setUncaughtExceptionCaptureCallback, - hasUncaughtExceptionCaptureCallback - } = NativeModule.require('internal/process/execution'); - - process._fatalException = fatalException; - process.setUncaughtExceptionCaptureCallback = - setUncaughtExceptionCaptureCallback; - process.hasUncaughtExceptionCaptureCallback = - hasUncaughtExceptionCaptureCallback; - } - - setupGlobalVariables(); - - // Bootstrappers for all threads, including worker threads and main thread - const perThreadSetup = NativeModule.require('internal/process/per_thread'); - // Bootstrappers for the main thread only - let mainThreadSetup; - // Bootstrappers for the worker threads only - let workerThreadSetup; - if (isMainThread) { - mainThreadSetup = NativeModule.require( - 'internal/process/main_thread_only' - ); - } else { - workerThreadSetup = NativeModule.require( - 'internal/process/worker_thread_only' - ); - } - - // process.config is serialized config.gypi - process.config = JSON.parse(internalBinding('native_module').config); - - const rawMethods = internalBinding('process_methods'); - // Set up methods and events on the process object for the main thread - if (isMainThread) { - // This depends on process being an event emitter - mainThreadSetup.setupSignalHandlers(internalBinding); - - process.abort = rawMethods.abort; - const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods); - process.umask = wrapped.umask; - process.chdir = wrapped.chdir; - - // TODO(joyeecheung): deprecate and remove these underscore methods - process._debugProcess = rawMethods._debugProcess; - process._debugEnd = rawMethods._debugEnd; - process._startProfilerIdleNotifier = - rawMethods._startProfilerIdleNotifier; - process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier; - } else { - const wrapped = workerThreadSetup.wrapProcessMethods(rawMethods); - - process.umask = wrapped.umask; - } +setupTraceCategoryState(); - // Set up methods on the process object for all threads - { - process.cwd = rawMethods.cwd; - 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; - - // TODO(joyeecheung): remove these - process.reallyExit = rawMethods.reallyExit; - process._kill = rawMethods._kill; - - const wrapped = perThreadSetup.wrapProcessMethods(rawMethods); - process._rawDebug = wrapped._rawDebug; - process.hrtime = wrapped.hrtime; - process.hrtime.bigint = wrapped.hrtimeBigInt; - process.cpuUsage = wrapped.cpuUsage; - process.memoryUsage = wrapped.memoryUsage; - process.kill = wrapped.kill; - process.exit = wrapped.exit; - } +setupProcessObject(); +// TODO(joyeecheung): this does not have to done so early, any fatal errors +// thrown before user code execution should simply crash the process +// and we do not care about any clean up at that point. We don't care +// about emitting any events if the process crash upon bootstrap either. +{ const { - onWarning, - emitWarning - } = NativeModule.require('internal/process/warning'); - if (!process.noProcessWarnings && process.env.NODE_NO_WARNINGS !== '1') { - process.on('warning', onWarning); - } - process.emitWarning = emitWarning; - - const { - nextTick, - runNextTicks - } = NativeModule.require('internal/process/next_tick').setup(); - - 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 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; - - if (isMainThread) { - const wrapped = mainThreadSetup.wrapPosixCredentialSetters(credentials); - process.initgroups = wrapped.initgroups; - process.setgroups = wrapped.setgroups; - process.setegid = wrapped.setegid; - process.seteuid = wrapped.seteuid; - process.setgid = wrapped.setgid; - process.setuid = wrapped.setuid; - } - } - - if (isMainThread) { - const { getStdout, getStdin, getStderr } = - NativeModule.require('internal/process/stdio').getMainThreadStdio(); - setupProcessStdio(getStdout, getStdin, getStderr); - } else { - const { getStdout, getStdin, getStderr } = - workerThreadSetup.initializeWorkerStdio(); - setupProcessStdio(getStdout, getStdin, getStderr); - } - - if (config.hasInspector) { - const { - enable, - disable - } = NativeModule.require('internal/inspector_async_hook'); - internalBinding('inspector').registerAsyncHook(enable, disable); - } - - // 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() - if (isMainThread && process.env.NODE_CHANNEL_FD) { - mainThreadSetup.setupChildProcessIpcChannel(); - } - - // TODO(joyeecheung): move this down further to get better snapshotting - const experimentalPolicy = getOptionValue('--experimental-policy'); - if (isMainThread && experimentalPolicy) { - process.emitWarning('Policies are experimental.', - 'ExperimentalWarning'); - const { pathToFileURL, URL } = NativeModule.require('url'); - // URL here as it is slightly different parsing - // no bare specifiers for now - let manifestURL; - if (NativeModule.require('path').isAbsolute(experimentalPolicy)) { - manifestURL = new URL(`file:///${experimentalPolicy}`); - } else { - const cwdURL = pathToFileURL(process.cwd()); - cwdURL.pathname += '/'; - manifestURL = new URL(experimentalPolicy, cwdURL); - } - const fs = NativeModule.require('fs'); - const src = fs.readFileSync(manifestURL, 'utf8'); - NativeModule.require('internal/process/policy') - .setup(src, manifestURL.href); - } - - const browserGlobals = !process._noBrowserGlobals; - if (browserGlobals) { - setupGlobalTimeouts(); - setupGlobalConsole(); - setupGlobalURL(); - setupGlobalEncoding(); - setupQueueMicrotask(); - } - - setupDOMException(); - - // On OpenBSD process.execPath will be relative unless we - // get the full path before process.execPath is used. - if (process.platform === 'openbsd') { - const { realpathSync } = NativeModule.require('fs'); - process.execPath = realpathSync.native(process.execPath); - } - - Object.defineProperty(process, 'argv0', { - enumerable: true, - configurable: false, - value: process.argv[0] - }); - process.argv[0] = process.execPath; - - // Handle `--debug*` deprecation and invalidation. - if (process._invalidDebug) { - process.emitWarning( - '`node --debug` and `node --debug-brk` are invalid. ' + - 'Please use `node --inspect` or `node --inspect-brk` instead.', - 'DeprecationWarning', 'DEP0062', startup, true); - process.exit(9); - } else if (process._deprecatedDebugBrk) { - process.emitWarning( - '`node --inspect --debug-brk` is deprecated. ' + - 'Please use `node --inspect-brk` instead.', - 'DeprecationWarning', 'DEP0062', startup, true); - } - - const { deprecate } = NativeModule.require('internal/util'); - { - // Install legacy getters on the `util` binding for typechecking. - // TODO(addaleax): Turn into a full runtime deprecation. - const pendingDeprecation = getOptionValue('--pending-deprecation'); - const utilBinding = internalBinding('util'); - const types = NativeModule.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]; - } - } + fatalException, + setUncaughtExceptionCaptureCallback, + hasUncaughtExceptionCaptureCallback + } = NativeModule.require('internal/process/execution'); + + process._fatalException = fatalException; + process.setUncaughtExceptionCaptureCallback = + setUncaughtExceptionCaptureCallback; + process.hasUncaughtExceptionCaptureCallback = + hasUncaughtExceptionCaptureCallback; +} - // process.allowedNodeEnvironmentFlags - Object.defineProperty(process, 'allowedNodeEnvironmentFlags', { - 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) { - Object.defineProperty(this, 'allowedNodeEnvironmentFlags', { - 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 } = config; - Object.defineProperty(process, 'features', { - enumerable: true, - writable: false, - configurable: false, - value: { - debug: isDebugBuild, - uv: true, - ipv6: true, // TODO(bnoordhuis) ping libuv - tls_alpn: hasOpenSSL, - tls_sni: hasOpenSSL, - tls_ocsp: hasOpenSSL, - tls: hasOpenSSL - } - }); +setupGlobalVariables(); + +// Bootstrappers for all threads, including worker threads and main thread +const perThreadSetup = NativeModule.require('internal/process/per_thread'); +// Bootstrappers for the main thread only +let mainThreadSetup; +// Bootstrappers for the worker threads only +let workerThreadSetup; +if (isMainThread) { + mainThreadSetup = NativeModule.require( + 'internal/process/main_thread_only' + ); +} else { + workerThreadSetup = NativeModule.require( + 'internal/process/worker_thread_only' + ); +} - // User-facing NODE_V8_COVERAGE environment variable that writes - // ScriptCoverage to a specified file. - if (process.env.NODE_V8_COVERAGE) { - const originalReallyExit = process.reallyExit; - const cwd = NativeModule.require('internal/process/execution').tryGetCwd(); - const { resolve } = NativeModule.require('path'); - // 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. - const coverageDirectory = resolve(cwd, process.env.NODE_V8_COVERAGE); - process.env.NODE_V8_COVERAGE = coverageDirectory; - const { - writeCoverage, - setCoverageDirectory - } = NativeModule.require('internal/coverage-gen/with_profiler'); - setCoverageDirectory(coverageDirectory); - process.on('exit', writeCoverage); - process.reallyExit = (code) => { - writeCoverage(); - originalReallyExit(code); - }; - } +// process.config is serialized config.gypi +process.config = JSON.parse(internalBinding('native_module').config); + +const rawMethods = internalBinding('process_methods'); +// Set up methods and events on the process object for the main thread +if (isMainThread) { + // This depends on process being an event emitter + mainThreadSetup.setupSignalHandlers(internalBinding); + + process.abort = rawMethods.abort; + const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods); + process.umask = wrapped.umask; + process.chdir = wrapped.chdir; + + // TODO(joyeecheung): deprecate and remove these underscore methods + process._debugProcess = rawMethods._debugProcess; + process._debugEnd = rawMethods._debugEnd; + process._startProfilerIdleNotifier = + rawMethods._startProfilerIdleNotifier; + process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier; +} else { + const wrapped = workerThreadSetup.wrapProcessMethods(rawMethods); + + process.umask = wrapped.umask; +} - const perf = internalBinding('performance'); - const { - NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE, - } = perf.constants; - perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); +// Set up methods on the process object for all threads +{ + process.cwd = rawMethods.cwd; + 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; + + // TODO(joyeecheung): remove these + process.reallyExit = rawMethods.reallyExit; + process._kill = rawMethods._kill; + + const wrapped = perThreadSetup.wrapProcessMethods(rawMethods); + process._rawDebug = wrapped._rawDebug; + process.hrtime = wrapped.hrtime; + process.hrtime.bigint = wrapped.hrtimeBigInt; + process.cpuUsage = wrapped.cpuUsage; + process.memoryUsage = wrapped.memoryUsage; + process.kill = wrapped.kill; + process.exit = wrapped.exit; +} - if (getOptionValue('--experimental-report')) { - NativeModule.require('internal/process/report').setup(); - } +const { + onWarning, + emitWarning +} = NativeModule.require('internal/process/warning'); +if (!process.noProcessWarnings && process.env.NODE_NO_WARNINGS !== '1') { + process.on('warning', onWarning); +} +process.emitWarning = emitWarning; + +const { + nextTick, + runNextTicks +} = NativeModule.require('internal/process/next_tick').setup(); + +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 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; if (isMainThread) { - return startMainThreadExecution; - } else { - return startWorkerThreadExecution; + const wrapped = mainThreadSetup.wrapPosixCredentialSetters(credentials); + process.initgroups = wrapped.initgroups; + process.setgroups = wrapped.setgroups; + process.setegid = wrapped.setegid; + process.seteuid = wrapped.seteuid; + process.setgid = wrapped.setgid; + process.setuid = wrapped.setuid; } } -function startWorkerThreadExecution() { - // If we are in a worker thread, execute the script sent through the - // message port. - const { - getEnvMessagePort, - threadId - } = internalBinding('worker'); - const { - createMessageHandler, - createWorkerFatalExeception - } = NativeModule.require('internal/process/worker_thread_only'); - - // Set up the message port and start listening - const debug = NativeModule.require('util').debuglog('worker'); - debug(`[${threadId}] is setting up worker child environment`); - - const port = getEnvMessagePort(); - port.on('message', createMessageHandler( - port, - prepareUserCodeExecution)); - port.start(); - - // Overwrite fatalException - process._fatalException = createWorkerFatalExeception(port); +if (isMainThread) { + const { getStdout, getStdin, getStderr } = + NativeModule.require('internal/process/stdio').getMainThreadStdio(); + setupProcessStdio(getStdout, getStdin, getStderr); +} else { + const { getStdout, getStdin, getStderr } = + workerThreadSetup.initializeWorkerStdio(); + setupProcessStdio(getStdout, getStdin, getStderr); } -// There are various modes that Node can run in. The most common two -// are running from a script and running the REPL - but there are a few -// others like the debugger or running --eval arguments. Here we decide -// which mode we run in. -function startMainThreadExecution(mainScript) { - if (mainScript) { - process.nextTick(() => { - NativeModule.require(mainScript); - }); - return; - } - - // `node inspect ...` or `node debug ...` - if (process.argv[1] === 'inspect' || process.argv[1] === 'debug') { - if (process.argv[1] === 'debug') { - process.emitWarning( - '`node debug` is deprecated. Please use `node inspect` instead.', - 'DeprecationWarning', 'DEP0068'); - } - - // Start the debugger agent. - process.nextTick(() => { - NativeModule.require('internal/deps/node-inspect/lib/_inspect').start(); - }); - return; - } - - // node --help - if (getOptionValue('--help')) { - NativeModule.require('internal/print_help').print(process.stdout); - return; - } - - // e.g. node --completion-bash >> ~/.bashrc - if (getOptionValue('--completion-bash')) { - NativeModule.require('internal/bash_completion').print(process.stdout); - return; - } - - // `node --prof-process` - if (getOptionValue('--prof-process')) { - NativeModule.require('internal/v8_prof_processor'); - return; - } - - // There is user code to be run. - prepareUserCodeExecution(); - executeUserCode(); +if (config.hasInspector) { + const { + enable, + disable + } = NativeModule.require('internal/inspector_async_hook'); + internalBinding('inspector').registerAsyncHook(enable, disable); } -function prepareUserCodeExecution() { - // 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). - if (process.argv[1] && process.env.NODE_UNIQUE_ID) { - const cluster = NativeModule.require('cluster'); - cluster._setupWorker(); - // Make sure it's not accidentally inherited by child processes. - delete process.env.NODE_UNIQUE_ID; - } +// 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() +if (isMainThread && process.env.NODE_CHANNEL_FD) { + mainThreadSetup.setupChildProcessIpcChannel(); +} - const experimentalModules = getOptionValue('--experimental-modules'); - const experimentalVMModules = getOptionValue('--experimental-vm-modules'); - if (experimentalModules || experimentalVMModules) { - if (experimentalModules) { - process.emitWarning( - 'The ESM module loader is experimental.', - 'ExperimentalWarning', undefined); - } +const browserGlobals = !process._noBrowserGlobals; +if (browserGlobals) { + setupGlobalTimeouts(); + setupGlobalConsole(); + setupGlobalURL(); + setupGlobalEncoding(); + setupQueueMicrotask(); +} - const { - setImportModuleDynamicallyCallback, - setInitializeImportMetaObjectCallback - } = internalBinding('module_wrap'); - const esm = NativeModule.require('internal/process/esm_loader'); - // Setup per-isolate callbacks that locate data or callbacks that we keep - // track of for different ESM modules. - setInitializeImportMetaObjectCallback(esm.initializeImportMetaObject); - setImportModuleDynamicallyCallback(esm.importModuleDynamicallyCallback); - const userLoader = getOptionValue('--loader'); - // If --loader is specified, create a loader with user hooks. Otherwise - // create the default loader. - esm.initializeLoader(process.cwd(), userLoader); - } +setupDOMException(); - // For user code, we preload modules if `-r` is passed - const preloadModules = getOptionValue('--require'); - if (preloadModules.length) { - const { - _preloadModules - } = NativeModule.require('internal/modules/cjs/loader'); - _preloadModules(preloadModules); - } +// On OpenBSD process.execPath will be relative unless we +// get the full path before process.execPath is used. +if (process.platform === 'openbsd') { + const { realpathSync } = NativeModule.require('fs'); + process.execPath = realpathSync.native(process.execPath); } -function executeUserCode() { - // User passed `-e` or `--eval` arguments to Node without `-i` or - // `--interactive`. - // Note that the name `forceRepl` is merely an alias of `interactive` - // in code. - if (getOptionValue('[has_eval_string]') && !getOptionValue('--interactive')) { - const { - addBuiltinLibsToObject - } = NativeModule.require('internal/modules/cjs/helpers'); - addBuiltinLibsToObject(global); - const source = getOptionValue('--eval'); - const { evalScript } = NativeModule.require('internal/process/execution'); - evalScript('[eval]', source, process._breakFirstLine); - return; - } - - // If the first argument is a file name, run it as a main script - if (process.argv[1] && process.argv[1] !== '-') { - // Expand process.argv[1] into a full path. - const path = NativeModule.require('path'); - process.argv[1] = path.resolve(process.argv[1]); - - const CJSModule = NativeModule.require('internal/modules/cjs/loader'); - - // If user passed `-c` or `--check` arguments to Node, check its syntax - // instead of actually running the file. - if (getOptionValue('--check')) { - const fs = NativeModule.require('fs'); - // Read the source. - const filename = CJSModule._resolveFilename(process.argv[1]); - const source = fs.readFileSync(filename, 'utf-8'); - checkScriptSyntax(source, filename); - process.exit(0); - } +Object.defineProperty(process, 'argv0', { + enumerable: true, + configurable: false, + value: process.argv[0] +}); +process.argv[0] = process.execPath; + +// Handle `--debug*` deprecation and invalidation. +if (process._invalidDebug) { + process.emitWarning( + '`node --debug` and `node --debug-brk` are invalid. ' + + 'Please use `node --inspect` or `node --inspect-brk` instead.', + 'DeprecationWarning', 'DEP0062', undefined, true); + process.exit(9); +} else if (process._deprecatedDebugBrk) { + process.emitWarning( + '`node --inspect --debug-brk` is deprecated. ' + + 'Please use `node --inspect-brk` instead.', + 'DeprecationWarning', 'DEP0062', undefined, true); +} - // Note: this actually tries to run the module as a ESM first if - // --experimental-modules is on. - // TODO(joyeecheung): can we move that logic to here? Note that this - // is an undocumented method available via `require('module').runMain` - CJSModule.runMain(); - return; +const { deprecate } = NativeModule.require('internal/util'); +{ + // Install legacy getters on the `util` binding for typechecking. + // TODO(addaleax): Turn into a full runtime deprecation. + const pendingDeprecation = getOptionValue('--pending-deprecation'); + const utilBinding = internalBinding('util'); + const types = NativeModule.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]; } +} - // Create the REPL if `-i` or `--interactive` is passed, or if - // stdin is a TTY. - // Note that the name `forceRepl` is merely an alias of `interactive` - // in code. - if (process._forceRepl || NativeModule.require('tty').isatty(0)) { - const cliRepl = NativeModule.require('internal/repl'); - cliRepl.createInternalRepl(process.env, (err, repl) => { - if (err) { - throw err; - } - repl.on('exit', () => { - if (repl._flushing) { - repl.pause(); - return repl.once('flushHistory', () => { - process.exit(); - }); - } - process.exit(); - }); +// process.allowedNodeEnvironmentFlags +Object.defineProperty(process, 'allowedNodeEnvironmentFlags', { + 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) { + Object.defineProperty(this, 'allowedNodeEnvironmentFlags', { + value, + configurable: true, + enumerable: true, + writable: true }); - - // User passed '-e' or '--eval' along with `-i` or `--interactive` - if (process._eval != null) { - const { evalScript } = NativeModule.require('internal/process/execution'); - evalScript('[eval]', process._eval, process._breakFirstLine); - } - return; - } - - // Stdin is not a TTY, we will read it and execute it. - readAndExecuteStdin(); + }, + 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 } = config; +Object.defineProperty(process, 'features', { + enumerable: true, + writable: false, + configurable: false, + value: { + debug: isDebugBuild, + uv: true, + ipv6: true, // TODO(bnoordhuis) ping libuv + tls_alpn: hasOpenSSL, + tls_sni: hasOpenSSL, + tls_ocsp: hasOpenSSL, + tls: hasOpenSSL + } +}); + +// User-facing NODE_V8_COVERAGE environment variable that writes +// ScriptCoverage to a specified file. +if (process.env.NODE_V8_COVERAGE) { + const originalReallyExit = process.reallyExit; + const cwd = NativeModule.require('internal/process/execution').tryGetCwd(); + const { resolve } = NativeModule.require('path'); + // 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. + const coverageDirectory = resolve(cwd, process.env.NODE_V8_COVERAGE); + process.env.NODE_V8_COVERAGE = coverageDirectory; + const { + writeCoverage, + setCoverageDirectory + } = NativeModule.require('internal/coverage-gen/with_profiler'); + setCoverageDirectory(coverageDirectory); + process.on('exit', writeCoverage); + process.reallyExit = (code) => { + writeCoverage(); + originalReallyExit(code); + }; } -function readAndExecuteStdin() { - process.stdin.setEncoding('utf8'); - - let code = ''; - process.stdin.on('data', (d) => { - code += d; - }); - - process.stdin.on('end', () => { - if (process._syntax_check_only != null) { - checkScriptSyntax(code, '[stdin]'); - } else { - process._eval = code; - const { evalScript } = NativeModule.require('internal/process/execution'); - evalScript('[stdin]', process._eval, process._breakFirstLine); - } - }); +if (getOptionValue('--experimental-report')) { + NativeModule.require('internal/process/report').setup(); } function setupTraceCategoryState() { @@ -795,22 +543,3 @@ function setupDOMException() { const { registerDOMException } = internalBinding('messaging'); registerDOMException(DOMException); } - -function checkScriptSyntax(source, filename) { - const CJSModule = NativeModule.require('internal/modules/cjs/loader'); - const vm = NativeModule.require('vm'); - const { - stripShebang, stripBOM - } = NativeModule.require('internal/modules/cjs/helpers'); - - // Remove Shebang. - source = stripShebang(source); - // Remove BOM. - source = stripBOM(source); - // Wrap it. - source = CJSModule.wrap(source); - // Compile the script, this will throw if it fails. - new vm.Script(source, { displayErrors: true, filename }); -} - -return startup(); diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js new file mode 100644 index 00000000000000..798c581a721735 --- /dev/null +++ b/lib/internal/bootstrap/pre_execution.js @@ -0,0 +1,82 @@ +'use strict'; + +const { getOptionValue } = require('internal/options'); + +function initializeClusterIPC() { + // 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). + 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 initializePolicy() { + const experimentalPolicy = getOptionValue('--experimental-policy'); + if (experimentalPolicy) { + process.emitWarning('Policies are experimental.', + 'ExperimentalWarning'); + const { pathToFileURL, URL } = require('url'); + // URL here as it is slightly different parsing + // no bare specifiers for now + let manifestURL; + if (require('path').isAbsolute(experimentalPolicy)) { + manifestURL = new URL(`file:///${experimentalPolicy}`); + } else { + const cwdURL = pathToFileURL(process.cwd()); + cwdURL.pathname += '/'; + manifestURL = new URL(experimentalPolicy, cwdURL); + } + const fs = require('fs'); + const src = fs.readFileSync(manifestURL, 'utf8'); + require('internal/process/policy') + .setup(src, manifestURL.href); + } +} + +function initializeESMLoader() { + const experimentalModules = getOptionValue('--experimental-modules'); + const experimentalVMModules = getOptionValue('--experimental-vm-modules'); + if (experimentalModules || experimentalVMModules) { + if (experimentalModules) { + process.emitWarning( + 'The ESM module loader is experimental.', + 'ExperimentalWarning', undefined); + } + + const { + setImportModuleDynamicallyCallback, + setInitializeImportMetaObjectCallback + } = internalBinding('module_wrap'); + const esm = require('internal/process/esm_loader'); + // Setup per-isolate callbacks that locate data or callbacks that we keep + // track of for different ESM modules. + setInitializeImportMetaObjectCallback(esm.initializeImportMetaObject); + setImportModuleDynamicallyCallback(esm.importModuleDynamicallyCallback); + const userLoader = getOptionValue('--loader'); + // If --loader is specified, create a loader with user hooks. Otherwise + // create the default loader. + esm.initializeLoader(process.cwd(), userLoader); + } +} + +function loadPreloadModules() { + // For user code, we preload modules if `-r` is passed + const preloadModules = getOptionValue('--require'); + if (preloadModules) { + const { + _preloadModules + } = require('internal/modules/cjs/loader'); + _preloadModules(preloadModules); + } +} + +module.exports = { + initializeClusterIPC, + initializePolicy, + initializeESMLoader, + loadPreloadModules +}; diff --git a/lib/internal/main/.eslintrc.yaml b/lib/internal/main/.eslintrc.yaml new file mode 100644 index 00000000000000..dfb75077782301 --- /dev/null +++ b/lib/internal/main/.eslintrc.yaml @@ -0,0 +1,2 @@ +globals: + markBootstrapComplete: true diff --git a/lib/internal/main/check_syntax.js b/lib/internal/main/check_syntax.js new file mode 100644 index 00000000000000..5d4d7a04ebd0eb --- /dev/null +++ b/lib/internal/main/check_syntax.js @@ -0,0 +1,56 @@ +'use strict'; + +// If user passed `-c` or `--check` arguments to Node, check its syntax +// instead of actually running the file. + +const { + initializeClusterIPC, + initializePolicy, + initializeESMLoader, + loadPreloadModules +} = require('internal/bootstrap/pre_execution'); + +const { + readStdin +} = require('internal/process/execution'); + +const CJSModule = require('internal/modules/cjs/loader'); +const vm = require('vm'); +const { + stripShebang, stripBOM +} = require('internal/modules/cjs/helpers'); + +// TODO(joyeecheung): not every one of these are necessary +initializeClusterIPC(); +initializePolicy(); +initializeESMLoader(); +loadPreloadModules(); +markBootstrapComplete(); + +if (process.argv[1] && process.argv[1] !== '-') { + // Expand process.argv[1] into a full path. + const path = require('path'); + process.argv[1] = path.resolve(process.argv[1]); + // Read the source. + const filename = CJSModule._resolveFilename(process.argv[1]); + + const fs = require('fs'); + const source = fs.readFileSync(filename, 'utf-8'); + + checkScriptSyntax(source, filename); +} else { + readStdin((code) => { + checkScriptSyntax(code, '[stdin]'); + }); +} + +function checkScriptSyntax(source, filename) { + // Remove Shebang. + source = stripShebang(source); + // Remove BOM. + source = stripBOM(source); + // Wrap it. + source = CJSModule.wrap(source); + // Compile the script, this will throw if it fails. + new vm.Script(source, { displayErrors: true, filename }); +} diff --git a/lib/internal/main/eval_stdin.js b/lib/internal/main/eval_stdin.js new file mode 100644 index 00000000000000..ad15fdb93cd49d --- /dev/null +++ b/lib/internal/main/eval_stdin.js @@ -0,0 +1,26 @@ +'use strict'; + +// Stdin is not a TTY, we will read it and execute it. + +const { + initializeClusterIPC, + initializePolicy, + initializeESMLoader, + loadPreloadModules +} = require('internal/bootstrap/pre_execution'); + +const { + evalScript, + readStdin +} = require('internal/process/execution'); + +initializeClusterIPC(); +initializePolicy(); +initializeESMLoader(); +loadPreloadModules(); +markBootstrapComplete(); + +readStdin((code) => { + process._eval = code; + evalScript('[stdin]', process._eval, process._breakFirstLine); +}); diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js new file mode 100644 index 00000000000000..cd382b48e76663 --- /dev/null +++ b/lib/internal/main/eval_string.js @@ -0,0 +1,22 @@ +'use strict'; + +// User passed `-e` or `--eval` arguments to Node without `-i` or +// `--interactive`. + +const { + initializeClusterIPC, + initializePolicy, + initializeESMLoader, + loadPreloadModules +} = require('internal/bootstrap/pre_execution'); +const { evalScript } = require('internal/process/execution'); +const { addBuiltinLibsToObject } = require('internal/modules/cjs/helpers'); + +const source = require('internal/options').getOptionValue('--eval'); +initializeClusterIPC(); +initializePolicy(); +initializeESMLoader(); +loadPreloadModules(); +addBuiltinLibsToObject(global); +markBootstrapComplete(); +evalScript('[eval]', source, process._breakFirstLine); diff --git a/lib/internal/main/inspect.js b/lib/internal/main/inspect.js new file mode 100644 index 00000000000000..7376f8984b13e1 --- /dev/null +++ b/lib/internal/main/inspect.js @@ -0,0 +1,16 @@ +'use strict'; + +// `node inspect ...` or `node debug ...` + +if (process.argv[1] === 'debug') { + process.emitWarning( + '`node debug` is deprecated. Please use `node inspect` instead.', + 'DeprecationWarning', 'DEP0068'); +} + +markBootstrapComplete(); + +// Start the debugger agent. +process.nextTick(() => { + require('internal/deps/node-inspect/lib/_inspect').start(); +}); diff --git a/lib/internal/bash_completion.js b/lib/internal/main/print_bash_completion.js similarity index 91% rename from lib/internal/bash_completion.js rename to lib/internal/main/print_bash_completion.js index 13363e8c4b8c32..225ed3d2221c00 100644 --- a/lib/internal/bash_completion.js +++ b/lib/internal/main/print_bash_completion.js @@ -18,6 +18,6 @@ function print(stream) { complete -F _node_complete node node_g`); } -module.exports = { - print -}; +markBootstrapComplete(); + +print(process.stdout); diff --git a/lib/internal/print_help.js b/lib/internal/main/print_help.js similarity index 99% rename from lib/internal/print_help.js rename to lib/internal/main/print_help.js index fdec7bd09bd87e..ca60994d942c6c 100644 --- a/lib/internal/print_help.js +++ b/lib/internal/main/print_help.js @@ -172,6 +172,6 @@ function print(stream) { stream.write('\nDocumentation can be found at https://nodejs.org/\n'); } -module.exports = { - print -}; +markBootstrapComplete(); + +print(process.stdout); diff --git a/lib/internal/main/prof_process.js b/lib/internal/main/prof_process.js new file mode 100644 index 00000000000000..a1143cb201e79c --- /dev/null +++ b/lib/internal/main/prof_process.js @@ -0,0 +1,4 @@ +'use strict'; + +markBootstrapComplete(); +require('internal/v8_prof_processor'); diff --git a/lib/internal/main/repl.js b/lib/internal/main/repl.js new file mode 100644 index 00000000000000..4ca328421bcb9a --- /dev/null +++ b/lib/internal/main/repl.js @@ -0,0 +1,44 @@ +'use strict'; + +// Create the REPL if `-i` or `--interactive` is passed, or if +// the main module is not specified and stdin is a TTY. + +const { + initializeClusterIPC, + initializePolicy, + initializeESMLoader, + loadPreloadModules +} = require('internal/bootstrap/pre_execution'); + +const { + evalScript +} = require('internal/process/execution'); + +initializeClusterIPC(); +initializePolicy(); +initializeESMLoader(); +loadPreloadModules(); + +const cliRepl = require('internal/repl'); +cliRepl.createInternalRepl(process.env, (err, repl) => { + if (err) { + throw err; + } + repl.on('exit', () => { + if (repl._flushing) { + repl.pause(); + return repl.once('flushHistory', () => { + process.exit(); + }); + } + process.exit(); + }); +}); + +// If user passed '-e' or '--eval' along with `-i` or `--interactive`, +// evaluate the code in the current context. +if (process._eval != null) { + evalScript('[eval]', process._eval, process._breakFirstLine); +} + +markBootstrapComplete(); diff --git a/lib/internal/main/run_main_module.js b/lib/internal/main/run_main_module.js new file mode 100644 index 00000000000000..b5049cffc5250c --- /dev/null +++ b/lib/internal/main/run_main_module.js @@ -0,0 +1,27 @@ +'use strict'; + +const { + initializeClusterIPC, + initializePolicy, + initializeESMLoader, + loadPreloadModules +} = require('internal/bootstrap/pre_execution'); + +initializeClusterIPC(); +initializePolicy(); +initializeESMLoader(); +loadPreloadModules(); + +// Expand process.argv[1] into a full path. +const path = require('path'); +process.argv[1] = path.resolve(process.argv[1]); + +const CJSModule = require('internal/modules/cjs/loader'); + +markBootstrapComplete(); + +// Note: this actually tries to run the module as a ESM first if +// --experimental-modules is on. +// TODO(joyeecheung): can we move that logic to here? Note that this +// is an undocumented method available via `require('module').runMain` +CJSModule.runMain(); diff --git a/lib/internal/main/run_third_party_main.js b/lib/internal/main/run_third_party_main.js new file mode 100644 index 00000000000000..c26c1f25f380c8 --- /dev/null +++ b/lib/internal/main/run_third_party_main.js @@ -0,0 +1,9 @@ +'use strict'; + +// Legacy _third_party_main.js support + +markBootstrapComplete(); + +process.nextTick(() => { + require('_third_party_main'); +}); diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js new file mode 100644 index 00000000000000..62a4bb1b7dd77e --- /dev/null +++ b/lib/internal/main/worker_thread.js @@ -0,0 +1,39 @@ +'use strict'; + +// In worker threads, execute the script sent through the +// message port. + +const { + initializeClusterIPC, + initializeESMLoader, + loadPreloadModules +} = require('internal/bootstrap/pre_execution'); + +const { + getEnvMessagePort, + threadId +} = internalBinding('worker'); + +const { + createMessageHandler, + createWorkerFatalExeception +} = require('internal/process/worker_thread_only'); + +const debug = require('util').debuglog('worker'); +debug(`[${threadId}] is setting up worker child environment`); + +function prepareUserCodeExecution() { + initializeClusterIPC(); + initializeESMLoader(); + loadPreloadModules(); +} + +// Set up the message port and start listening +const port = getEnvMessagePort(); +port.on('message', createMessageHandler(port, prepareUserCodeExecution)); +port.start(); + +// Overwrite fatalException +process._fatalException = createWorkerFatalExeception(port); + +markBootstrapComplete(); diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index b11f54b35d6253..74d6575e0a9114 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -165,7 +165,21 @@ function createFatalException() { }; } +function readStdin(callback) { + process.stdin.setEncoding('utf8'); + + let code = ''; + process.stdin.on('data', (d) => { + code += d; + }); + + process.stdin.on('end', () => { + callback(code); + }); +} + module.exports = { + readStdin, tryGetCwd, evalScript, fatalException: createFatalException(), diff --git a/node.gyp b/node.gyp index 0089b0a850619d..c7107f02953c8a 100644 --- a/node.gyp +++ b/node.gyp @@ -29,6 +29,7 @@ 'lib/internal/bootstrap/cache.js', 'lib/internal/bootstrap/loaders.js', 'lib/internal/bootstrap/node.js', + 'lib/internal/bootstrap/pre_execution.js', 'lib/async_hooks.js', 'lib/assert.js', 'lib/buffer.js', @@ -85,7 +86,6 @@ 'lib/zlib.js', 'lib/internal/assert.js', 'lib/internal/async_hooks.js', - 'lib/internal/bash_completion.js', 'lib/internal/buffer.js', 'lib/internal/cli_table.js', 'lib/internal/child_process.js', @@ -130,6 +130,17 @@ 'lib/internal/inspector_async_hook.js', 'lib/internal/js_stream_socket.js', 'lib/internal/linkedlist.js', + 'lib/internal/main/check_syntax.js', + 'lib/internal/main/eval_string.js', + 'lib/internal/main/eval_stdin.js', + 'lib/internal/main/inspect.js', + 'lib/internal/main/print_bash_completion.js', + 'lib/internal/main/print_help.js', + 'lib/internal/main/prof_process.js', + 'lib/internal/main/repl.js', + 'lib/internal/main/run_main_module.js', + 'lib/internal/main/run_third_party_main.js', + 'lib/internal/main/worker_thread.js', 'lib/internal/modules/cjs/helpers.js', 'lib/internal/modules/cjs/loader.js', 'lib/internal/modules/esm/loader.js', @@ -141,9 +152,8 @@ 'lib/internal/safe_globals.js', 'lib/internal/net.js', 'lib/internal/options.js', - 'lib/internal/policy/sri.js', 'lib/internal/policy/manifest.js', - 'lib/internal/print_help.js', + 'lib/internal/policy/sri.js', 'lib/internal/priority_queue.js', 'lib/internal/process/esm_loader.js', 'lib/internal/process/execution.js', diff --git a/src/env.cc b/src/env.cc index 78ed42f89af9fc..8ecab92df73f1f 100644 --- a/src/env.cc +++ b/src/env.cc @@ -332,9 +332,20 @@ void Environment::Start(bool start_profiler_idle_notifier) { uv_key_set(&thread_local_env, this); } -MaybeLocal Environment::CreateProcessObject( +MaybeLocal Environment::ProcessCliArgs( const std::vector& args, const std::vector& exec_args) { + if (args.size() > 1) { + std::string first_arg = args[1]; + if (first_arg == "inspect") { + execution_mode_ = ExecutionMode::kInspect; + } else if (first_arg == "debug") { + execution_mode_ = ExecutionMode::kDebug; + } else if (first_arg != "-") { + execution_mode_ = ExecutionMode::kRunMainModule; + } + } + if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( TRACING_CATEGORY_NODE1(environment)) != 0) { auto traced_value = tracing::TracedValue::Create(); diff --git a/src/env.h b/src/env.h index 0d44c774184059..d42ef33c1534b0 100644 --- a/src/env.h +++ b/src/env.h @@ -315,7 +315,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(write_host_object_string, "_writeHostObject") \ V(write_queue_size_string, "writeQueueSize") \ V(x_forwarded_string, "x-forwarded-for") \ - V(zero_return_string, "ZERO_RETURN") \ + V(zero_return_string, "ZERO_RETURN") #define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ V(as_external, v8::External) \ @@ -355,11 +355,13 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(http2session_on_stream_trailers_function, v8::Function) \ V(http2settings_constructor_template, v8::ObjectTemplate) \ V(http2stream_constructor_template, v8::ObjectTemplate) \ + V(internal_binding_loader, v8::Function) \ V(immediate_callback_function, v8::Function) \ V(inspector_console_extension_installer, v8::Function) \ V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \ V(message_port, v8::Object) \ V(message_port_constructor_template, v8::FunctionTemplate) \ + V(native_module_require, v8::Function) \ V(performance_entry_callback, v8::Function) \ V(performance_entry_template, v8::Function) \ V(pipe_constructor_template, v8::FunctionTemplate) \ @@ -371,7 +373,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(script_data_constructor_function, v8::Function) \ V(secure_context_constructor_template, v8::FunctionTemplate) \ V(shutdown_wrap_template, v8::ObjectTemplate) \ - V(start_execution_function, v8::Function) \ V(tcp_constructor_template, v8::FunctionTemplate) \ V(tick_callback_function, v8::Function) \ V(timers_callback_function, v8::Function) \ @@ -613,7 +614,7 @@ class Environment { ~Environment(); void Start(bool start_profiler_idle_notifier); - v8::MaybeLocal CreateProcessObject( + v8::MaybeLocal ProcessCliArgs( const std::vector& args, const std::vector& exec_args); @@ -930,6 +931,24 @@ class Environment { inline std::shared_ptr options(); inline std::shared_ptr inspector_host_port(); + enum class ExecutionMode { + kDefault, + kInspect, // node inspect + kDebug, // node debug + kPrintHelp, // node --help + kPrintBashCompletion, // node --completion-bash + kProfProcess, // node --prof-process + kEvalString, // node --eval without --interactive + kCheckSyntax, // node --check (incompatible with --eval) + kRepl, + kEvalStdin, + kRunMainModule + }; + + inline ExecutionMode execution_mode() { return execution_mode_; } + + inline void set_execution_mode(ExecutionMode mode) { execution_mode_ = mode; } + private: inline void CreateImmediate(native_immediate_callback cb, void* data, @@ -939,6 +958,7 @@ class Environment { inline void ThrowError(v8::Local (*fun)(v8::Local), const char* errmsg); + ExecutionMode execution_mode_ = ExecutionMode::kDefault; std::list loaded_addons_; v8::Isolate* const isolate_; IsolateData* const isolate_data_; diff --git a/src/node.cc b/src/node.cc index 881ace6e425a77..12185ced0ed88e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -100,6 +100,7 @@ #if defined(_MSC_VER) #include #include +#define STDIN_FILENO 0 #else #include #include // getrlimit, setrlimit @@ -114,6 +115,7 @@ using v8::Array; using v8::Boolean; using v8::Context; using v8::DEFAULT; +using v8::EscapableHandleScope; using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; @@ -605,8 +607,20 @@ static MaybeLocal ExecuteBootstrapper( const char* id, std::vector>* parameters, std::vector>* arguments) { - MaybeLocal ret = per_process::native_module_loader.CompileAndCall( - env->context(), id, parameters, arguments, env); + EscapableHandleScope scope(env->isolate()); + MaybeLocal maybe_fn = + per_process::native_module_loader.LookupAndCompile( + env->context(), id, parameters, env); + + if (maybe_fn.IsEmpty()) { + return MaybeLocal(); + } + + Local fn = maybe_fn.ToLocalChecked(); + MaybeLocal result = fn->Call(env->context(), + Undefined(env->isolate()), + arguments->size(), + arguments->data()); // If there was an error during bootstrap then it was either handled by the // FatalException handler or it's unrecoverable (e.g. max call stack @@ -615,44 +629,17 @@ static MaybeLocal ExecuteBootstrapper( // There are only two ways to have a stack size > 1: 1) the user manually // called MakeCallback or 2) user awaited during bootstrap, which triggered // _tickCallback(). - if (ret.IsEmpty()) { + if (result.IsEmpty()) { env->async_hooks()->clear_async_id_stack(); } - return ret; -} - -void LoadEnvironment(Environment* env) { - RunBootstrapping(env); - - // To allow people to extend Node in different ways, this hook allows - // one to drop a file lib/_third_party_main.js into the build - // directory which will be executed instead of Node's normal loading. - if (per_process::native_module_loader.Exists("_third_party_main")) { - StartExecution(env, "_third_party_main"); - } else { - // TODO(joyeecheung): create different scripts for different - // execution modes: - // - `main_thread_main.js` when env->is_main_thread() - // - `worker_thread_main.js` when !env->is_main_thread() - // - `run_third_party_main.js` for `_third_party_main` - // - `inspect_main.js` for `node inspect` - // - `mkcodecache_main.js` for the code cache generator - // - `print_help_main.js` for --help - // - `bash_completion_main.js` for --completion-bash - // - `internal/v8_prof_processor` for --prof-process - // And leave bootstrap/node.js dedicated to the setup of the environment. - // We may want to move this switch out of LoadEnvironment, especially for - // the per-process options. - StartExecution(env, nullptr); - } + return scope.EscapeMaybe(result); } -void RunBootstrapping(Environment* env) { +MaybeLocal RunBootstrapping(Environment* env) { CHECK(!env->has_run_bootstrapping_code()); - env->set_has_run_bootstrapping_code(true); - HandleScope handle_scope(env->isolate()); + EscapableHandleScope scope(env->isolate()); Isolate* isolate = env->isolate(); Local context = env->context(); @@ -702,14 +689,24 @@ void RunBootstrapping(Environment* env) { Boolean::New(isolate, env->options()->expose_internals)}; - MaybeLocal loader_exports; // Bootstrap internal loaders - loader_exports = ExecuteBootstrapper( + MaybeLocal loader_exports = ExecuteBootstrapper( env, "internal/bootstrap/loaders", &loaders_params, &loaders_args); if (loader_exports.IsEmpty()) { - return; + return MaybeLocal(); } + Local loader_exports_obj = + loader_exports.ToLocalChecked().As(); + Local internal_binding_loader = + loader_exports_obj->Get(context, env->internal_binding_string()) + .ToLocalChecked(); + env->set_internal_binding_loader(internal_binding_loader.As()); + + Local require = + loader_exports_obj->Get(context, env->require_string()).ToLocalChecked(); + env->set_native_module_require(require.As()); + // process, loaderExports, isMainThread std::vector> node_params = { env->process_string(), @@ -717,43 +714,107 @@ void RunBootstrapping(Environment* env) { FIXED_ONE_BYTE_STRING(isolate, "isMainThread")}; std::vector> node_args = { process, - loader_exports.ToLocalChecked(), + loader_exports_obj, Boolean::New(isolate, env->is_main_thread())}; - Local start_execution; - if (!ExecuteBootstrapper( - env, "internal/bootstrap/node", &node_params, &node_args) - .ToLocal(&start_execution)) { - return; - } + MaybeLocal result = ExecuteBootstrapper( + env, "internal/bootstrap/node", &node_params, &node_args); - if (start_execution->IsFunction()) - env->set_start_execution_function(start_execution.As()); + env->set_has_run_bootstrapping_code(true); + + return scope.EscapeMaybe(result); } -void StartExecution(Environment* env, const char* main_script_id) { - HandleScope handle_scope(env->isolate()); - // We have to use Local<>::New because of the optimized way in which we access - // the object in the env->...() getters, which does not play well with - // resetting the handle while we're accessing the object through the Local<>. - Local start_execution = - Local::New(env->isolate(), env->start_execution_function()); - env->set_start_execution_function(Local()); - - if (start_execution.IsEmpty()) return; - - Local main_script_v; - if (main_script_id == nullptr) { - // TODO(joyeecheung): make this mandatory - we may also create an overload - // for main_script that is a Local. - main_script_v = Undefined(env->isolate()); - } else { - main_script_v = OneByteString(env->isolate(), main_script_id); +void MarkBootstrapComplete(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + env->performance_state()->Mark( + performance::NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); +} + +MaybeLocal StartExecution(Environment* env, const char* main_script_id) { + EscapableHandleScope scope(env->isolate()); + CHECK_NE(main_script_id, nullptr); + + std::vector> parameters = { + env->process_string(), + env->require_string(), + env->internal_binding_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")}; + + std::vector> arguments = { + env->process_object(), + env->native_module_require(), + env->internal_binding_loader(), + env->NewFunctionTemplate(MarkBootstrapComplete) + ->GetFunction(env->context()) + .ToLocalChecked()}; + + MaybeLocal result = + ExecuteBootstrapper(env, main_script_id, ¶meters, &arguments); + return scope.EscapeMaybe(result); +} + +MaybeLocal StartMainThreadExecution(Environment* env) { + // To allow people to extend Node in different ways, this hook allows + // one to drop a file lib/_third_party_main.js into the build + // directory which will be executed instead of Node's normal loading. + if (per_process::native_module_loader.Exists("_third_party_main")) { + return StartExecution(env, "internal/main/run_third_party_main"); + } + + if (env->execution_mode() == Environment::ExecutionMode::kInspect || + env->execution_mode() == Environment::ExecutionMode::kDebug) { + return StartExecution(env, "internal/main/inspect"); + } + + if (per_process::cli_options->print_help) { + env->set_execution_mode(Environment::ExecutionMode::kPrintHelp); + return StartExecution(env, "internal/main/print_help"); + } + + if (per_process::cli_options->print_bash_completion) { + env->set_execution_mode(Environment::ExecutionMode::kPrintBashCompletion); + return StartExecution(env, "internal/main/print_bash_completion"); + } + + if (per_process::cli_options->prof_process) { + env->set_execution_mode(Environment::ExecutionMode::kPrintBashCompletion); + return StartExecution(env, "internal/main/prof_process"); + } + + // -e/--eval without -i/--interactive + if (env->options()->has_eval_string && !env->options()->force_repl) { + env->set_execution_mode(Environment::ExecutionMode::kEvalString); + return StartExecution(env, "internal/main/eval_string"); + } + + if (env->options()->syntax_check_only) { + env->set_execution_mode(Environment::ExecutionMode::kCheckSyntax); + return StartExecution(env, "internal/main/check_syntax"); + } + + if (env->execution_mode() == Environment::ExecutionMode::kRunMainModule) { + return StartExecution(env, "internal/main/run_main_module"); + } + + if (env->options()->force_repl || uv_guess_handle(STDIN_FILENO) == UV_TTY) { + env->set_execution_mode(Environment::ExecutionMode::kRepl); + return StartExecution(env, "internal/main/repl"); } - Local argv[] = {main_script_v}; - USE(start_execution->Call( - env->context(), Undefined(env->isolate()), arraysize(argv), argv)); + env->set_execution_mode(Environment::ExecutionMode::kEvalStdin); + return StartExecution(env, "internal/main/eval_stdin"); +} + +void LoadEnvironment(Environment* env) { + CHECK(env->is_main_thread()); + // TODO(joyeecheung): Not all of the execution modes in + // StartMainThreadExecution() make sense for embedders. Pick the + // useful ones out, and allow embedders to customize the entry + // point more directly without using _third_party_main.js + if (!RunBootstrapping(env).IsEmpty()) { + USE(StartMainThreadExecution(env)); + } } @@ -1180,7 +1241,7 @@ Environment* CreateEnvironment(IsolateData* isolate_data, std::vector exec_args(exec_argv, exec_argv + exec_argc); Environment* env = new Environment(isolate_data, context); env->Start(per_process::v8_is_profiling); - env->CreateProcessObject(args, exec_args); + env->ProcessCliArgs(args, exec_args); return env; } @@ -1220,7 +1281,7 @@ void FreePlatform(MultiIsolatePlatform* platform) { Local NewContext(Isolate* isolate, Local object_template) { - auto context = Context::New(isolate, nullptr, object_template); + Local context = Context::New(isolate, nullptr, object_template); if (context.IsEmpty()) return context; HandleScope handle_scope(isolate); @@ -1233,12 +1294,19 @@ Local NewContext(Isolate* isolate, std::vector> parameters = { FIXED_ONE_BYTE_STRING(isolate, "global")}; - std::vector> arguments = {context->Global()}; - MaybeLocal result = per_process::native_module_loader.CompileAndCall( - context, "internal/per_context", ¶meters, &arguments, nullptr); + Local arguments[] = {context->Global()}; + MaybeLocal maybe_fn = + per_process::native_module_loader.LookupAndCompile( + context, "internal/per_context", ¶meters, nullptr); + if (maybe_fn.IsEmpty()) { + return Local(); + } + Local fn = maybe_fn.ToLocalChecked(); + MaybeLocal result = + fn->Call(context, Undefined(isolate), arraysize(arguments), arguments); + // Execution failed during context creation. + // TODO(joyeecheung): deprecate this signature and return a MaybeLocal. if (result.IsEmpty()) { - // Execution failed during context creation. - // TODO(joyeecheung): deprecate this signature and return a MaybeLocal. return Local(); } } @@ -1255,7 +1323,7 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, Context::Scope context_scope(context); Environment env(isolate_data, context); env.Start(per_process::v8_is_profiling); - env.CreateProcessObject(args, exec_args); + env.ProcessCliArgs(args, exec_args); #if HAVE_INSPECTOR && NODE_USE_V8_PLATFORM CHECK(!env.inspector_agent()->IsListening()); diff --git a/src/node_internals.h b/src/node_internals.h index 0d4fe74ebf3d02..bf66c77e6f043c 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -268,8 +268,9 @@ bool SafeGetenv(const char* key, std::string* text); void DefineZlibConstants(v8::Local target); -void RunBootstrapping(Environment* env); -void StartExecution(Environment* env, const char* main_script_id); +v8::MaybeLocal RunBootstrapping(Environment* env); +v8::MaybeLocal StartExecution(Environment* env, + const char* main_script_id); } // namespace node diff --git a/src/node_native_module.cc b/src/node_native_module.cc index 27456dd54606e4..675495e34bff5a 100644 --- a/src/node_native_module.cc +++ b/src/node_native_module.cc @@ -175,26 +175,6 @@ void NativeModuleLoader::CompileFunction( } } -// TODO(joyeecheung): it should be possible to generate the argument names -// from some special comments for the bootstrapper case. -MaybeLocal NativeModuleLoader::CompileAndCall( - Local context, - const char* id, - std::vector>* parameters, - std::vector>* arguments, - Environment* optional_env) { - Isolate* isolate = context->GetIsolate(); - MaybeLocal compiled = - per_process::native_module_loader.LookupAndCompile( - context, id, parameters, nullptr); - if (compiled.IsEmpty()) { - return MaybeLocal(); - } - Local fn = compiled.ToLocalChecked().As(); - return fn->Call( - context, v8::Null(isolate), arguments->size(), arguments->data()); -} - MaybeLocal NativeModuleLoader::CompileAsModule(Environment* env, const char* id) { std::vector> parameters = {env->exports_string(), diff --git a/src/node_native_module.h b/src/node_native_module.h index 62c417a0b61474..be1fc92a7672f3 100644 --- a/src/node_native_module.h +++ b/src/node_native_module.h @@ -42,20 +42,17 @@ class NativeModuleLoader { // Returns config.gypi as a JSON string v8::Local GetConfigString(v8::Isolate* isolate) const; - // Run a script with JS source bundled inside the binary as if it's wrapped - // in a function called with a null receiver and arguments specified in C++. - // The returned value is empty if an exception is encountered. - // JS code run with this method can assume that their top-level - // declarations won't affect the global scope. - v8::MaybeLocal CompileAndCall( + bool Exists(const char* id); + + // For bootstrappers optional_env may be a nullptr. + // If an exception is encountered (e.g. source code contains + // syntax error), the returned value is empty. + v8::MaybeLocal LookupAndCompile( v8::Local context, const char* id, std::vector>* parameters, - std::vector>* arguments, Environment* optional_env); - bool Exists(const char* id); - private: static void GetCacheUsage(const v8::FunctionCallbackInfo& args); // Passing ids of builtin module source code into JS land as @@ -87,15 +84,6 @@ class NativeModuleLoader { static v8::MaybeLocal CompileAsModule(Environment* env, const char* id); - // For bootstrappers optional_env may be a nullptr. - // If an exception is encountered (e.g. source code contains - // syntax error), the returned value is empty. - v8::MaybeLocal LookupAndCompile( - v8::Local context, - const char* id, - std::vector>* parameters, - Environment* optional_env); - NativeModuleRecordMap source_; NativeModuleCacheMap code_cache_; UnionBytes config_; diff --git a/src/node_options.cc b/src/node_options.cc index 8bd8b827faa109..eca56144618888 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -205,11 +205,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { AddOption("--preserve-symlinks-main", "preserve symbolic links when resolving the main module", &EnvironmentOptions::preserve_symlinks_main); - AddOption("--prof-process", - "process V8 profiler output generated using --prof", - &EnvironmentOptions::prof_process); - // Options after --prof-process are passed through to the prof processor. - AddAlias("--prof-process", { "--prof-process", "--" }); AddOption("--redirect-warnings", "write warnings to file instead of stderr", &EnvironmentOptions::redirect_warnings, @@ -387,6 +382,11 @@ PerProcessOptionsParser::PerProcessOptionsParser() { AddOption("--v8-options", "print V8 command line options", &PerProcessOptions::print_v8_help); + AddOption("--prof-process", + "process V8 profiler output generated using --prof", + &PerProcessOptions::prof_process); + // Options after --prof-process are passed through to the prof processor. + AddAlias("--prof-process", {"--prof-process", "--"}); #ifdef NODE_HAVE_I18N_SUPPORT AddOption("--icu-data-dir", diff --git a/src/node_options.h b/src/node_options.h index 50c66ce890bf4c..b0759b6d74f870 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -107,7 +107,6 @@ class EnvironmentOptions : public Options { bool pending_deprecation = false; bool preserve_symlinks = false; bool preserve_symlinks_main = false; - bool prof_process = false; std::string redirect_warnings; bool throw_deprecation = false; bool trace_deprecation = false; @@ -175,6 +174,7 @@ class PerProcessOptions : public Options { bool print_help = false; bool print_v8_help = false; bool print_version = false; + bool prof_process = false; #ifdef NODE_HAVE_I18N_SUPPORT std::string icu_data_dir; diff --git a/src/node_process_object.cc b/src/node_process_object.cc index c1f8806110ffef..0e10e59e8a29b2 100644 --- a/src/node_process_object.cc +++ b/src/node_process_object.cc @@ -238,7 +238,7 @@ MaybeLocal CreateProcessObject( // --prof-process // TODO(addaleax): Remove this. - if (env->options()->prof_process) { + if (per_process::cli_options->prof_process) { READONLY_PROPERTY(process, "profProcess", True(env->isolate())); } diff --git a/src/node_worker.cc b/src/node_worker.cc index e5ba438bc1501c..4b78d653929d4b 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -133,8 +133,8 @@ Worker::Worker(Environment* env, env_->set_thread_id(thread_id_); env_->Start(env->profiler_idle_notifier_started()); - env_->CreateProcessObject(std::vector{}, - std::vector{}); + env_->ProcessCliArgs(std::vector{}, + std::vector{}); // Done while on the parent thread AddWorkerInspector(env, env_.get(), thread_id_, url_); } @@ -192,10 +192,10 @@ void Worker::Run() { HandleScope handle_scope(isolate_); Environment::AsyncCallbackScope callback_scope(env_.get()); env_->async_hooks()->push_async_ids(1, 0); - RunBootstrapping(env_.get()); - // TODO(joyeecheung): create a main script for worker threads - // that starts listening on the message port. - StartExecution(env_.get(), nullptr); + if (!RunBootstrapping(env_.get()).IsEmpty()) { + USE(StartExecution(env_.get(), "internal/main/worker_thread")); + } + env_->async_hooks()->pop_async_id(1); Debug(this, "Loaded environment for worker %llu", thread_id_); diff --git a/test/code-cache/test-code-cache.js b/test/code-cache/test-code-cache.js index 54bd0b0e2a66be..6232f1ae61b324 100644 --- a/test/code-cache/test-code-cache.js +++ b/test/code-cache/test-code-cache.js @@ -9,7 +9,7 @@ const { isMainThread } = require('../common'); const assert = require('assert'); const { cachableBuiltins, - cannotUseCache + cannotBeRequired } = require('internal/bootstrap/cache'); const { @@ -60,7 +60,7 @@ if (process.config.variables.node_code_cache_path === undefined) { ); for (const key of loadedModules) { - if (cannotUseCache.includes(key)) { + if (cannotBeRequired.includes(key)) { assert(compiledWithoutCache.has(key), `"${key}" should've been compiled without code cache`); } else { diff --git a/test/message/assert_throws_stack.out b/test/message/assert_throws_stack.out index cf96ee42c98cb7..0457670d6a317c 100644 --- a/test/message/assert_throws_stack.out +++ b/test/message/assert_throws_stack.out @@ -17,4 +17,3 @@ AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: at * at * at * - at * diff --git a/test/message/core_line_numbers.out b/test/message/core_line_numbers.out index 59953132fa0542..0336e5b451651e 100644 --- a/test/message/core_line_numbers.out +++ b/test/message/core_line_numbers.out @@ -12,4 +12,4 @@ RangeError: Invalid input at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* diff --git a/test/message/error_exit.out b/test/message/error_exit.out index 7e0112d586cecb..3364210099ca75 100644 --- a/test/message/error_exit.out +++ b/test/message/error_exit.out @@ -14,5 +14,4 @@ AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* diff --git a/test/message/eval_messages.out b/test/message/eval_messages.out index 3af7c9792121b9..0d2e68f3d79b04 100644 --- a/test/message/eval_messages.out +++ b/test/message/eval_messages.out @@ -9,8 +9,7 @@ SyntaxError: Strict mode code may not include a with statement at Object. ([eval]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/eval_string.js:*:* 42 42 [eval]:1 @@ -24,8 +23,7 @@ Error: hello at Object. ([eval]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/eval_string.js:*:* [eval]:1 throw new Error("hello") @@ -38,8 +36,7 @@ Error: hello at Object. ([eval]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/eval_string.js:*:* 100 [eval]:1 var x = 100; y = x; @@ -52,8 +49,7 @@ ReferenceError: y is not defined at Object. ([eval]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/eval_string.js:*:* [eval]:1 var ______________________________________________; throw 10 diff --git a/test/message/events_unhandled_error_common_trace.out b/test/message/events_unhandled_error_common_trace.out index 331d669272320c..0d64143c67f865 100644 --- a/test/message/events_unhandled_error_common_trace.out +++ b/test/message/events_unhandled_error_common_trace.out @@ -12,11 +12,10 @@ Error: foo:bar at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* Emitted 'error' event at: at quux (*events_unhandled_error_common_trace.js:*:*) at Object. (*events_unhandled_error_common_trace.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) [... lines matching original stack trace ...] - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* diff --git a/test/message/events_unhandled_error_nexttick.out b/test/message/events_unhandled_error_nexttick.out index 8875eda532882f..4132ae9f3bc1be 100644 --- a/test/message/events_unhandled_error_nexttick.out +++ b/test/message/events_unhandled_error_nexttick.out @@ -10,12 +10,10 @@ Error at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* Emitted 'error' event at: at process.nextTick (*events_unhandled_error_nexttick.js:*:*) at processTicksAndRejections (internal/process/next_tick.js:*:*) at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* diff --git a/test/message/events_unhandled_error_sameline.out b/test/message/events_unhandled_error_sameline.out index ff024c826eb5da..f877b254aafe97 100644 --- a/test/message/events_unhandled_error_sameline.out +++ b/test/message/events_unhandled_error_sameline.out @@ -10,10 +10,9 @@ Error at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* Emitted 'error' event at: at Object. (*events_unhandled_error_sameline.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) [... lines matching original stack trace ...] - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* diff --git a/test/message/nexttick_throw.out b/test/message/nexttick_throw.out index 4bcdcaa62c8bc5..7aa38a0424bd99 100644 --- a/test/message/nexttick_throw.out +++ b/test/message/nexttick_throw.out @@ -7,5 +7,4 @@ ReferenceError: undefined_reference_error_maker is not defined at processTicksAndRejections (internal/process/next_tick.js:*:*) at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:*:*) at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) - at executeUserCode (internal/bootstrap/node.js:*:*) - at startMainThreadExecution (internal/bootstrap/node.js:*:*) + at internal/main/run_main_module.js:*:* diff --git a/test/message/stdin_messages.out b/test/message/stdin_messages.out index 2bf935d7cbd16f..837e9017d7f947 100644 --- a/test/message/stdin_messages.out +++ b/test/message/stdin_messages.out @@ -2,6 +2,7 @@ [stdin]:1 with(this){__filename} ^^^^ + SyntaxError: Strict mode code may not include a with statement at new Script (vm.js:*) at createScript (vm.js:*) @@ -9,10 +10,10 @@ SyntaxError: Strict mode code may not include a with statement at Object. ([stdin]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at Socket.process.stdin.on (internal/bootstrap/node.js:*:*) + at readStdin (internal/main/eval_stdin.js:*:*) + at Socket.process.stdin.on (internal/process/execution.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) - at processTicksAndRejections (internal/process/next_tick.js:*:*) 42 42 [stdin]:1 @@ -26,10 +27,10 @@ Error: hello at Object. ([stdin]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at Socket.process.stdin.on (internal/bootstrap/node.js:*:*) + at readStdin (internal/main/eval_stdin.js:*:*) + at Socket.process.stdin.on (internal/process/execution.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) - at processTicksAndRejections (internal/process/next_tick.js:*:*) [stdin]:1 throw new Error("hello") ^ @@ -41,10 +42,10 @@ Error: hello at Object. ([stdin]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at Socket.process.stdin.on (internal/bootstrap/node.js:*:*) + at readStdin (internal/main/eval_stdin.js:*:*) + at Socket.process.stdin.on (internal/process/execution.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) - at processTicksAndRejections (internal/process/next_tick.js:*:*) 100 [stdin]:1 var x = 100; y = x; @@ -57,10 +58,10 @@ ReferenceError: y is not defined at Object. ([stdin]-wrapper:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) at evalScript (internal/process/execution.js:*:*) - at Socket.process.stdin.on (internal/bootstrap/node.js:*:*) + at readStdin (internal/main/eval_stdin.js:*:*) + at Socket.process.stdin.on (internal/process/execution.js:*:*) at Socket.emit (events.js:*:*) at endReadableNT (_stream_readable.js:*:*) - at processTicksAndRejections (internal/process/next_tick.js:*:*) [stdin]:1 var ______________________________________________; throw 10 diff --git a/test/message/unhandled_promise_trace_warnings.out b/test/message/unhandled_promise_trace_warnings.out index 1f88a136a9aa13..60c08805f5cd54 100644 --- a/test/message/unhandled_promise_trace_warnings.out +++ b/test/message/unhandled_promise_trace_warnings.out @@ -13,8 +13,6 @@ at * at * at * - at * - at * (node:*) Error: This was rejected at * (*test*message*unhandled_promise_trace_warnings.js:*) at * @@ -24,7 +22,6 @@ at * at * at * - at * (node:*) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. at * at * @@ -33,7 +30,6 @@ at * at * at * - at * (node:*) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) at handledRejection (internal/process/promises.js:*) at promiseRejectHandler (internal/process/promises.js:*) diff --git a/test/message/util_inspect_error.out b/test/message/util_inspect_error.out index 406d8112ce2599..31b65eb2e2bf3c 100644 --- a/test/message/util_inspect_error.out +++ b/test/message/util_inspect_error.out @@ -9,7 +9,6 @@ at * at * at * - at * nested: { err: Error: foo @@ -22,7 +21,6 @@ at * at * at * - at * } } { err: Error: foo bar @@ -34,7 +32,6 @@ at * at * at * - at *, nested: { err: Error: foo bar @@ -46,7 +43,6 @@ at * at * at * - at * } } { Error: foo @@ -59,5 +55,4 @@ bar at * at * at * - at * foo: 'bar' } From 857d84f564a25a695789d189e5d3c8cd3616e6ab Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 16 Jan 2019 00:08:20 +0800 Subject: [PATCH 2/3] worker: move worker thread setup code into the main script This patch directly inlines `createMessageHandler()` and `createWorkerFatalExeception()` in the new `lib/internal/main/worker_thread.js` since the implementation of the two methods are related to the execution flow of workers. --- lib/internal/main/worker_thread.js | 116 ++++++++++++++++++--- lib/internal/process/worker_thread_only.js | 107 +------------------ 2 files changed, 103 insertions(+), 120 deletions(-) diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 62a4bb1b7dd77e..94d0e613e8ce38 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -10,30 +10,118 @@ const { } = require('internal/bootstrap/pre_execution'); const { - getEnvMessagePort, - threadId + threadId, + getEnvMessagePort } = internalBinding('worker'); const { - createMessageHandler, - createWorkerFatalExeception -} = require('internal/process/worker_thread_only'); + messageTypes: { + // Messages that may be received by workers + LOAD_SCRIPT, + // Messages that may be posted from workers + UP_AND_RUNNING, + ERROR_MESSAGE, + COULD_NOT_SERIALIZE_ERROR, + // Messages that may be either received or posted + STDIO_PAYLOAD, + STDIO_WANTS_MORE_DATA, + }, + kStdioWantsMoreDataCallback +} = require('internal/worker/io'); +const { + fatalException: originalFatalException +} = require('internal/process/execution'); + +const publicWorker = require('worker_threads'); const debug = require('util').debuglog('worker'); -debug(`[${threadId}] is setting up worker child environment`); -function prepareUserCodeExecution() { - initializeClusterIPC(); - initializeESMLoader(); - loadPreloadModules(); -} +debug(`[${threadId}] is setting up worker child environment`); // Set up the message port and start listening const port = getEnvMessagePort(); -port.on('message', createMessageHandler(port, prepareUserCodeExecution)); -port.start(); + +port.on('message', (message) => { + if (message.type === LOAD_SCRIPT) { + const { + filename, + doEval, + workerData, + publicPort, + manifestSrc, + manifestURL, + hasStdin + } = message; + if (manifestSrc) { + require('internal/process/policy').setup(manifestSrc, manifestURL); + } + initializeClusterIPC(); + initializeESMLoader(); + loadPreloadModules(); + publicWorker.parentPort = publicPort; + publicWorker.workerData = workerData; + + if (!hasStdin) + process.stdin.push(null); + + debug(`[${threadId}] starts worker script ${filename} ` + + `(eval = ${eval}) at cwd = ${process.cwd()}`); + port.unref(); + port.postMessage({ type: UP_AND_RUNNING }); + if (doEval) { + const { evalScript } = require('internal/process/execution'); + evalScript('[worker eval]', filename); + } else { + process.argv[1] = filename; // script filename + require('module').runMain(); + } + return; + } else if (message.type === STDIO_PAYLOAD) { + const { stream, chunk, encoding } = message; + process[stream].push(chunk, encoding); + return; + } else if (message.type === STDIO_WANTS_MORE_DATA) { + const { stream } = message; + process[stream][kStdioWantsMoreDataCallback](); + return; + } + + require('assert').fail(`Unknown worker message type ${message.type}`); +}); // Overwrite fatalException -process._fatalException = createWorkerFatalExeception(port); +process._fatalException = (error) => { + debug(`[${threadId}] gets fatal exception`); + let caught = false; + try { + caught = originalFatalException.call(this, error); + } catch (e) { + error = e; + } + debug(`[${threadId}] fatal exception caught = ${caught}`); + + if (!caught) { + let serialized; + try { + const { serializeError } = require('internal/error-serdes'); + serialized = serializeError(error); + } catch {} + debug(`[${threadId}] fatal exception serialized = ${!!serialized}`); + if (serialized) + port.postMessage({ + type: ERROR_MESSAGE, + error: serialized + }); + else + port.postMessage({ type: COULD_NOT_SERIALIZE_ERROR }); + + const { clearAsyncIdStack } = require('internal/async_hooks'); + clearAsyncIdStack(); + + process.exit(); + } +}; markBootstrapComplete(); + +port.start(); diff --git a/lib/internal/process/worker_thread_only.js b/lib/internal/process/worker_thread_only.js index 2171d2b586a4ca..f05d5e932bebf2 100644 --- a/lib/internal/process/worker_thread_only.js +++ b/lib/internal/process/worker_thread_only.js @@ -3,13 +3,10 @@ // This file contains process bootstrappers that can only be // run in the worker thread. const { - getEnvMessagePort, - threadId + getEnvMessagePort } = internalBinding('worker'); const { - messageTypes, - kStdioWantsMoreDataCallback, kWaitingStreams, ReadableWorkerStdio, WritableWorkerStdio @@ -18,15 +15,6 @@ const { const { codes: { ERR_WORKER_UNSUPPORTED_OPERATION } } = require('internal/errors'); - -let debuglog; -function debug(...args) { - if (!debuglog) { - debuglog = require('util').debuglog('worker'); - } - return debuglog(...args); -} - const workerStdio = {}; function initializeWorkerStdio() { @@ -43,97 +31,6 @@ function initializeWorkerStdio() { }; } -function createMessageHandler(port, prepareUserCodeExecution) { - const publicWorker = require('worker_threads'); - - return function(message) { - if (message.type === messageTypes.LOAD_SCRIPT) { - const { - filename, - doEval, - workerData, - publicPort, - manifestSrc, - manifestURL, - hasStdin - } = message; - if (manifestSrc) { - require('internal/process/policy').setup(manifestSrc, manifestURL); - } - prepareUserCodeExecution(); - publicWorker.parentPort = publicPort; - publicWorker.workerData = workerData; - - if (!hasStdin) - workerStdio.stdin.push(null); - - debug(`[${threadId}] starts worker script ${filename} ` + - `(eval = ${eval}) at cwd = ${process.cwd()}`); - port.unref(); - port.postMessage({ type: messageTypes.UP_AND_RUNNING }); - if (doEval) { - const { evalScript } = require('internal/process/execution'); - evalScript('[worker eval]', filename); - } else { - process.argv[1] = filename; // script filename - require('module').runMain(); - } - return; - } else if (message.type === messageTypes.STDIO_PAYLOAD) { - const { stream, chunk, encoding } = message; - workerStdio[stream].push(chunk, encoding); - return; - } else if (message.type === messageTypes.STDIO_WANTS_MORE_DATA) { - const { stream } = message; - workerStdio[stream][kStdioWantsMoreDataCallback](); - return; - } - - require('assert').fail(`Unknown worker message type ${message.type}`); - }; -} - -// XXX(joyeecheung): this has to be returned as an anonymous function -// wrapped in a closure, see the comment of the original -// process._fatalException in lib/internal/process/execution.js -function createWorkerFatalExeception(port) { - const { - fatalException: originalFatalException - } = require('internal/process/execution'); - - return (error) => { - debug(`[${threadId}] gets fatal exception`); - let caught = false; - try { - caught = originalFatalException.call(this, error); - } catch (e) { - error = e; - } - debug(`[${threadId}] fatal exception caught = ${caught}`); - - if (!caught) { - let serialized; - try { - const { serializeError } = require('internal/error-serdes'); - serialized = serializeError(error); - } catch {} - debug(`[${threadId}] fatal exception serialized = ${!!serialized}`); - if (serialized) - port.postMessage({ - type: messageTypes.ERROR_MESSAGE, - error: serialized - }); - else - port.postMessage({ type: messageTypes.COULD_NOT_SERIALIZE_ERROR }); - - const { clearAsyncIdStack } = require('internal/async_hooks'); - clearAsyncIdStack(); - - process.exit(); - } - }; -} - // The execution of this function itself should not cause any side effects. function wrapProcessMethods(binding) { function umask(mask) { @@ -150,7 +47,5 @@ function wrapProcessMethods(binding) { module.exports = { initializeWorkerStdio, - createMessageHandler, - createWorkerFatalExeception, wrapProcessMethods }; From 455c9234591a88e24f5351d889e215cade3e3ebb Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 29 Jan 2019 21:01:45 +0800 Subject: [PATCH 3/3] fixup! process: split execution into main scripts --- src/node.cc | 2 +- src/node_options.cc | 10 +++++----- src/node_options.h | 2 +- src/node_process_object.cc | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/node.cc b/src/node.cc index 12185ced0ed88e..e67b16af83f0df 100644 --- a/src/node.cc +++ b/src/node.cc @@ -777,7 +777,7 @@ MaybeLocal StartMainThreadExecution(Environment* env) { return StartExecution(env, "internal/main/print_bash_completion"); } - if (per_process::cli_options->prof_process) { + if (env->options()->prof_process) { env->set_execution_mode(Environment::ExecutionMode::kPrintBashCompletion); return StartExecution(env, "internal/main/prof_process"); } diff --git a/src/node_options.cc b/src/node_options.cc index eca56144618888..8bd8b827faa109 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -205,6 +205,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { AddOption("--preserve-symlinks-main", "preserve symbolic links when resolving the main module", &EnvironmentOptions::preserve_symlinks_main); + AddOption("--prof-process", + "process V8 profiler output generated using --prof", + &EnvironmentOptions::prof_process); + // Options after --prof-process are passed through to the prof processor. + AddAlias("--prof-process", { "--prof-process", "--" }); AddOption("--redirect-warnings", "write warnings to file instead of stderr", &EnvironmentOptions::redirect_warnings, @@ -382,11 +387,6 @@ PerProcessOptionsParser::PerProcessOptionsParser() { AddOption("--v8-options", "print V8 command line options", &PerProcessOptions::print_v8_help); - AddOption("--prof-process", - "process V8 profiler output generated using --prof", - &PerProcessOptions::prof_process); - // Options after --prof-process are passed through to the prof processor. - AddAlias("--prof-process", {"--prof-process", "--"}); #ifdef NODE_HAVE_I18N_SUPPORT AddOption("--icu-data-dir", diff --git a/src/node_options.h b/src/node_options.h index b0759b6d74f870..50c66ce890bf4c 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -107,6 +107,7 @@ class EnvironmentOptions : public Options { bool pending_deprecation = false; bool preserve_symlinks = false; bool preserve_symlinks_main = false; + bool prof_process = false; std::string redirect_warnings; bool throw_deprecation = false; bool trace_deprecation = false; @@ -174,7 +175,6 @@ class PerProcessOptions : public Options { bool print_help = false; bool print_v8_help = false; bool print_version = false; - bool prof_process = false; #ifdef NODE_HAVE_I18N_SUPPORT std::string icu_data_dir; diff --git a/src/node_process_object.cc b/src/node_process_object.cc index 0e10e59e8a29b2..c1f8806110ffef 100644 --- a/src/node_process_object.cc +++ b/src/node_process_object.cc @@ -238,7 +238,7 @@ MaybeLocal CreateProcessObject( // --prof-process // TODO(addaleax): Remove this. - if (per_process::cli_options->prof_process) { + if (env->options()->prof_process) { READONLY_PROPERTY(process, "profProcess", True(env->isolate())); }