src: clean up `node::Init()` wrt embedder scenarios

This makes the STL variant of `node::Init()` a bit more
suitable for inclusion in a proper embedder API, as errors
or other output are reported to the caller rather than
directly being printed, and the process is not exited
directly either.

PR-URL: https://github.com/nodejs/node/pull/25370
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
diff --git a/src/node.cc b/src/node.cc
index 3a1a6a9..e55fbef 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -1147,42 +1147,23 @@
 #endif  // _WIN32
 }
 
-void ProcessArgv(std::vector<std::string>* args,
-                 std::vector<std::string>* exec_args,
-                 bool is_env) {
+int ProcessGlobalArgs(std::vector<std::string>* args,
+                      std::vector<std::string>* exec_args,
+                      std::vector<std::string>* errors,
+                      bool is_env) {
   // Parse a few arguments which are specific to Node.
   std::vector<std::string> v8_args;
-  std::vector<std::string> errors{};
 
-  {
-    // TODO(addaleax): The mutex here should ideally be held during the
-    // entire function, but that doesn't play well with the exit() calls below.
-    Mutex::ScopedLock lock(per_process::cli_options_mutex);
-    options_parser::PerProcessOptionsParser::instance.Parse(
-        args,
-        exec_args,
-        &v8_args,
-        per_process::cli_options.get(),
-        is_env ? kAllowedInEnvironment : kDisallowedInEnvironment,
-        &errors);
-  }
+  Mutex::ScopedLock lock(per_process::cli_options_mutex);
+  options_parser::PerProcessOptionsParser::instance.Parse(
+      args,
+      exec_args,
+      &v8_args,
+      per_process::cli_options.get(),
+      is_env ? kAllowedInEnvironment : kDisallowedInEnvironment,
+      errors);
 
-  if (!errors.empty()) {
-    for (auto const& error : errors) {
-      fprintf(stderr, "%s: %s\n", args->at(0).c_str(), error.c_str());
-    }
-    exit(9);
-  }
-
-  if (per_process::cli_options->print_version) {
-    printf("%s\n", NODE_VERSION);
-    exit(0);
-  }
-
-  if (per_process::cli_options->print_v8_help) {
-    V8::SetFlagsFromString("--help", 6);
-    exit(0);
-  }
+  if (!errors->empty()) return 9;
 
   for (const std::string& cve : per_process::cli_options->security_reverts)
     Revert(cve.c_str());
@@ -1222,19 +1203,17 @@
   }
 
   // Anything that's still in v8_argv is not a V8 or a node option.
-  for (size_t i = 1; i < v8_args_as_char_ptr.size(); i++) {
-    fprintf(stderr, "%s: bad option: %s\n",
-            args->at(0).c_str(), v8_args_as_char_ptr[i]);
-  }
+  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) {
-    exit(9);
-  }
+  if (v8_args_as_char_ptr.size() > 1) return 9;
+
+  return 0;
 }
 
-
-void Init(std::vector<std::string>* argv,
-          std::vector<std::string>* exec_argv) {
+int Init(std::vector<std::string>* argv,
+         std::vector<std::string>* exec_argv,
+         std::vector<std::string>* errors) {
   // Initialize prog_start_time to get relative uptime.
   per_process::prog_start_time = static_cast<double>(uv_now(uv_default_loop()));
 
@@ -1295,11 +1274,13 @@
     std::vector<std::string> env_argv = SplitString("x " + node_options, ' ');
     env_argv[0] = argv->at(0);
 
-    ProcessArgv(&env_argv, nullptr, true);
+    const int exit_code = ProcessGlobalArgs(&env_argv, nullptr, errors, true);
+    if (exit_code != 0) return exit_code;
   }
 #endif
 
-  ProcessArgv(argv, exec_argv, false);
+  const int exit_code = ProcessGlobalArgs(argv, exec_argv, errors, false);
+  if (exit_code != 0) return exit_code;
 
   // Set the process.title immediately after processing argv if --title is set.
   if (!per_process::cli_options->title.empty())
@@ -1313,11 +1294,9 @@
   // Initialize ICU.
   // If icu_data_dir is empty here, it will load the 'minimal' data.
   if (!i18n::InitializeICUDirectory(per_process::cli_options->icu_data_dir)) {
-    fprintf(stderr,
-            "%s: could not initialize ICU "
-            "(check NODE_ICU_DATA or --icu-data-dir parameters)\n",
-            argv->at(0).c_str());
-    exit(9);
+    errors->push_back("could not initialize ICU "
+                      "(check NODE_ICU_DATA or --icu-data-dir parameters)\n");
+    return 9;
   }
   per_process::metadata.versions.InitializeIntlVersions();
 #endif
@@ -1326,6 +1305,7 @@
   // otherwise embedders using node::Init to initialize everything will not be
   // able to set it and native modules will not load for them.
   node_is_initialized = true;
+  return 0;
 }
 
 // TODO(addaleax): Deprecate and eventually remove this.
@@ -1335,8 +1315,25 @@
           const char*** exec_argv) {
   std::vector<std::string> argv_(argv, argv + *argc);  // NOLINT
   std::vector<std::string> exec_argv_;
+  std::vector<std::string> errors;
 
-  Init(&argv_, &exec_argv_);
+  // This (approximately) duplicates some logic that has been moved to
+  // node::Start(), with the difference that here we explicitly call `exit()`.
+  int exit_code = Init(&argv_, &exec_argv_, &errors);
+
+  for (const std::string& error : errors)
+    fprintf(stderr, "%s: %s\n", argv_.at(0).c_str(), error.c_str());
+  if (exit_code != 0) exit(exit_code);
+
+  if (per_process::cli_options->print_version) {
+    printf("%s\n", NODE_VERSION);
+    exit(0);
+  }
+
+  if (per_process::cli_options->print_v8_help) {
+    V8::SetFlagsFromString("--help", 6);  // Doesn't return.
+    UNREACHABLE();
+  }
 
   *argc = argv_.size();
   *exec_argc = exec_argv_.size();
@@ -1653,6 +1650,16 @@
   if (isolate == nullptr)
     return 12;  // Signal internal error.
 
+  if (per_process::cli_options->print_version) {
+    printf("%s\n", NODE_VERSION);
+    return 0;
+  }
+
+  if (per_process::cli_options->print_v8_help) {
+    V8::SetFlagsFromString("--help", 6);  // Doesn't return.
+    UNREACHABLE();
+  }
+
   {
     Mutex::ScopedLock scoped_lock(per_process::main_isolate_mutex);
     CHECK_NULL(per_process::main_isolate);
@@ -1712,8 +1719,14 @@
 
   std::vector<std::string> args(argv, argv + argc);
   std::vector<std::string> exec_args;
+  std::vector<std::string> errors;
   // This needs to run *before* V8::Initialize().
-  Init(&args, &exec_args);
+  {
+    const int exit_code = Init(&args, &exec_args, &errors);
+    for (const std::string& error : errors)
+      fprintf(stderr, "%s: %s\n", args.at(0).c_str(), error.c_str());
+    if (exit_code != 0) return exit_code;
+  }
 
 #if HAVE_OPENSSL
   {