From dc81538d3144b61148f65544526163ccf4a80ca3 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 22 Sep 2022 23:11:04 +0800 Subject: [PATCH] src: consolidate exit codes in the code base Add an ExitCode enum class and use it throughout the code base instead of hard-coding the exit codes everywhere. At the moment, the exit codes used in many places do not actually conform to what the documentation describes. With the new enums (which are also available to the JS land as constants in an internal binding) we could migrate to a more consistent usage of the codes, and eventually expose the constants to the user land when they are stable enough. --- lib/internal/async_hooks.js | 4 +- lib/internal/cluster/child.js | 8 +- lib/internal/debugger/inspect.js | 14 +- lib/internal/main/repl.js | 5 +- lib/internal/main/test_runner.js | 3 +- lib/internal/main/watch_mode.js | 8 +- lib/internal/main/worker_thread.js | 3 +- .../modules/esm/handle_process_exit.js | 4 +- lib/internal/policy/manifest.js | 4 +- lib/internal/process/execution.js | 5 +- lib/internal/process/per_thread.js | 5 +- lib/internal/process/promises.js | 5 +- lib/internal/test_runner/harness.js | 4 +- lib/internal/test_runner/runner.js | 3 +- node.gyp | 1 + src/api/embed_helpers.cc | 16 +- src/api/environment.cc | 22 ++- src/api/hooks.cc | 20 ++- src/env-inl.h | 2 +- src/env.cc | 8 +- src/env.h | 15 +- src/node.cc | 145 +++++++++++------- src/node_errors.cc | 19 ++- src/node_errors.h | 1 - src/node_exit_code.h | 53 +++++++ src/node_internals.h | 5 +- src/node_main.cc | 6 +- src/node_main_instance.cc | 19 ++- src/node_main_instance.h | 7 +- src/node_process_methods.cc | 7 +- src/node_snapshot_builder.h | 13 +- src/node_snapshotable.cc | 47 +++--- src/node_worker.cc | 46 ++++-- src/node_worker.h | 5 +- src/util.h | 2 +- tools/snapshot/node_mksnapshot.cc | 8 +- 36 files changed, 368 insertions(+), 174 deletions(-) create mode 100644 src/node_exit_code.h diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js index 25f0075bf45a07..d0ded3d6709f9b 100644 --- a/lib/internal/async_hooks.js +++ b/lib/internal/async_hooks.js @@ -8,6 +8,8 @@ const { Symbol, } = primordials; +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); + const promiseHooks = require('internal/promise_hooks'); const async_wrap = internalBinding('async_wrap'); @@ -171,7 +173,7 @@ function fatalError(e) { if (getOptionValue('--abort-on-uncaught-exception')) { process.abort(); } - process.exit(1); + process.exit(kGenericUserError); } function lookupPublicResource(resource) { diff --git a/lib/internal/cluster/child.js b/lib/internal/cluster/child.js index f960878a70aca3..48e81d8744310b 100644 --- a/lib/internal/cluster/child.js +++ b/lib/internal/cluster/child.js @@ -15,6 +15,8 @@ const EventEmitter = require('events'); const { owner_symbol } = require('internal/async_hooks').symbols; const Worker = require('internal/cluster/worker'); const { internal, sendHelper } = require('internal/cluster/utils'); +const { exitCodes: { kNoFailure } } = internalBinding('errors'); + const cluster = new EventEmitter(); const handles = new SafeMap(); const indexes = new SafeMap(); @@ -43,7 +45,7 @@ cluster._setupWorker = function() { if (!worker.exitedAfterDisconnect) { // Unexpected disconnect, primary exited, or some such nastiness, so // worker exits immediately. - process.exit(0); + process.exit(kNoFailure); } }); @@ -278,10 +280,10 @@ Worker.prototype.destroy = function() { this.exitedAfterDisconnect = true; if (!this.isConnected()) { - process.exit(0); + process.exit(kNoFailure); } else { this.state = 'destroying'; send({ act: 'exitedAfterDisconnect' }, () => process.disconnect()); - process.once('disconnect', () => process.exit(0)); + process.once('disconnect', () => process.exit(kNoFailure)); } }; diff --git a/lib/internal/debugger/inspect.js b/lib/internal/debugger/inspect.js index e006f513f9b93f..936685d1c295f7 100644 --- a/lib/internal/debugger/inspect.js +++ b/lib/internal/debugger/inspect.js @@ -44,6 +44,9 @@ const { 0: InspectClient, 1: createRepl } = const debuglog = util.debuglog('inspect'); const { ERR_DEBUGGER_STARTUP_ERROR } = require('internal/errors').codes; +const { exitCodes: { + kGenericUserError, kNoFailure +} } = internalBinding('errors'); async function portIsFree(host, port, timeout = 3000) { if (port === 0) return; // Binding to a random port. @@ -167,7 +170,7 @@ class NodeInspector { // Handle all possible exits process.on('exit', () => this.killChild()); - const exitCodeZero = () => process.exit(0); + const exitCodeZero = () => process.exit(kNoFailure); process.once('SIGTERM', exitCodeZero); process.once('SIGHUP', exitCodeZero); @@ -234,7 +237,7 @@ class NodeInspector { } } this.stdout.write(' failed to connect, please retry\n'); - process.exit(1); + process.exit(kGenericUserError); } clearLine() { @@ -314,7 +317,7 @@ function parseArgv(args) { } catch (e) { if (e.code === 'ESRCH') { console.error(`Target process: ${pid} doesn't exist.`); - process.exit(1); + process.exit(kGenericUserError); } throw e; } @@ -337,7 +340,8 @@ function startInspect(argv = ArrayPrototypeSlice(process.argv, 2), console.error(` ${invokedAs} :`); console.error(` ${invokedAs} --port=`); console.error(` ${invokedAs} -p `); - process.exit(1); + // TODO(joyeecheung): should be kInvalidCommandLineArgument. + process.exit(kGenericUserError); } const options = parseArgv(argv); @@ -355,7 +359,7 @@ function startInspect(argv = ArrayPrototypeSlice(process.argv, 2), console.error(e.message); } if (inspector.child) inspector.child.kill(); - process.exit(1); + process.exit(kGenericUserError); } process.on('uncaughtException', handleUnexpectedError); diff --git a/lib/internal/main/repl.js b/lib/internal/main/repl.js index 7da68dc05e84b4..1fb48774ca6849 100644 --- a/lib/internal/main/repl.js +++ b/lib/internal/main/repl.js @@ -17,6 +17,8 @@ const console = require('internal/console/global'); const { getOptionValue } = require('internal/options'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); + prepareMainThreadExecution(); markBootstrapComplete(); @@ -31,7 +33,8 @@ if (process.env.NODE_REPL_EXTERNAL_MODULE) { // If we can't write to stderr, we'd like to make this a noop, // so use console.error. console.error('Cannot specify --input-type for REPL'); - process.exit(1); + // TODO(joyeecheung): should be kInvalidCommandLineArgument. + process.exit(kGenericUserError); } esmLoader.loadESM(() => { diff --git a/lib/internal/main/test_runner.js b/lib/internal/main/test_runner.js index 63a93d9dc0f4f0..12753f3bbbf6dc 100644 --- a/lib/internal/main/test_runner.js +++ b/lib/internal/main/test_runner.js @@ -5,6 +5,7 @@ const { } = require('internal/process/pre_execution'); const { isUsingInspector } = require('internal/util/inspector'); const { run } = require('internal/test_runner/runner'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); prepareMainThreadExecution(false); markBootstrapComplete(); @@ -22,5 +23,5 @@ if (isUsingInspector()) { const tapStream = run({ concurrency, inspectPort }); tapStream.pipe(process.stdout); tapStream.once('test:fail', () => { - process.exitCode = 1; + process.exitCode = kGenericUserError; }); diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 93aa42a1e7b95a..9b5295adab8de1 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -12,7 +12,10 @@ const { prepareMainThreadExecution, markBootstrapComplete } = require('internal/process/pre_execution'); -const { triggerUncaughtException } = internalBinding('errors'); +const { + triggerUncaughtException, + exitCodes: { kNoFailure } +} = internalBinding('errors'); const { getOptionValue } = require('internal/options'); const { emitExperimentalWarning } = require('internal/util'); const { FilesWatcher } = require('internal/watch_mode/files_watcher'); @@ -24,7 +27,6 @@ const { setTimeout, clearTimeout } = require('timers'); const { resolve } = require('path'); const { once, on } = require('events'); - prepareMainThreadExecution(false, false); markBootstrapComplete(); @@ -125,7 +127,7 @@ function signalHandler(signal) { return async () => { watcher.clear(); const exitCode = await killAndWait(signal, true); - process.exit(exitCode ?? 0); + process.exit(exitCode ?? kNoFailure); }; } process.on('SIGTERM', signalHandler('SIGTERM')); diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index f7ead4084ed4ed..ed22270e9f801f 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -66,6 +66,7 @@ let debug = require('internal/util/debuglog').debuglog('worker', (fn) => { }); const assert = require('internal/assert'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); patchProcessObject(); setupInspectorHooks(); @@ -234,7 +235,7 @@ function workerOnGlobalUncaughtException(error, fromPromise) { if (!process._exiting) { try { process._exiting = true; - process.exitCode = 1; + process.exitCode = kGenericUserError; if (!handlerThrew) { process.emit('exit', process.exitCode); } diff --git a/lib/internal/modules/esm/handle_process_exit.js b/lib/internal/modules/esm/handle_process_exit.js index db830900bd3154..9d6b609ef1cfc3 100644 --- a/lib/internal/modules/esm/handle_process_exit.js +++ b/lib/internal/modules/esm/handle_process_exit.js @@ -1,10 +1,12 @@ 'use strict'; +const { exitCodes: { kUnfinishedTopLevelAwait } } = internalBinding('errors'); + // Handle a Promise from running code that potentially does Top-Level Await. // In that case, it makes sense to set the exit code to a specific non-zero // value if the main code never finishes running. function handleProcessExit() { - process.exitCode ??= 13; + process.exitCode ??= kUnfinishedTopLevelAwait; } module.exports = { diff --git a/lib/internal/policy/manifest.js b/lib/internal/policy/manifest.js index 062e1ba219bbe8..86368628b44e86 100644 --- a/lib/internal/policy/manifest.js +++ b/lib/internal/policy/manifest.js @@ -40,6 +40,8 @@ const { getOptionValue } = require('internal/options'); const shouldAbortOnUncaughtException = getOptionValue( '--abort-on-uncaught-exception' ); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); + const { abort, exit, _rawDebug } = process; // #endregion @@ -72,7 +74,7 @@ function REACTION_EXIT(error) { if (shouldAbortOnUncaughtException) { abort(); } - exit(1); + exit(kGenericUserError); } function REACTION_LOG(error) { diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 889f94e434bf74..7c17e6f729cc44 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -14,6 +14,7 @@ const { ERR_EVAL_ESM_CANNOT_PRINT, }, } = require('internal/errors'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); const { executionAsyncId, @@ -161,8 +162,8 @@ function createOnGlobalUncaughtException() { try { if (!process._exiting) { process._exiting = true; - process.exitCode = 1; - process.emit('exit', 1); + process.exitCode = kGenericUserError; + process.emit('exit', kGenericUserError); } } catch { // Nothing to be done about it at this point. diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index c2be274659e6c7..c0428f8a6ca330 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -58,6 +58,7 @@ const kInternal = Symbol('internal properties'); function assert(x, msg) { if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); } +const { exitCodes: { kNoFailure } } = internalBinding('errors'); const binding = internalBinding('process_methods'); @@ -187,12 +188,12 @@ function wrapProcessMethods(binding) { if (!process._exiting) { process._exiting = true; - process.emit('exit', process.exitCode || 0); + process.emit('exit', process.exitCode || kNoFailure); } // FIXME(joyeecheung): This is an undocumented API that gets monkey-patched // in the user land. Either document it, or deprecate it in favor of a // better public alternative. - process.reallyExit(process.exitCode || 0); + process.reallyExit(process.exitCode || kNoFailure); } function kill(pid, sig) { diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 421538373e0399..17804200cf04ac 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -24,7 +24,8 @@ const { deprecate } = require('internal/util'); const { noSideEffectsToString, - triggerUncaughtException + triggerUncaughtException, + exitCodes: { kGenericUserError } } = internalBinding('errors'); const { @@ -294,7 +295,7 @@ function processPromiseRejections() { const handled = emit(reason, promise, promiseInfo); if (!handled) { emitUnhandledRejectionWarning(uid, reason); - process.exitCode = 1; + process.exitCode = kGenericUserError; } break; } diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index 62d26a1a1bd8bf..99465ddfedd382 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -13,6 +13,8 @@ const { ERR_TEST_FAILURE, }, } = require('internal/errors'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); + const { kEmptyObject } = require('internal/util'); const { getOptionValue } = require('internal/options'); const { kCancelledByParent, Test, ItTest, Suite } = require('internal/test_runner/test'); @@ -118,7 +120,7 @@ function getGlobalRoot() { globalRoot = createTestTree(); globalRoot.reporter.pipe(process.stdout); globalRoot.reporter.once('test:fail', () => { - process.exitCode = 1; + process.exitCode = kGenericUserError; }); } return globalRoot; diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 9994ac12ecf3a4..c82799a30ac6af 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -34,6 +34,7 @@ const { } = require('internal/test_runner/utils'); const { basename, join, resolve } = require('path'); const { once } = require('events'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); const kFilterArgs = ['--test']; @@ -90,7 +91,7 @@ function createTestFileList() { } catch (err) { if (err?.code === 'ENOENT') { console.error(`Could not find '${err.path}'`); - process.exit(1); + process.exit(kGenericUserError); } throw err; diff --git a/node.gyp b/node.gyp index 51bdae5770548f..0a4d0eadbfb1f7 100644 --- a/node.gyp +++ b/node.gyp @@ -599,6 +599,7 @@ 'src/node_contextify.h', 'src/node_dir.h', 'src/node_errors.h', + 'src/node_exit_code.h', 'src/node_external_reference.h', 'src/node_file.h', 'src/node_file-inl.h', diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index bd0459f20b1b3e..cdd7c9f22d6125 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -7,6 +7,7 @@ using v8::Function; using v8::Global; using v8::HandleScope; using v8::Isolate; +using v8::Just; using v8::Local; using v8::Locker; using v8::Maybe; @@ -15,7 +16,7 @@ using v8::SealHandleScope; namespace node { -Maybe SpinEventLoop(Environment* env) { +Maybe SpinEventLoopInternal(Environment* env) { CHECK_NOT_NULL(env); MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env); CHECK_NOT_NULL(platform); @@ -25,7 +26,7 @@ Maybe SpinEventLoop(Environment* env) { Context::Scope context_scope(env->context()); SealHandleScope seal(isolate); - if (env->is_stopping()) return Nothing(); + if (env->is_stopping()) return Nothing(); env->set_trace_sync_io(env->options()->trace_sync_io); { @@ -59,7 +60,7 @@ Maybe SpinEventLoop(Environment* env) { env->performance_state()->Mark( node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); } - if (env->is_stopping()) return Nothing(); + if (env->is_stopping()) return Nothing(); env->set_trace_sync_io(false); // Clear the serialize callback even though the JS-land queue should @@ -69,7 +70,7 @@ Maybe SpinEventLoop(Environment* env) { env->PrintInfoForSnapshotIfDebug(); env->VerifyNoStrongBaseObjects(); - return EmitProcessExit(env); + return EmitProcessExitInternal(env); } struct CommonEnvironmentSetup::Impl { @@ -155,6 +156,13 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() { delete impl_; } +Maybe SpinEventLoop(Environment* env) { + Maybe result = SpinEventLoopInternal(env); + if (result.IsNothing()) { + return Nothing(); + } + return Just(static_cast(result.FromJust())); +} uv_loop_t* CommonEnvironmentSetup::event_loop() const { return &impl_->loop; diff --git a/src/api/environment.cc b/src/api/environment.cc index 60d6b0a14ebba6..e32bb42eaceb49 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -1,7 +1,9 @@ +#include #include "node.h" #include "node_builtins.h" #include "node_context_data.h" #include "node_errors.h" +#include "node_exit_code.h" #include "node_internals.h" #include "node_options-inl.h" #include "node_platform.h" @@ -368,6 +370,7 @@ Environment* CreateEnvironment( Environment* env = new Environment( isolate_data, context, args, exec_args, nullptr, flags, thread_id); #if HAVE_INSPECTOR + // TODO(joyeecheung): handle the exit code returned by InitializeInspector(). if (env->should_create_inspector()) { if (inspector_parent_handle) { env->InitializeInspector( @@ -763,19 +766,32 @@ ThreadId AllocateEnvironmentThreadId() { return ThreadId { next_thread_id++ }; } -void DefaultProcessExitHandler(Environment* env, int exit_code) { +[[noreturn]] void Exit(ExitCode exit_code) { + exit(static_cast(exit_code)); +} + +void DefaultProcessExitHandlerInternal(Environment* env, ExitCode exit_code) { env->set_can_call_into_js(false); env->stop_sub_worker_contexts(); env->isolate()->DumpAndResetStats(); DisposePlatform(); uv_library_shutdown(); - exit(exit_code); + Exit(exit_code); } +void DefaultProcessExitHandler(Environment* env, int exit_code) { + DefaultProcessExitHandlerInternal(env, static_cast(exit_code)); +} +void SetProcessExitHandler( + Environment* env, std::function&& handler) { + env->set_process_exit_handler(std::move(handler)); +} void SetProcessExitHandler(Environment* env, std::function&& handler) { - env->set_process_exit_handler(std::move(handler)); + env->set_process_exit_handler([&](Environment* env, ExitCode exit_code) { + handler(env, static_cast(exit_code)); + }); } } // namespace node diff --git a/src/api/hooks.cc b/src/api/hooks.cc index bf4176cc7881c4..82f68cb7fd0add 100644 --- a/src/api/hooks.cc +++ b/src/api/hooks.cc @@ -53,11 +53,15 @@ Maybe EmitProcessBeforeExit(Environment* env) { Nothing() : Just(true); } +ExitCode EmitExitInternal(Environment* env) { + return EmitProcessExitInternal(env).FromMaybe(ExitCode::kGenericUserError); +} + int EmitExit(Environment* env) { - return EmitProcessExit(env).FromMaybe(1); + return static_cast(EmitExitInternal(env)); } -Maybe EmitProcessExit(Environment* env) { +Maybe EmitProcessExitInternal(Environment* env) { // process.emit('exit') Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); @@ -80,10 +84,18 @@ Maybe EmitProcessExit(Environment* env) { // Reload exit code, it may be changed by `emit('exit')` !process_object->Get(context, exit_code).ToLocal(&code_v) || !code_v->Int32Value(context).To(&code)) { - return Nothing(); + return Nothing(); } - return Just(code); + return Just(static_cast(code)); +} + +Maybe EmitProcessExit(Environment* env) { + Maybe result = EmitProcessExitInternal(env); + if (result.IsNothing()) { + return Nothing(); + } + return Just(static_cast(result.FromJust())); } typedef void (*CleanupHook)(void* arg); diff --git a/src/env-inl.h b/src/env-inl.h index e04c0c2cf179e3..6c325008e388ff 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -816,7 +816,7 @@ void Environment::set_main_utf16(std::unique_ptr str) { } void Environment::set_process_exit_handler( - std::function&& handler) { + std::function&& handler) { process_exit_handler_ = std::move(handler); } diff --git a/src/env.cc b/src/env.cc index f8f0d1aeae92f3..01cc221c590040 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1501,14 +1501,14 @@ void AsyncHooks::FailWithCorruptedAsyncStack(double expected_async_id) { expected_async_id); DumpBacktrace(stderr); fflush(stderr); - if (!env()->abort_on_uncaught_exception()) - exit(1); + // TODO(joyeecheung): should this exit code be more specific? + if (!env()->abort_on_uncaught_exception()) Exit(ExitCode::kGenericUserError); fprintf(stderr, "\n"); fflush(stderr); ABORT_NO_BACKTRACE(); } -void Environment::Exit(int exit_code) { +void Environment::Exit(ExitCode exit_code) { if (options()->trace_exit) { HandleScope handle_scope(isolate()); Isolate::DisallowJavascriptExecutionScope disallow_js( @@ -1536,7 +1536,7 @@ void Environment::stop_sub_worker_contexts() { while (!sub_worker_contexts_.empty()) { Worker* w = *sub_worker_contexts_.begin(); remove_sub_worker_context(w); - w->Exit(1); + w->Exit(ExitCode::kGenericUserError); w->JoinThread(); } } diff --git a/src/env.h b/src/env.h index 439a2aef1dc029..c73489470b17c9 100644 --- a/src/env.h +++ b/src/env.h @@ -37,6 +37,7 @@ #include "node.h" #include "node_binding.h" #include "node_builtins.h" +#include "node_exit_code.h" #include "node_main_instance.h" #include "node_options.h" #include "node_perf_common.h" @@ -578,6 +579,10 @@ struct SnapshotData { SnapshotData() = default; }; +void DefaultProcessExitHandlerInternal(Environment* env, ExitCode exit_code); +v8::Maybe SpinEventLoopInternal(Environment* env); +v8::Maybe EmitProcessExitInternal(Environment* env); + /** * Environment is a per-isolate data structure that represents an execution * environment. Each environment has a principal realm. An environment can @@ -615,7 +620,7 @@ class Environment : public MemoryRetainer { #if HAVE_INSPECTOR // If the environment is created for a worker, pass parent_handle and // the ownership if transferred into the Environment. - int InitializeInspector( + ExitCode InitializeInspector( std::unique_ptr parent_handle); #endif @@ -688,7 +693,7 @@ class Environment : public MemoryRetainer { void RegisterHandleCleanups(); void CleanupHandles(); - void Exit(int code); + void Exit(ExitCode code); void ExitEnv(); // Register clean-up cb to be called on environment destruction. @@ -1026,7 +1031,7 @@ class Environment : public MemoryRetainer { inline void set_main_utf16(std::unique_ptr); inline void set_process_exit_handler( - std::function&& handler); + std::function&& handler); void RunAndClearNativeImmediates(bool only_refed = false); void RunAndClearInterrupts(); @@ -1194,8 +1199,8 @@ class Environment : public MemoryRetainer { std::unordered_set unmanaged_fds_; - std::function process_exit_handler_ { - DefaultProcessExitHandler }; + std::function process_exit_handler_{ + DefaultProcessExitHandlerInternal}; std::unique_ptr principal_realm_ = nullptr; diff --git a/src/node.cc b/src/node.cc index 4e48a3ade9c5b5..6762a7f82ea09e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -166,7 +166,7 @@ void SignalExit(int signo, siginfo_t* info, void* ucontext) { #endif // __POSIX__ #if HAVE_INSPECTOR -int Environment::InitializeInspector( +ExitCode Environment::InitializeInspector( std::unique_ptr parent_handle) { std::string inspector_path; bool is_main = !parent_handle; @@ -187,7 +187,7 @@ int Environment::InitializeInspector( is_main); if (options_->debug_options().inspector_enabled && !inspector_agent_->IsListening()) { - return 12; // Signal internal error + return ExitCode::kInvalidCommandLineArgument2; // Signal internal error } profiler::StartProfilers(this); @@ -196,7 +196,7 @@ int Environment::InitializeInspector( inspector_agent_->PauseOnNextJavascriptStatement("Break at bootstrap"); } - return 0; + return ExitCode::kNoFailure; } #endif // HAVE_INSPECTOR @@ -626,11 +626,10 @@ void ResetStdio() { #endif // __POSIX__ } - -int ProcessGlobalArgs(std::vector* args, - std::vector* exec_args, - std::vector* errors, - OptionEnvvarSettings settings) { +ExitCode ProcessGlobalArgsInternal(std::vector* args, + std::vector* exec_args, + std::vector* errors, + OptionEnvvarSettings settings) { // Parse a few arguments which are specific to Node. std::vector v8_args; @@ -643,14 +642,15 @@ int ProcessGlobalArgs(std::vector* args, settings, errors); - if (!errors->empty()) return 9; + if (!errors->empty()) return ExitCode::kInvalidCommandLineArgument; std::string revert_error; for (const std::string& cve : per_process::cli_options->security_reverts) { Revert(cve.c_str(), &revert_error); if (!revert_error.empty()) { errors->emplace_back(std::move(revert_error)); - return 12; + // TODO(joyeecheung): merge into kInvalidCommandLineArgument. + return ExitCode::kInvalidCommandLineArgument2; } } @@ -658,7 +658,8 @@ int ProcessGlobalArgs(std::vector* args, per_process::cli_options->disable_proto != "throw" && per_process::cli_options->disable_proto != "") { errors->emplace_back("invalid mode passed to --disable-proto"); - return 12; + // TODO(joyeecheung): merge into kInvalidCommandLineArgument. + return ExitCode::kInvalidCommandLineArgument2; } // TODO(aduh95): remove this when the harmony-import-assertions flag @@ -698,19 +699,29 @@ int ProcessGlobalArgs(std::vector* args, for (size_t i = 1; i < v8_args_as_char_ptr.size(); i++) errors->push_back("bad option: " + std::string(v8_args_as_char_ptr[i])); - if (v8_args_as_char_ptr.size() > 1) return 9; + if (v8_args_as_char_ptr.size() > 1) + return ExitCode::kInvalidCommandLineArgument; - return 0; + return ExitCode::kNoFailure; +} + +int ProcessGlobalArgs(std::vector* args, + std::vector* exec_args, + std::vector* errors, + OptionEnvvarSettings settings) { + return static_cast( + ProcessGlobalArgsInternal(args, exec_args, errors, settings)); } static std::atomic_bool init_called{false}; // TODO(addaleax): Turn this into a wrapper around InitializeOncePerProcess() // (with the corresponding additional flags set), then eventually remove this. -int InitializeNodeWithArgs(std::vector* argv, - std::vector* exec_argv, - std::vector* errors, - ProcessInitializationFlags::Flags flags) { +ExitCode InitializeNodeWithArgsInternal( + std::vector* argv, + std::vector* exec_argv, + std::vector* errors, + ProcessInitializationFlags::Flags flags) { // Make sure InitializeNodeWithArgs() is called only once. CHECK(!init_called.exchange(true)); @@ -747,26 +758,22 @@ int InitializeNodeWithArgs(std::vector* argv, std::vector env_argv = ParseNodeOptionsEnvVar(node_options, errors); - if (!errors->empty()) return 9; + if (!errors->empty()) return ExitCode::kInvalidCommandLineArgument; // [0] is expected to be the program name, fill it in from the real argv. env_argv.insert(env_argv.begin(), argv->at(0)); - const int exit_code = ProcessGlobalArgs(&env_argv, - nullptr, - errors, - kAllowedInEnvironment); - if (exit_code != 0) return exit_code; + const ExitCode exit_code = ProcessGlobalArgsInternal( + &env_argv, nullptr, errors, kAllowedInEnvironment); + if (exit_code != ExitCode::kNoFailure) return exit_code; } } #endif if (!(flags & ProcessInitializationFlags::kDisableCLIOptions)) { - const int exit_code = ProcessGlobalArgs(argv, - exec_argv, - errors, - kDisallowedInEnvironment); - if (exit_code != 0) return exit_code; + const ExitCode exit_code = ProcessGlobalArgsInternal( + argv, exec_argv, errors, kDisallowedInEnvironment); + if (exit_code != ExitCode::kNoFailure) return exit_code; } // Set the process.title immediately after processing argv if --title is set. @@ -803,7 +810,7 @@ int InitializeNodeWithArgs(std::vector* argv, if (!i18n::InitializeICUDirectory(per_process::cli_options->icu_data_dir)) { errors->push_back("could not initialize ICU " "(check NODE_ICU_DATA or --icu-data-dir parameters)\n"); - return 9; + return ExitCode::kInvalidCommandLineArgument; } per_process::metadata.versions.InitializeIntlVersions(); } @@ -821,12 +828,21 @@ int InitializeNodeWithArgs(std::vector* argv, // otherwise embedders using node::Init to initialize everything will not be // able to set it and native addons will not load for them. node_is_initialized = true; - return 0; + return ExitCode::kNoFailure; } -std::unique_ptr InitializeOncePerProcess( +int InitializeNodeWithArgs(std::vector* argv, + std::vector* exec_argv, + std::vector* errors, + ProcessInitializationFlags::Flags flags) { + return static_cast( + InitializeNodeWithArgsInternal(argv, exec_argv, errors, flags)); +} + +std::unique_ptr InitializeOncePerProcessInternal( const std::vector& args, - ProcessInitializationFlags::Flags flags) { + ProcessInitializationFlags::Flags flags = + ProcessInitializationFlags::kNoFlags) { auto result = std::make_unique(); result->args_ = args; @@ -840,9 +856,9 @@ std::unique_ptr InitializeOncePerProcess( // This needs to run *before* V8::Initialize(). { - result->exit_code_ = InitializeNodeWithArgs( + result->exit_code_ = InitializeNodeWithArgsInternal( &result->args_, &result->exec_args_, &result->errors_, flags); - if (result->exit_code() != 0) { + if (result->exit_code_enum() != ExitCode::kNoFailure) { result->early_return_ = true; return result; } @@ -860,7 +876,7 @@ std::unique_ptr InitializeOncePerProcess( if (!(flags & ProcessInitializationFlags::kNoPrintHelpOrVersionOutput)) { if (per_process::cli_options->print_version) { printf("%s\n", NODE_VERSION); - result->exit_code_ = 0; + result->exit_code_ = ExitCode::kNoFailure; result->early_return_ = true; return result; } @@ -868,14 +884,14 @@ std::unique_ptr InitializeOncePerProcess( if (per_process::cli_options->print_bash_completion) { std::string completion = options_parser::GetBashCompletion(); printf("%s\n", completion.c_str()); - result->exit_code_ = 0; + result->exit_code_ = ExitCode::kNoFailure; result->early_return_ = true; return result; } if (per_process::cli_options->print_v8_help) { V8::SetFlagsFromString("--help", static_cast(6)); - result->exit_code_ = 0; + result->exit_code_ = ExitCode::kNoFailure; result->early_return_ = true; return result; } @@ -953,7 +969,8 @@ std::unique_ptr InitializeOncePerProcess( if (ERR_peek_error() != 0) { // XXX: ERR_GET_REASON does not return something that is // useful as an exit code at all. - result->exit_code_ = ERR_GET_REASON(ERR_peek_error()); + result->exit_code_ = + static_cast(ERR_GET_REASON(ERR_peek_error())); result->early_return_ = true; result->errors_.emplace_back("OpenSSL configuration error:\n" + GetOpenSSLErrorString()); @@ -967,7 +984,8 @@ std::unique_ptr InitializeOncePerProcess( if (!crypto::ProcessFipsOptions()) { // XXX: ERR_GET_REASON does not return something that is // useful as an exit code at all. - result->exit_code_ = ERR_GET_REASON(ERR_peek_error()); + result->exit_code_ = + static_cast(ERR_GET_REASON(ERR_peek_error())); result->early_return_ = true; result->errors_.emplace_back( "OpenSSL error when trying to enable FIPS:\n" + @@ -999,6 +1017,12 @@ std::unique_ptr InitializeOncePerProcess( return result; } +std::unique_ptr InitializeOncePerProcess( + const std::vector& args, + ProcessInitializationFlags::Flags flags) { + return InitializeOncePerProcessInternal(args, flags); +} + void TearDownOncePerProcess() { const uint64_t flags = init_process_flags.load(); ResetStdio(); @@ -1032,9 +1056,9 @@ void TearDownOncePerProcess() { InitializationResult::~InitializationResult() {} InitializationResultImpl::~InitializationResultImpl() {} -int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, - const InitializationResult* result) { - int exit_code = result->exit_code(); +ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, + const InitializationResultImpl* result) { + ExitCode exit_code = result->exit_code_enum(); // nullptr indicates there's no snapshot data. DCHECK_NULL(*snapshot_data_ptr); @@ -1048,7 +1072,8 @@ int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, "node:embedded_snapshot_main was specified as snapshot " "entry point but Node.js was built without embedded " "snapshot.\n"); - exit_code = 1; + // TODO(joyeecheung): should be kInvalidCommandLineArgument instead. + exit_code = ExitCode::kGenericUserError; return exit_code; } } else { @@ -1057,7 +1082,7 @@ int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, std::make_unique(); exit_code = node::SnapshotBuilder::Generate( generated_data.get(), result->args(), result->exec_args()); - if (exit_code == 0) { + if (exit_code == ExitCode::kNoFailure) { *snapshot_data_ptr = generated_data.release(); } else { return exit_code; @@ -1081,14 +1106,15 @@ int GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, fprintf(stderr, "Cannot open %s for writing a snapshot.\n", snapshot_blob_path.c_str()); - exit_code = 1; + // TODO(joyeecheung): should be kStartupSnapshotFailure. + exit_code = ExitCode::kGenericUserError; } return exit_code; } -int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, - const InitializationResult* result) { - int exit_code = result->exit_code(); +ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, + const InitializationResultImpl* result) { + ExitCode exit_code = result->exit_code_enum(); // nullptr indicates there's no snapshot data. DCHECK_NULL(*snapshot_data_ptr); // --snapshot-blob indicates that we are reading a customized snapshot. @@ -1097,13 +1123,15 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, FILE* fp = fopen(filename.c_str(), "rb"); if (fp == nullptr) { fprintf(stderr, "Cannot open %s", filename.c_str()); - exit_code = 1; + // TODO(joyeecheung): should be kStartupSnapshotFailure. + exit_code = ExitCode::kGenericUserError; return exit_code; } std::unique_ptr read_data = std::make_unique(); if (!SnapshotData::FromBlob(read_data.get(), fp)) { // If we fail to read the customized snapshot, simply exit with 1. - exit_code = 1; + // TODO(joyeecheung): should be kStartupSnapshotFailure. + exit_code = ExitCode::kGenericUserError; return exit_code; } *snapshot_data_ptr = read_data.release(); @@ -1132,22 +1160,23 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, return exit_code; } -int Start(int argc, char** argv) { +ExitCode StartInternal(int argc, char** argv) { CHECK_GT(argc, 0); // Hack around with the argv pointer. Used for process.title = "blah". argv = uv_setup_args(argc, argv); - std::unique_ptr result = - InitializeOncePerProcess(std::vector(argv, argv + argc)); + std::unique_ptr result = + InitializeOncePerProcessInternal( + std::vector(argv, argv + argc)); for (const std::string& error : result->errors()) { FPrintF(stderr, "%s: %s\n", result->args().at(0), error); } if (result->early_return()) { - return result->exit_code(); + return result->exit_code_enum(); } - DCHECK_EQ(result->exit_code(), 0); + DCHECK_EQ(result->exit_code(), ExitCode::kNoFailure); const SnapshotData* snapshot_data = nullptr; auto cleanup_process = OnScopeLeave([&]() { @@ -1167,7 +1196,7 @@ int Start(int argc, char** argv) { fprintf(stderr, "--build-snapshot must be used with an entry point script.\n" "Usage: node --build-snapshot /path/to/entry.js\n"); - return 9; + return ExitCode::kInvalidCommandLineArgument; } return GenerateAndWriteSnapshotData(&snapshot_data, result.get()); } @@ -1176,6 +1205,10 @@ int Start(int argc, char** argv) { return LoadSnapshotDataAndRun(&snapshot_data, result.get()); } +int Start(int argc, char** argv) { + return static_cast(StartInternal(argc, argv)); +} + int Stop(Environment* env) { env->ExitEnv(); return 0; diff --git a/src/node_errors.cc b/src/node_errors.cc index 323fc7d4ff635c..45419f2c61fd79 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -569,7 +569,7 @@ TryCatchScope::~TryCatchScope() { if (message.IsEmpty()) message = Exception::CreateMessage(env_->isolate(), exception); ReportFatalException(env_, exception, message, enhance); - env_->Exit(7); + env_->Exit(ExitCode::kExceptionInFatalExceptionHandler); } } @@ -1019,6 +1019,17 @@ void Initialize(Local target, context, target, "noSideEffectsToString", NoSideEffectsToString); SetMethod( context, target, "triggerUncaughtException", TriggerUncaughtException); + + Isolate* isolate = context->GetIsolate(); + Local exit_codes = Object::New(isolate); + READONLY_PROPERTY(target, "exitCodes", exit_codes); + +#define V(Name, Code) \ + constexpr int k##Name = static_cast(ExitCode::k##Name); \ + NODE_DEFINE_CONSTANT(exit_codes, k##Name); + + EXIT_CODE_LIST(V) +#undef V } void DecorateErrorStack(Environment* env, @@ -1094,7 +1105,7 @@ void TriggerUncaughtException(Isolate* isolate, if (!fatal_exception_function->IsFunction()) { ReportFatalException( env, error, message, EnhanceFatalException::kDontEnhance); - env->Exit(6); + env->Exit(ExitCode::kInvalidFatalExceptionMonkeyPatching); return; } @@ -1145,9 +1156,9 @@ void TriggerUncaughtException(Isolate* isolate, Local code; if (process_object->Get(env->context(), exit_code).ToLocal(&code) && code->IsInt32()) { - env->Exit(code.As()->Value()); + env->Exit(static_cast(code.As()->Value())); } else { - env->Exit(1); + env->Exit(ExitCode::kGenericUserError); } } diff --git a/src/node_errors.h b/src/node_errors.h index 5587c234862610..5cc385c743e0b3 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -276,7 +276,6 @@ v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings( v8::Local context, v8::Local source, bool is_code_like); - } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node_exit_code.h b/src/node_exit_code.h new file mode 100644 index 00000000000000..cedc920112131b --- /dev/null +++ b/src/node_exit_code.h @@ -0,0 +1,53 @@ +#ifndef SRC_NODE_EXIT_CODE_H_ +#define SRC_NODE_EXIT_CODE_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +namespace node { +#define EXIT_CODE_LIST(V) \ + V(NoFailure, 0) \ + /* This was intended for uncaught JS exceptions from the user land but we */ \ + /* actually use this for all kinds of generic errors. */ \ + V(GenericUserError, 1) \ + /* 2 is unused */ \ + /* 3 is actually unused because we pre-compile all builtins during */ \ + /* snapshot building, when we exit with 1 if there's any error. */ \ + V(InternalJSParseError, 3) \ + /* 4 is actually unused. We exit with 1 in this case. */ \ + V(InternalJSEvaluationFailure, 4) \ + /* 5 is actually unused. We exit with 133 (128+SIGTRAP) or 134 */ \ + /* (128+SIGABRT) in this case. */ \ + V(V8FatalError, 5) \ + V(InvalidFatalExceptionMonkeyPatching, 6) \ + V(ExceptionInFatalExceptionHandler, 7) \ + /* 8 is unused */ \ + V(InvalidCommandLineArgument, 9) \ + V(BootstrapFailure, 10) \ + /* 11 is unused */ \ + /* This was intended for invalid inspector arguments but is actually now */ \ + /* just a duplicate of InvalidCommandLineArgument */ \ + V(InvalidCommandLineArgument2, 12) \ + V(UnfinishedTopLevelAwait, 13) \ + V(StartupSnapshotFailure, 14) + +// If the process exits from unhandled signals e.g. SIGABRT, SIGTRAP, typically +// the exit codes are 128 + signal number. We also exit with certain error +// codes directly for legacy reasons. + +// TODO(joyeecheung): expose this to user land when the codes are stable. +// The underlying type should be an int, or we can get undefined behavior when +// casting error codes into exit codes (technically we shouldn't do that, +// but that's how things have been). +enum class ExitCode : int { +#define V(Name, Code) k##Name = Code, + EXIT_CODE_LIST(V) +#undef V +}; + +[[noreturn]] void Exit(ExitCode exit_code); + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_EXIT_CODE_H_ diff --git a/src/node_internals.h b/src/node_internals.h index 9cd89faca13159..9f15a807d02e09 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -312,14 +312,15 @@ void MarkBootstrapComplete(const v8::FunctionCallbackInfo& args); class InitializationResultImpl final : public InitializationResult { public: ~InitializationResultImpl(); - int exit_code() const { return exit_code_; } + int exit_code() const { return static_cast(exit_code_enum()); } + ExitCode exit_code_enum() const { return exit_code_; } bool early_return() const { return early_return_; } const std::vector& args() const { return args_; } const std::vector& exec_args() const { return exec_args_; } const std::vector& errors() const { return errors_; } MultiIsolatePlatform* platform() const { return platform_; } - int exit_code_ = 0; + ExitCode exit_code_ = ExitCode::kNoFailure; std::vector args_; std::vector exec_args_; std::vector errors_; diff --git a/src/node_main.cc b/src/node_main.cc index d486bbc31c1f6b..a8165d27b3b894 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -64,7 +64,8 @@ int wmain(int argc, wchar_t* wargv[]) { if (size == 0) { // This should never happen. fprintf(stderr, "Could not convert arguments to utf8."); - exit(1); + // FIXME(joyeecheung): should be kInvalidCommandLineArgument. + Exit(ExitCode::kGenericUserError); } // Do the actual conversion argv[i] = new char[size]; @@ -79,7 +80,8 @@ int wmain(int argc, wchar_t* wargv[]) { if (result == 0) { // This should never happen. fprintf(stderr, "Could not convert arguments to utf8."); - exit(1); + // FIXME(joyeecheung): should be kInvalidCommandLineArgument. + Exit(ExitCode::kGenericUserError); } } argv[argc] = nullptr; diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index 2ec53a1b314d2d..6ded80aa05b878 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -118,12 +118,12 @@ NodeMainInstance::~NodeMainInstance() { isolate_->Dispose(); } -int NodeMainInstance::Run() { +ExitCode NodeMainInstance::Run() { Locker locker(isolate_); Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); - int exit_code = 0; + ExitCode exit_code = ExitCode::kNoFailure; DeleteFnPtr env = CreateMainEnvironment(&exit_code); CHECK_NOT_NULL(env); @@ -133,11 +133,12 @@ int NodeMainInstance::Run() { return exit_code; } -void NodeMainInstance::Run(int* exit_code, Environment* env) { - if (*exit_code == 0) { +void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) { + if (*exit_code == ExitCode::kNoFailure) { LoadEnvironment(env, StartExecutionCallback{}); - *exit_code = SpinEventLoop(env).FromMaybe(1); + *exit_code = + SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError); } #if defined(LEAK_SANITIZER) @@ -146,8 +147,8 @@ void NodeMainInstance::Run(int* exit_code, Environment* env) { } DeleteFnPtr -NodeMainInstance::CreateMainEnvironment(int* exit_code) { - *exit_code = 0; // Reset the exit code to 0 +NodeMainInstance::CreateMainEnvironment(ExitCode* exit_code) { + *exit_code = ExitCode::kNoFailure; // Reset the exit code to 0 HandleScope handle_scope(isolate_); @@ -180,6 +181,8 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { SetIsolateErrorHandlers(isolate_, {}); env->InitializeMainContext(context, &(snapshot_data_->env_info)); #if HAVE_INSPECTOR + // TODO(joyeecheung): handle the exit code returned by + // InitializeInspector(). env->InitializeInspector({}); #endif @@ -198,6 +201,8 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { EnvironmentFlags::kDefaultFlags, {})); #if HAVE_INSPECTOR + // TODO(joyeecheung): handle the exit code returned by + // InitializeInspector(). env->InitializeInspector({}); #endif if (env->principal_realm()->RunBootstrapping().IsEmpty()) { diff --git a/src/node_main_instance.h b/src/node_main_instance.h index 3be22767d87863..43b6a8d64e409f 100644 --- a/src/node_main_instance.h +++ b/src/node_main_instance.h @@ -7,6 +7,7 @@ #include #include "node.h" +#include "node_exit_code.h" #include "util.h" #include "uv.h" #include "v8.h" @@ -57,13 +58,13 @@ class NodeMainInstance { ~NodeMainInstance(); // Start running the Node.js instances, return the exit code when finished. - int Run(); - void Run(int* exit_code, Environment* env); + ExitCode Run(); + void Run(ExitCode* exit_code, Environment* env); IsolateData* isolate_data() { return isolate_data_.get(); } DeleteFnPtr CreateMainEnvironment( - int* exit_code); + ExitCode* exit_code); NodeMainInstance(const NodeMainInstance&) = delete; NodeMainInstance& operator=(const NodeMainInstance&) = delete; diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 296d5245cd5658..5acd560e4638fc 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -45,6 +45,7 @@ using v8::HeapStatistics; using v8::Integer; using v8::Isolate; using v8::Local; +using v8::Maybe; using v8::NewStringType; using v8::Number; using v8::Object; @@ -442,7 +443,11 @@ static void DebugEnd(const FunctionCallbackInfo& args) { static void ReallyExit(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); RunAtExit(env); - int code = args[0]->Int32Value(env->context()).FromMaybe(0); + ExitCode code = ExitCode::kNoFailure; + Maybe code_int = args[0]->Int32Value(env->context()); + if (!code_int.IsNothing()) { + code = static_cast(code_int.FromJust()); + } env->Exit(code); } diff --git a/src/node_snapshot_builder.h b/src/node_snapshot_builder.h index 7a82c00255e780..2bea50a2540b2a 100644 --- a/src/node_snapshot_builder.h +++ b/src/node_snapshot_builder.h @@ -5,6 +5,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include +#include "node_exit_code.h" #include "node_mutex.h" #include "v8.h" @@ -15,14 +16,14 @@ struct SnapshotData; class NODE_EXTERN_PRIVATE SnapshotBuilder { public: - static int Generate(std::ostream& out, - const std::vector args, - const std::vector exec_args); + static ExitCode Generate(std::ostream& out, + const std::vector args, + const std::vector exec_args); // Generate the snapshot into out. - static int Generate(SnapshotData* out, - const std::vector args, - const std::vector exec_args); + static ExitCode Generate(SnapshotData* out, + const std::vector args, + const std::vector exec_args); // If nullptr is returned, the binary is not built with embedded // snapshot. diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 859417d9fb5fd7..1ed4fe043ab171 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1067,14 +1067,9 @@ void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data, const_cast(&(data->v8_snapshot_blob_data)); } -// TODO(joyeecheung): share these exit code constants across the code base. -constexpr int UNCAUGHT_EXCEPTION_ERROR = 1; -constexpr int BOOTSTRAP_ERROR = 10; -constexpr int SNAPSHOT_ERROR = 14; - -int SnapshotBuilder::Generate(SnapshotData* out, - const std::vector args, - const std::vector exec_args) { +ExitCode SnapshotBuilder::Generate(SnapshotData* out, + const std::vector args, + const std::vector exec_args) { const std::vector& external_references = CollectExternalReferences(); Isolate* isolate = Isolate::Allocate(); @@ -1137,7 +1132,7 @@ int SnapshotBuilder::Generate(SnapshotData* out, if (!contextify::ContextifyContext::CreateV8Context( isolate, global_template, nullptr, nullptr) .ToLocal(&vm_context)) { - return SNAPSHOT_ERROR; + return ExitCode::kStartupSnapshotFailure; } } @@ -1146,13 +1141,13 @@ int SnapshotBuilder::Generate(SnapshotData* out, // without breaking compatibility. Local base_context = NewContext(isolate); if (base_context.IsEmpty()) { - return BOOTSTRAP_ERROR; + return ExitCode::kBootstrapFailure; } ResetContextSettingsBeforeSnapshot(base_context); Local main_context = NewContext(isolate); if (main_context.IsEmpty()) { - return BOOTSTRAP_ERROR; + return ExitCode::kBootstrapFailure; } // Initialize the main instance context. { @@ -1169,7 +1164,7 @@ int SnapshotBuilder::Generate(SnapshotData* out, // Run scripts in lib/internal/bootstrap/ if (env->principal_realm()->RunBootstrapping().IsEmpty()) { - return BOOTSTRAP_ERROR; + return ExitCode::kBootstrapFailure; } // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be // loaded via LoadEnvironment() to execute process.argv[1] as the entry @@ -1178,17 +1173,19 @@ int SnapshotBuilder::Generate(SnapshotData* out, // in the future). if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) { #if HAVE_INSPECTOR - // TODO(joyeecheung): move this before RunBootstrapping(). + // TODO(joyeecheung): handle the exit code returned by + // InitializeInspector(). env->InitializeInspector({}); #endif if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) { - return UNCAUGHT_EXCEPTION_ERROR; + return ExitCode::kGenericUserError; } // FIXME(joyeecheung): right now running the loop in the snapshot // builder seems to introduces inconsistencies in JS land that need to // be synchronized again after snapshot restoration. - int exit_code = SpinEventLoop(env).FromMaybe(UNCAUGHT_EXCEPTION_ERROR); - if (exit_code != 0) { + ExitCode exit_code = + SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError); + if (exit_code != ExitCode::kNoFailure) { return exit_code; } } @@ -1206,7 +1203,7 @@ int SnapshotBuilder::Generate(SnapshotData* out, #ifdef NODE_USE_NODE_CODE_CACHE // Regenerate all the code cache. if (!builtins::BuiltinLoader::CompileAllBuiltins(main_context)) { - return UNCAUGHT_EXCEPTION_ERROR; + return ExitCode::kGenericUserError; } builtins::BuiltinLoader::CopyCodeCache(&(out->code_cache)); for (const auto& item : out->code_cache) { @@ -1241,7 +1238,7 @@ int SnapshotBuilder::Generate(SnapshotData* out, // We must be able to rehash the blob when we restore it or otherwise // the hash seed would be fixed by V8, introducing a vulnerability. if (!out->v8_snapshot_blob_data.CanBeRehashed()) { - return SNAPSHOT_ERROR; + return ExitCode::kStartupSnapshotFailure; } out->metadata = SnapshotMetadata{snapshot_type, @@ -1260,17 +1257,17 @@ int SnapshotBuilder::Generate(SnapshotData* out, PrintLibuvHandleInformation(env->event_loop(), stderr); } if (!queues_are_empty) { - return SNAPSHOT_ERROR; + return ExitCode::kStartupSnapshotFailure; } - return 0; + return ExitCode::kNoFailure; } -int SnapshotBuilder::Generate(std::ostream& out, - const std::vector args, - const std::vector exec_args) { +ExitCode SnapshotBuilder::Generate(std::ostream& out, + const std::vector args, + const std::vector exec_args) { SnapshotData data; - int exit_code = Generate(&data, args, exec_args); - if (exit_code != 0) { + ExitCode exit_code = Generate(&data, args, exec_args); + if (exit_code != ExitCode::kNoFailure) { return exit_code; } FormatBlob(out, &data); diff --git a/src/node_worker.cc b/src/node_worker.cc index 9e2999ed8c3955..7f66c72a8849c8 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -137,7 +137,8 @@ class WorkerThreadData { if (ret != 0) { char err_buf[128]; uv_err_name_r(ret, err_buf, sizeof(err_buf)); - w->Exit(1, "ERR_WORKER_INIT_FAILED", err_buf); + // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? + w->Exit(ExitCode::kGenericUserError, "ERR_WORKER_INIT_FAILED", err_buf); return; } loop_init_failed_ = false; @@ -156,7 +157,10 @@ class WorkerThreadData { Isolate* isolate = Isolate::Allocate(); if (isolate == nullptr) { - w->Exit(1, "ERR_WORKER_INIT_FAILED", "Failed to create new Isolate"); + // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? + w->Exit(ExitCode::kGenericUserError, + "ERR_WORKER_INIT_FAILED", + "Failed to create new Isolate"); return; } @@ -257,7 +261,10 @@ size_t Worker::NearHeapLimit(void* data, size_t current_heap_limit, "new_limit=%" PRIu64 "\n", static_cast(new_limit)); } - worker->Exit(1, "ERR_WORKER_OUT_OF_MEMORY", "JS heap out of memory"); + // TODO(joyeecheung): maybe this should be kV8FatalError instead? + worker->Exit(ExitCode::kGenericUserError, + "ERR_WORKER_OUT_OF_MEMORY", + "JS heap out of memory"); return new_limit; } @@ -322,7 +329,10 @@ void Worker::Run() { context = NewContext(isolate_); } if (context.IsEmpty()) { - Exit(1, "ERR_WORKER_INIT_FAILED", "Failed to create new Context"); + // TODO(joyeecheung): maybe this should be kBootstrapFailure instead? + Exit(ExitCode::kGenericUserError, + "ERR_WORKER_INIT_FAILED", + "Failed to create new Context"); return; } } @@ -343,7 +353,7 @@ void Worker::Run() { CHECK_NOT_NULL(env_); env_->set_env_vars(std::move(env_vars_)); SetProcessExitHandler(env_.get(), [this](Environment*, int exit_code) { - Exit(exit_code); + Exit(static_cast(exit_code)); }); } { @@ -367,14 +377,16 @@ void Worker::Run() { } { - Maybe exit_code = SpinEventLoop(env_.get()); + Maybe exit_code = SpinEventLoopInternal(env_.get()); Mutex::ScopedLock lock(mutex_); - if (exit_code_ == 0 && exit_code.IsJust()) { + if (exit_code_ == ExitCode::kNoFailure && exit_code.IsJust()) { exit_code_ = exit_code.FromJust(); } - Debug(this, "Exiting thread for worker %llu with exit code %d", - thread_id_.id, exit_code_); + Debug(this, + "Exiting thread for worker %llu with exit code %d", + thread_id_.id, + static_cast(exit_code_)); } } @@ -419,7 +431,7 @@ void Worker::JoinThread() { Undefined(env()->isolate())).Check(); Local args[] = { - Integer::New(env()->isolate(), exit_code_), + Integer::New(env()->isolate(), static_cast(exit_code_)), custom_error_ != nullptr ? OneByteString(env()->isolate(), custom_error_).As() : Null(env()->isolate()).As(), @@ -684,7 +696,7 @@ void Worker::StopThread(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); Debug(w, "Worker %llu is getting stopped by parent", w->thread_id_.id); - w->Exit(1); + w->Exit(ExitCode::kGenericUserError); } void Worker::Ref(const FunctionCallbackInfo& args) { @@ -724,10 +736,16 @@ Local Worker::GetResourceLimits(Isolate* isolate) const { return Float64Array::New(ab, 0, kTotalResourceLimitCount); } -void Worker::Exit(int code, const char* error_code, const char* error_message) { +void Worker::Exit(ExitCode code, + const char* error_code, + const char* error_message) { Mutex::ScopedLock lock(mutex_); - Debug(this, "Worker %llu called Exit(%d, %s, %s)", - thread_id_.id, code, error_code, error_message); + Debug(this, + "Worker %llu called Exit(%d, %s, %s)", + thread_id_.id, + static_cast(code), + error_code, + error_message); if (error_code != nullptr) { custom_error_ = error_code; diff --git a/src/node_worker.h b/src/node_worker.h index b3814066287d4b..dcb58d13e0e6f9 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -5,6 +5,7 @@ #include #include +#include "node_exit_code.h" #include "node_messaging.h" #include "uv.h" @@ -41,7 +42,7 @@ class Worker : public AsyncWrap { // Forcibly exit the thread with a specified exit code. This may be called // from any thread. `error_code` and `error_message` can be used to create // a custom `'error'` event before emitting `'exit'`. - void Exit(int code, + void Exit(ExitCode code, const char* error_code = nullptr, const char* error_message = nullptr); @@ -95,7 +96,7 @@ class Worker : public AsyncWrap { const char* custom_error_ = nullptr; std::string custom_error_str_; - int exit_code_ = 0; + ExitCode exit_code_ = ExitCode::kNoFailure; ThreadId thread_id_; uintptr_t stack_base_ = 0; diff --git a/src/util.h b/src/util.h index 0fe821e9fa97ce..74f1339f346618 100644 --- a/src/util.h +++ b/src/util.h @@ -118,7 +118,7 @@ void DumpBacktrace(FILE* fp); // Windows 8+ does not like abort() in Release mode #ifdef _WIN32 -#define ABORT_NO_BACKTRACE() _exit(134) +#define ABORT_NO_BACKTRACE() _exit(static_cast(ExitCode::kAbort)) #else #define ABORT_NO_BACKTRACE() abort() #endif diff --git a/tools/snapshot/node_mksnapshot.cc b/tools/snapshot/node_mksnapshot.cc index 0b8b7a6d5e77cf..7e69b5053d15c2 100644 --- a/tools/snapshot/node_mksnapshot.cc +++ b/tools/snapshot/node_mksnapshot.cc @@ -84,18 +84,18 @@ int BuildSnapshot(int argc, char* argv[]) { return 1; } - int exit_code = 0; + node::ExitCode exit_code = node::ExitCode::kNoFailure; { exit_code = node::SnapshotBuilder::Generate( out, result->args(), result->exec_args()); - if (exit_code == 0) { + if (exit_code == node::ExitCode::kNoFailure) { if (!out) { std::cerr << "Failed to write " << out_path << "\n"; - exit_code = 1; + exit_code = node::ExitCode::kGenericUserError; } } } node::TearDownOncePerProcess(); - return exit_code; + return static_cast(exit_code); }