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..cddd0f426731d6 100644 --- a/lib/internal/debugger/inspect.js +++ b/lib/internal/debugger/inspect.js @@ -44,6 +44,12 @@ 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 +173,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 +240,7 @@ class NodeInspector { } } this.stdout.write(' failed to connect, please retry\n'); - process.exit(1); + process.exit(kGenericUserError); } clearLine() { @@ -314,7 +320,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 +343,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 +362,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..ea02190fa13d00 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..8d92afd9c29464 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 53477f967d6bd1..16b7515f003854 100644 --- a/node.gyp +++ b/node.gyp @@ -600,6 +600,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 13e0f826cf1e75..f0f92c690eb63c 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->ForEachRealm([](Realm* realm) { realm->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..635c96c17a2be4 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,34 @@ 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)); + auto movedHandler = std::move(handler); + env->set_process_exit_handler([=](Environment* env, ExitCode exit_code) { + movedHandler(env, static_cast(exit_code)); + }); } } // namespace node diff --git a/src/api/hooks.cc b/src/api/hooks.cc index bf4176cc7881c4..04fc00c394404b 100644 --- a/src/api/hooks.cc +++ b/src/api/hooks.cc @@ -53,11 +53,15 @@ Maybe EmitProcessBeforeExit(Environment* env) { Nothing() : Just(true); } +static 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 fbc4fbb27b065d..4446b6057d5977 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -801,7 +801,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 88106c80f3a9a5..d5552ce9133519 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1500,14 +1500,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( @@ -1520,8 +1520,9 @@ void Environment::Exit(int exit_code) { uv_os_getpid(), thread_id()); } - fprintf( - stderr, "WARNING: Exited the environment with code %d\n", exit_code); + fprintf(stderr, + "WARNING: Exited the environment with code %d\n", + static_cast(exit_code)); PrintStackTrace(isolate(), StackTrace::CurrentStackTrace( isolate(), stack_trace_limit(), StackTrace::kDetailed)); @@ -1535,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 29d3ee2d768bb5..9e72e5541c3a9b 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" @@ -577,6 +578,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 @@ -612,7 +617,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 @@ -685,7 +690,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. @@ -1010,7 +1015,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(); @@ -1176,8 +1181,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 cf840fd1066136..c7e20acfaf9aca 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) { +static 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) { +static 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( - const std::vector& args, - ProcessInitializationFlags::Flags flags) { +int InitializeNodeWithArgs(std::vector* argv, + std::vector* exec_argv, + std::vector* errors, + ProcessInitializationFlags::Flags flags) { + return static_cast( + InitializeNodeWithArgsInternal(argv, exec_argv, errors, flags)); +} + +static std::unique_ptr +InitializeOncePerProcessInternal(const std::vector& args, + 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; } @@ -952,7 +968,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()); @@ -966,7 +983,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" + @@ -1004,6 +1022,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(); @@ -1037,9 +1061,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); @@ -1053,7 +1077,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 { @@ -1062,7 +1087,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; @@ -1086,14 +1111,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. @@ -1102,13 +1128,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(); @@ -1137,22 +1165,23 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, return exit_code; } -int Start(int argc, char** argv) { +static 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_enum(), ExitCode::kNoFailure); const SnapshotData* snapshot_data = nullptr; auto cleanup_process = OnScopeLeave([&]() { @@ -1172,7 +1201,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()); } @@ -1181,6 +1210,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..07c5a94e35ee28 --- /dev/null +++ b/src/node_exit_code.h @@ -0,0 +1,54 @@ +#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) \ + /* 1 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. Here we define those */ \ + /* that are used to normalize the exit code on Windows. */ \ + V(Abort, 134) + +// 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..56deaf47fa5115 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -64,6 +64,8 @@ int wmain(int argc, wchar_t* wargv[]) { if (size == 0) { // This should never happen. fprintf(stderr, "Could not convert arguments to utf8."); + // TODO(joyeecheung): should be ExitCode::kInvalidCommandLineArgument, + // but we are not ready to expose that to node.h yet. exit(1); } // Do the actual conversion @@ -79,6 +81,8 @@ int wmain(int argc, wchar_t* wargv[]) { if (result == 0) { // This should never happen. fprintf(stderr, "Could not convert arguments to utf8."); + // TODO(joyeecheung): should be ExitCode::kInvalidCommandLineArgument, + // but we are not ready to expose that to node.h yet. exit(1); } } 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 cb42a464c2ac0a..6a6b899cb83529 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 0aa191aad23943..378fa3d16c4b45 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..7a41eae4aa77df 100644 --- a/src/util.h +++ b/src/util.h @@ -27,6 +27,7 @@ #include "v8.h" #include "node.h" +#include "node_exit_code.h" #include #include @@ -118,7 +119,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(node::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); }