#include "node_options.h" // NOLINT(build/include_inline) #include "node_options-inl.h" #include "env-inl.h" #include "node_binding.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "node_sea.h" #include "uv.h" #if HAVE_OPENSSL #include "openssl/opensslv.h" #include "quic/guard.h" #endif #include #include #include #include #include #include #include #include using v8::Boolean; using v8::Context; using v8::FunctionCallbackInfo; using v8::Integer; using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::Map; using v8::Name; using v8::Null; using v8::Number; using v8::Object; using v8::String; using v8::Undefined; using v8::Value; namespace node { namespace per_process { Mutex cli_options_mutex; std::shared_ptr cli_options{new PerProcessOptions()}; } // namespace per_process void DebugOptions::CheckOptions(std::vector* errors, std::vector* argv) { #if !NODE_USE_V8_PLATFORM && !HAVE_INSPECTOR if (inspector_enabled) { errors->push_back("Inspector is not available when Node is compiled " "--without-v8-platform and --without-inspector."); } #endif if (deprecated_debug) { errors->push_back("[DEP0062]: `node --debug` and `node --debug-brk` " "are invalid. Please use `node --inspect` and " "`node --inspect-brk` instead."); } using std::operator""sv; auto entries = std::views::split(inspect_publish_uid_string, ","sv); inspect_publish_uid.console = false; inspect_publish_uid.http = false; for (const auto& entry : entries) { std::string_view destination(entry.data(), entry.size()); if (destination == "stderr"sv) { inspect_publish_uid.console = true; } else if (destination == "http"sv) { inspect_publish_uid.http = true; } else { errors->emplace_back("--inspect-publish-uid destination can be " "stderr or http"); } } } void PerProcessOptions::CheckOptions(std::vector* errors, std::vector* argv) { #if HAVE_OPENSSL if (use_openssl_ca && use_bundled_ca) { errors->push_back("either --use-openssl-ca or --use-bundled-ca can be " "used, not both"); } // Any value less than 2 disables use of the secure heap. #ifndef V8_ENABLE_SANDBOX // The secure heap is not supported when V8_ENABLE_SANDBOX is enabled. if (secure_heap >= 2) { if ((secure_heap & (secure_heap - 1)) != 0) errors->push_back("--secure-heap must be a power of 2"); secure_heap_min = std::min({ secure_heap, secure_heap_min, static_cast(std::numeric_limits::max())}); secure_heap_min = std::max(static_cast(2), secure_heap_min); if ((secure_heap_min & (secure_heap_min - 1)) != 0) errors->push_back("--secure-heap-min must be a power of 2"); } #endif // V8_ENABLE_SANDBOX #endif // HAVE_OPENSSL if (!build_sea.empty()) { #if defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION) errors->push_back("Single executable application is disabled.\n"); #elif !defined(HAVE_LIEF) errors->push_back( "Node.js must be built with LIEF to build support --build-sea.\n"); #endif // !defined(HAVE_LIEF) } if (use_largepages != "off" && use_largepages != "on" && use_largepages != "silent") { errors->push_back("invalid value for --use-largepages"); } per_isolate->CheckOptions(errors, argv); } void PerIsolateOptions::HandleMaxOldSpaceSizePercentage( std::vector* errors, std::string* max_old_space_size_percentage) { std::string original_input_for_error = *max_old_space_size_percentage; // Parse the percentage value char* end_ptr; double percentage = std::strtod(max_old_space_size_percentage->c_str(), &end_ptr); // Validate the percentage value if (*end_ptr != '\0' || percentage <= 0.0 || percentage > 100.0) { errors->push_back("--max-old-space-size-percentage must be greater " "than 0 and up to 100. Got: " + original_input_for_error); return; } // Get available memory in bytes #ifdef V8_COMPRESS_POINTERS // When pointer compression is enabled, V8 uses a 4 GiB heap limit. // We'll use the smaller of that or the total system memory as // reported by uv. uint64_t total_memory = std::min(uv_get_total_memory(), kMaxPointerCompressionHeap); // 4 GiB #else uint64_t total_memory = uv_get_total_memory(); #endif uint64_t constrained_memory = uv_get_constrained_memory(); // Use constrained memory if available, otherwise use total memory // This logic correctly handles the documented guarantees. // Use uint64_t for the result to prevent data loss on 32-bit systems. uint64_t available_memory = (constrained_memory > 0 && constrained_memory != UINT64_MAX) ? constrained_memory : total_memory; if (available_memory == 0) { errors->push_back("the available memory can not be calculated"); return; } // Convert to MB and calculate the percentage uint64_t memory_mb = available_memory / (1024 * 1024); uint64_t calculated_mb = static_cast(memory_mb * percentage / 100.0); // Convert back to string max_old_space_size = std::to_string(calculated_mb); } void PerIsolateOptions::CheckOptions(std::vector* errors, std::vector* argv) { if (!max_old_space_size_percentage.empty()) { HandleMaxOldSpaceSizePercentage(errors, &max_old_space_size_percentage); } per_env->CheckOptions(errors, argv); } void EnvironmentOptions::CheckOptions(std::vector* errors, std::vector* argv) { if (!input_type.empty()) { if (input_type != "commonjs" && input_type != "module" && input_type != "commonjs-typescript" && input_type != "module-typescript") { errors->push_back( "--input-type must be \"module\"," "\"commonjs\", \"module-typescript\" or \"commonjs-typescript\""); } } if (syntax_check_only && has_eval_string) { errors->push_back("either --check or --eval can be used, not both"); } if (!unhandled_rejections.empty() && unhandled_rejections != "warn-with-error-code" && unhandled_rejections != "throw" && unhandled_rejections != "strict" && unhandled_rejections != "warn" && unhandled_rejections != "none") { errors->push_back("invalid value for --unhandled-rejections"); } if (tls_min_v1_3 && tls_max_v1_2) { errors->push_back("either --tls-min-v1.3 or --tls-max-v1.2 can be " "used, not both"); } #if HAVE_OPENSSL if (use_system_ca && per_process::cli_options->use_openssl_ca) { errors->push_back("either --use-openssl-ca or --use-system-ca can be " "used, not both"); } #endif // HAVE_OPENSSL if (heap_snapshot_near_heap_limit < 0) { errors->push_back("--heapsnapshot-near-heap-limit must not be negative"); } if (!trace_require_module.empty() && trace_require_module != "all" && trace_require_module != "no-node-modules") { errors->push_back("invalid value for --trace-require-module"); } if (test_runner) { if (test_isolation == "none") { debug_options_.allow_attaching_debugger = true; } else { if (test_isolation != "process") { errors->push_back("invalid value for --test-isolation"); } #ifndef ALLOW_ATTACHING_DEBUGGER_IN_TEST_RUNNER debug_options_.allow_attaching_debugger = false; #endif } if (syntax_check_only) { errors->push_back("either --test or --check can be used, not both"); } if (has_eval_string) { errors->push_back("either --test or --eval can be used, not both"); } if (force_repl) { errors->push_back("either --test or --interactive can be used, not both"); } if (watch_mode_paths.size() > 0) { errors->push_back( "--watch-path cannot be used in combination with --test"); } } if (watch_mode) { if (syntax_check_only) { errors->push_back("either --watch or --check can be used, not both"); } else if (has_eval_string) { errors->push_back("either --watch or --eval can be used, not both"); } else if (force_repl) { errors->push_back("either --watch or --interactive " "can be used, not both"); } else if (test_runner_force_exit) { errors->push_back("either --watch or --test-force-exit " "can be used, not both"); } else if (!test_runner && watch_mode_paths.empty() && argv->size() < 1) { errors->push_back("--watch requires specifying a file"); } #ifndef ALLOW_ATTACHING_DEBUGGER_IN_WATCH_MODE debug_options_.allow_attaching_debugger = false; #endif } #if HAVE_INSPECTOR if (!cpu_prof) { if (!cpu_prof_name.empty()) { errors->push_back("--cpu-prof-name must be used with --cpu-prof"); } if (!cpu_prof_dir.empty()) { errors->push_back("--cpu-prof-dir must be used with --cpu-prof"); } // We can't catch the case where the value passed is the default value, // then the option just becomes a noop which is fine. if (cpu_prof_interval != kDefaultCpuProfInterval) { errors->push_back("--cpu-prof-interval must be used with --cpu-prof"); } } if (cpu_prof && cpu_prof_dir.empty() && !diagnostic_dir.empty()) { cpu_prof_dir = diagnostic_dir; } if (!heap_prof) { if (!heap_prof_name.empty()) { errors->push_back("--heap-prof-name must be used with --heap-prof"); } if (!heap_prof_dir.empty()) { errors->push_back("--heap-prof-dir must be used with --heap-prof"); } // We can't catch the case where the value passed is the default value, // then the option just becomes a noop which is fine. if (heap_prof_interval != kDefaultHeapProfInterval) { errors->push_back("--heap-prof-interval must be used with --heap-prof"); } } if (heap_prof && heap_prof_dir.empty() && !diagnostic_dir.empty()) { heap_prof_dir = diagnostic_dir; } debug_options_.CheckOptions(errors, argv); #endif // HAVE_INSPECTOR } namespace options_parser { // Helper function to convert option types to their string representation // and add them to a V8 Map static bool AddOptionTypeToObject(Isolate* isolate, Local context, Local object, const std::string& option_name, const OptionMappingDetails& option_details) { std::string type; switch (static_cast(option_details.type)) { case 0: // No-op case 1: // V8 flags break; // V8 and NoOp flags are not supported case 2: type = "boolean"; break; case 3: // integer case 4: // unsigned integer case 6: // host port type = "number"; break; case 5: // string type = "string"; break; case 7: // string array type = "array"; break; default: UNREACHABLE(); } if (type.empty()) { return true; // Skip this entry but continue processing } Local option_key; if (!String::NewFromUtf8(isolate, option_name.data(), v8::NewStringType::kNormal, option_name.size()) .ToLocal(&option_key)) { return true; // Skip this entry but continue processing } Local type_value; if (!String::NewFromUtf8( isolate, type.data(), v8::NewStringType::kNormal, type.size()) .ToLocal(&type_value)) { return true; // Skip this entry but continue processing } Local help_text; if (!String::NewFromUtf8(isolate, option_details.help_text.data(), v8::NewStringType::kNormal, option_details.help_text.size()) .ToLocal(&help_text)) { return true; // Skip this entry but continue processing } // Create an object with type and help_text properties Local null_value = Null(isolate); constexpr size_t kOptionInfoLength = 2; std::array, kOptionInfoLength> names = { String::NewFromUtf8Literal(isolate, "type"), String::NewFromUtf8Literal(isolate, "description")}; std::array, kOptionInfoLength> values = {type_value, help_text}; Local option_info = Object::New( isolate, null_value, names.data(), values.data(), kOptionInfoLength); if (object->Set(context, option_key, option_info).IsNothing()) { return false; // Error occurred, stop processing } return true; } class DebugOptionsParser : public OptionsParser { public: DebugOptionsParser(); }; class EnvironmentOptionsParser : public OptionsParser { public: EnvironmentOptionsParser(); explicit EnvironmentOptionsParser(const DebugOptionsParser& dop) : EnvironmentOptionsParser() { Insert(dop, &EnvironmentOptions::get_debug_options); } }; class PerIsolateOptionsParser : public OptionsParser { public: PerIsolateOptionsParser() = delete; explicit PerIsolateOptionsParser(const EnvironmentOptionsParser& eop); }; class PerProcessOptionsParser : public OptionsParser { public: PerProcessOptionsParser() = delete; explicit PerProcessOptionsParser(const PerIsolateOptionsParser& iop); }; #if HAVE_INSPECTOR const DebugOptionsParser _dop_instance{}; const EnvironmentOptionsParser _eop_instance{_dop_instance}; // This Parse is not dead code. It is used by embedders (e.g., Electron). template <> void Parse( StringVector* const args, StringVector* const exec_args, StringVector* const v8_args, DebugOptions* const options, OptionEnvvarSettings required_env_settings, StringVector* const errors) { _dop_instance.Parse( args, exec_args, v8_args, options, required_env_settings, errors); } #else const EnvironmentOptionsParser _eop_instance{}; #endif // HAVE_INSPECTOR const PerIsolateOptionsParser _piop_instance{_eop_instance}; const PerProcessOptionsParser _ppop_instance{_piop_instance}; template <> void Parse( StringVector* const args, StringVector* const exec_args, StringVector* const v8_args, PerIsolateOptions* const options, OptionEnvvarSettings required_env_settings, StringVector* const errors) { _piop_instance.Parse( args, exec_args, v8_args, options, required_env_settings, errors); } template <> void Parse( StringVector* const args, StringVector* const exec_args, StringVector* const v8_args, PerProcessOptions* const options, OptionEnvvarSettings required_env_settings, StringVector* const errors) { _ppop_instance.Parse( args, exec_args, v8_args, options, required_env_settings, errors); } // XXX: If you add an option here, please also add it to doc/node.1 and // doc/api/cli.md // TODO(addaleax): Make that unnecessary. DebugOptionsParser::DebugOptionsParser() { AddOption("--inspect-port", "set host:port for inspector", &DebugOptions::host_port, kAllowedInEnvvar); AddAlias("--debug-port", "--inspect-port"); AddOption("--inspect", "activate inspector on host:port (default: 127.0.0.1:9229)", &DebugOptions::inspector_enabled, kAllowedInEnvvar); AddAlias("--inspect=", { "--inspect-port", "--inspect" }); AddOption("--debug", "", &DebugOptions::deprecated_debug); AddAlias("--debug=", "--debug"); AddOption("--debug-brk", "", &DebugOptions::deprecated_debug); AddAlias("--debug-brk=", "--debug-brk"); AddOption("--inspect-brk", "activate inspector on host:port and break at start of user script", &DebugOptions::break_first_line, kAllowedInEnvvar); Implies("--inspect-brk", "--inspect"); AddAlias("--inspect-brk=", { "--inspect-port", "--inspect-brk" }); AddOption("--inspect-brk-node", "", &DebugOptions::break_node_first_line); Implies("--inspect-brk-node", "--inspect"); AddAlias("--inspect-brk-node=", { "--inspect-port", "--inspect-brk-node" }); AddOption( "--inspect-wait", "activate inspector on host:port and wait for debugger to be attached", &DebugOptions::inspect_wait, kAllowedInEnvvar); Implies("--inspect-wait", "--inspect"); AddAlias("--inspect-wait=", {"--inspect-port", "--inspect-wait"}); AddOption("--inspect-publish-uid", "comma separated list of destinations for inspector uid" "(default: stderr,http)", &DebugOptions::inspect_publish_uid_string, kAllowedInEnvvar); } EnvironmentOptionsParser::EnvironmentOptionsParser() { AddOption("--conditions", "additional user conditions for conditional exports and imports", &EnvironmentOptions::conditions, kAllowedInEnvvar); AddAlias("-C", "--conditions"); AddOption("--experimental-detect-module", "when ambiguous modules fail to evaluate because they contain " "ES module syntax, try again to evaluate them as ES modules", &EnvironmentOptions::detect_module, kAllowedInEnvvar, true); AddOption("--experimental-print-required-tla", "Print pending top-level await. If --require-module " "is true, evaluate asynchronous graphs loaded by `require()` but " "do not run the microtasks, in order to to find and print " "top-level await in the graph", &EnvironmentOptions::print_required_tla, kAllowedInEnvvar); AddOption("--require-module", "Allow loading synchronous ES Modules in require().", &EnvironmentOptions::require_module, kAllowedInEnvvar, true); AddOption("--experimental-require-module", "Legacy alias for --require-module", &EnvironmentOptions::require_module, kAllowedInEnvvar, true); Implies("--experimental-require-module", "--require-module"); AddOption("--diagnostic-dir", "set dir for all output files" " (default: current working directory)", &EnvironmentOptions::diagnostic_dir, kAllowedInEnvvar); AddOption("--disable-sigusr1", "Disable inspector thread to be listening for SIGUSR1 signal", &EnvironmentOptions::disable_sigusr1, kAllowedInEnvvar, false); AddOption("--dns-result-order", "set default value of verbatim in dns.lookup. Options are " "'ipv4first' (IPv4 addresses are placed before IPv6 addresses) " "'ipv6first' (IPv6 addresses are placed before IPv4 addresses) " "'verbatim' (addresses are in the order the DNS resolver " "returned)", &EnvironmentOptions::dns_result_order, kAllowedInEnvvar); AddOption("--network-family-autoselection", "Disable network address family autodetection algorithm", &EnvironmentOptions::network_family_autoselection, kAllowedInEnvvar, true); AddOption("--network-family-autoselection-attempt-timeout", "Sets the default value for the network family autoselection " "attempt timeout.", &EnvironmentOptions::network_family_autoselection_attempt_timeout, kAllowedInEnvvar); AddAlias("--enable-network-family-autoselection", "--network-family-autoselection"); AddOption("--enable-source-maps", "Source Map V3 support for stack traces", &EnvironmentOptions::enable_source_maps, kAllowedInEnvvar); AddOption("--entry-url", "Treat the entrypoint as a URL", &EnvironmentOptions::entry_is_url, kAllowedInEnvvar); AddOption("--experimental-addon-modules", "experimental import support for addons", &EnvironmentOptions::experimental_addon_modules, kAllowedInEnvvar); AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-eventsource", "experimental EventSource API", &EnvironmentOptions::experimental_eventsource, kAllowedInEnvvar, false); AddOption("--experimental-fetch", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-websocket", "experimental WebSocket API", &EnvironmentOptions::experimental_websocket, kAllowedInEnvvar, true); AddOption("--experimental-global-customevent", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-sqlite", "experimental node:sqlite module", &EnvironmentOptions::experimental_sqlite, kAllowedInEnvvar, true); AddOption("--experimental-stream-iter", "experimental iterable streams API (node:stream/iter)", &EnvironmentOptions::experimental_stream_iter, kAllowedInEnvvar); AddOption("--experimental-quic", #ifndef OPENSSL_NO_QUIC "experimental QUIC support", &EnvironmentOptions::experimental_quic, #else "" /* undocumented when no-op */, NoOp{}, #endif kAllowedInEnvvar); AddOption("--experimental-webstorage", "experimental Web Storage API", &EnvironmentOptions::webstorage, kAllowedInEnvvar, HAVE_SQLITE); AddAlias("--webstorage", "--experimental-webstorage"); AddOption("--localstorage-file", "file used to persist localStorage data", &EnvironmentOptions::localstorage_file, kAllowedInEnvvar); AddOption("--experimental-global-navigator", "expose experimental Navigator API on the global scope", &EnvironmentOptions::experimental_global_navigator, kAllowedInEnvvar, true); AddOption("--experimental-global-webcrypto", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-json-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-loader", "use the specified module as a custom loader", &EnvironmentOptions::userland_loaders, kAllowedInEnvvar); AddAlias("--loader", "--experimental-loader"); AddOption("--experimental-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-wasm-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-import-meta-resolve", "experimental ES Module import.meta.resolve() parentURL support", &EnvironmentOptions::experimental_import_meta_resolve, kAllowedInEnvvar); AddOption("--permission", "enable the permission system", &EnvironmentOptions::permission, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--permission-audit", "enable audit only for the permission system", &EnvironmentOptions::permission_audit, kAllowedInEnvvar, false); AddOption("--allow-fs-read", "allow permissions to read the filesystem", &EnvironmentOptions::allow_fs_read, kAllowedInEnvvar, OptionNamespaces::kPermissionNamespace); AddOption("--allow-fs-write", "allow permissions to write in the filesystem", &EnvironmentOptions::allow_fs_write, kAllowedInEnvvar, OptionNamespaces::kPermissionNamespace); AddOption("--allow-addons", "allow use of addons when any permissions are set", &EnvironmentOptions::allow_addons, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--allow-child-process", "allow use of child process when any permissions are set", &EnvironmentOptions::allow_child_process, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--allow-inspector", "allow use of inspector when any permissions are set", &EnvironmentOptions::allow_inspector, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--allow-net", "allow use of network when any permissions are set", &EnvironmentOptions::allow_net, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--allow-wasi", "allow wasi when any permissions are set", &EnvironmentOptions::allow_wasi, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--allow-worker", "allow worker threads when any permissions are set", &EnvironmentOptions::allow_worker_threads, kAllowedInEnvvar, false, OptionNamespaces::kPermissionNamespace); AddOption("--experimental-repl-await", "experimental await keyword support in REPL", &EnvironmentOptions::experimental_repl_await, kAllowedInEnvvar, true); AddOption("--experimental-vm-modules", "experimental ES Module support in vm module", &EnvironmentOptions::experimental_vm_modules, kAllowedInEnvvar); AddOption("--experimental-worker", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-report", "", NoOp{}, kAllowedInEnvvar); AddOption( "--experimental-wasi-unstable-preview1", "", NoOp{}, kAllowedInEnvvar); AddOption("--expose-gc", "expose gc extension", V8Option{}, kAllowedInEnvvar); AddOption("--async-context-frame", "Improve AsyncLocalStorage performance with AsyncContextFrame", &EnvironmentOptions::async_context_frame, kAllowedInEnvvar, true); AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals); AddOption("--frozen-intrinsics", "experimental frozen intrinsics support", &EnvironmentOptions::frozen_intrinsics, kAllowedInEnvvar); AddOption("--heapsnapshot-signal", "Generate heap snapshot on specified signal", &EnvironmentOptions::heap_snapshot_signal, kAllowedInEnvvar); AddOption("--heapsnapshot-near-heap-limit", "Generate heap snapshots whenever V8 is approaching " "the heap limit. No more than the specified number of " "heap snapshots will be generated.", &EnvironmentOptions::heap_snapshot_near_heap_limit, kAllowedInEnvvar); AddOption("--http-parser", "", NoOp{}, kAllowedInEnvvar); AddOption("--insecure-http-parser", "use an insecure HTTP parser that accepts invalid HTTP headers", &EnvironmentOptions::insecure_http_parser, kAllowedInEnvvar); AddOption("--input-type", "set module type for string input", &EnvironmentOptions::input_type, kAllowedInEnvvar); AddOption( "--experimental-specifier-resolution", "", NoOp{}, kAllowedInEnvvar); AddAlias("--es-module-specifier-resolution", "--experimental-specifier-resolution"); AddOption("--deprecation", "silence deprecation warnings", &EnvironmentOptions::deprecation, kAllowedInEnvvar, true); AddOption("--force-async-hooks-checks", "disable checks for async_hooks", &EnvironmentOptions::force_async_hooks_checks, kAllowedInEnvvar, true); AddOption( "--force-node-api-uncaught-exceptions-policy", "enforces 'uncaughtException' event on Node API asynchronous callbacks", &EnvironmentOptions::force_node_api_uncaught_exceptions_policy, kAllowedInEnvvar, false); AddOption("--addons", "disable loading native addons", &EnvironmentOptions::allow_native_addons, kAllowedInEnvvar, true); AddOption("--global-search-paths", "disable global module search paths", &EnvironmentOptions::global_search_paths, kAllowedInEnvvar, true); AddOption("--warnings", "silence all process warnings", &EnvironmentOptions::warnings, kAllowedInEnvvar, true); AddOption("--disable-warning", "silence specific process warnings", &EnvironmentOptions::disable_warnings, kAllowedInEnvvar); AddOption("--force-context-aware", "disable loading non-context-aware addons", &EnvironmentOptions::force_context_aware, kAllowedInEnvvar); AddOption("--pending-deprecation", "emit pending deprecation warnings", &EnvironmentOptions::pending_deprecation, kAllowedInEnvvar); AddOption("--use-env-proxy", "parse proxy settings from HTTP_PROXY/HTTPS_PROXY/NO_PROXY" "environment variables and apply the setting in global HTTP/HTTPS " "clients", &EnvironmentOptions::use_env_proxy, kAllowedInEnvvar); AddOption("--preserve-symlinks", "preserve symbolic links when resolving", &EnvironmentOptions::preserve_symlinks, kAllowedInEnvvar); AddOption("--preserve-symlinks-main", "preserve symbolic links when resolving the main module", &EnvironmentOptions::preserve_symlinks_main, kAllowedInEnvvar); AddOption("--prof", "Generate V8 profiler output.", V8Option{}); AddOption("--prof-process", "process V8 profiler output generated using --prof", &EnvironmentOptions::prof_process); // Options after --prof-process are passed through to the prof processor. AddAlias("--prof-process", {"--prof-process", "--"}); #if HAVE_INSPECTOR AddOption("--cpu-prof", "Start the V8 CPU profiler on start up, and write the CPU profile " "to disk before exit. If --cpu-prof-dir is not specified, write " "the profile to the current working directory.", &EnvironmentOptions::cpu_prof, kAllowedInEnvvar); AddOption("--cpu-prof-name", "specified file name of the V8 CPU profile generated with " "--cpu-prof", &EnvironmentOptions::cpu_prof_name, kAllowedInEnvvar); AddOption("--cpu-prof-interval", "specified sampling interval in microseconds for the V8 CPU " "profile generated with --cpu-prof. (default: 1000)", &EnvironmentOptions::cpu_prof_interval, kAllowedInEnvvar); AddOption("--cpu-prof-dir", "Directory where the V8 profiles generated by --cpu-prof will be " "placed. Does not affect --prof.", &EnvironmentOptions::cpu_prof_dir, kAllowedInEnvvar); AddOption("--experimental-network-inspection", "experimental network inspection support", &EnvironmentOptions::experimental_network_inspection); AddOption("--experimental-storage-inspection", "experimental storage inspection support", &EnvironmentOptions::experimental_storage_inspection); AddOption("--experimental-worker-inspection", "experimental worker inspection support", &EnvironmentOptions::experimental_worker_inspection); AddOption("--experimental-inspector-network-resource", "experimental load network resources via the inspector", &EnvironmentOptions::experimental_inspector_network_resource); AddOption( "--heap-prof", "Start the V8 heap profiler on start up, and write the heap profile " "to disk before exit. If --heap-prof-dir is not specified, write " "the profile to the current working directory.", &EnvironmentOptions::heap_prof, kAllowedInEnvvar); AddOption("--heap-prof-name", "specified file name of the V8 heap profile generated with " "--heap-prof", &EnvironmentOptions::heap_prof_name, kAllowedInEnvvar); AddOption("--heap-prof-dir", "Directory where the V8 heap profiles generated by --heap-prof " "will be placed.", &EnvironmentOptions::heap_prof_dir, kAllowedInEnvvar); AddOption("--heap-prof-interval", "specified sampling interval in bytes for the V8 heap " "profile generated with --heap-prof. (default: 512 * 1024)", &EnvironmentOptions::heap_prof_interval, kAllowedInEnvvar); #endif // HAVE_INSPECTOR AddOption("--max-http-header-size", "set the maximum size of HTTP headers (default: 16384 (16KB))", &EnvironmentOptions::max_http_header_size, kAllowedInEnvvar); AddOption("--redirect-warnings", "write warnings to file instead of stderr", &EnvironmentOptions::redirect_warnings, kAllowedInEnvvar); AddOption( "[has_env_file_string]", "", &EnvironmentOptions::has_env_file_string); AddOption("--env-file", "set environment variables from supplied file", &EnvironmentOptions::env_file); Implies("--env-file", "[has_env_file_string]"); AddOption("--env-file-if-exists", "set environment variables from supplied file", &EnvironmentOptions::optional_env_file); Implies("--env-file-if-exists", "[has_env_file_string]"); AddOption("--experimental-config-file", "set config file from supplied file", &EnvironmentOptions::experimental_config_file_path); AddOption("--experimental-default-config-file", "set config file from default config file", &EnvironmentOptions::experimental_default_config_file); AddOption("--test", "launch test runner on startup", &EnvironmentOptions::test_runner, kDisallowedInEnvvar, false, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-concurrency", "specify test runner concurrency", &EnvironmentOptions::test_runner_concurrency, kDisallowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-force-exit", "force test runner to exit upon completion", &EnvironmentOptions::test_runner_force_exit, kDisallowedInEnvvar, false, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-timeout", "specify test runner timeout", &EnvironmentOptions::test_runner_timeout, kDisallowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-update-snapshots", "regenerate test snapshots", &EnvironmentOptions::test_runner_update_snapshots, kDisallowedInEnvvar, false, OptionNamespaces::kTestRunnerNamespace); AddOption("--experimental-test-coverage", "enable code coverage in the test runner", &EnvironmentOptions::test_runner_coverage, kDisallowedInEnvvar, false, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-coverage-branches", "the branch coverage minimum threshold", &EnvironmentOptions::test_coverage_branches, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-coverage-functions", "the function coverage minimum threshold", &EnvironmentOptions::test_coverage_functions, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-coverage-lines", "the line coverage minimum threshold", &EnvironmentOptions::test_coverage_lines, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-isolation", "configures the type of test isolation used in the test runner", &EnvironmentOptions::test_isolation, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); // TODO(cjihrig): Remove this alias in a semver major. AddAlias("--experimental-test-isolation", "--test-isolation"); AddOption("--experimental-test-module-mocks", "enable module mocking in the test runner", &EnvironmentOptions::test_runner_module_mocks, kDisallowedInEnvvar, false, OptionNamespaces::kTestRunnerNamespace); AddOption("--experimental-test-snapshots", "", NoOp{}, kDisallowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-name-pattern", "run tests whose name matches this regular expression", &EnvironmentOptions::test_name_pattern, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-reporter", "report test output using the given reporter", &EnvironmentOptions::test_reporter, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-reporter-destination", "report given reporter to the given destination", &EnvironmentOptions::test_reporter_destination, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-only", "run tests with 'only' option set", &EnvironmentOptions::test_only, kAllowedInEnvvar, false, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-shard", "run test at specific shard", &EnvironmentOptions::test_shard, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-skip-pattern", "run tests whose name do not match this regular expression", &EnvironmentOptions::test_skip_pattern, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-coverage-include", "include files in coverage report that match this glob pattern", &EnvironmentOptions::coverage_include_pattern, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-coverage-exclude", "exclude files from coverage report that match this glob pattern", &EnvironmentOptions::coverage_exclude_pattern, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-global-setup", "specifies the path to the global setup file", &EnvironmentOptions::test_global_setup_path, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-rerun-failures", "specifies the path to the rerun state file", &EnvironmentOptions::test_rerun_failures_path, kAllowedInEnvvar, OptionNamespaces::kTestRunnerNamespace); AddOption("--test-udp-no-try-send", "", // For testing only. &EnvironmentOptions::test_udp_no_try_send, kDisallowedInEnvvar); AddOption("--throw-deprecation", "throw an exception on deprecations", &EnvironmentOptions::throw_deprecation, kAllowedInEnvvar); AddOption("--trace-deprecation", "show stack traces on deprecations", &EnvironmentOptions::trace_deprecation, kAllowedInEnvvar); AddOption("--trace-exit", "show stack trace when an environment exits", &EnvironmentOptions::trace_exit, kAllowedInEnvvar); AddOption("--trace-sync-io", "show stack trace when use of sync IO is detected after the " "first tick", &EnvironmentOptions::trace_sync_io, kAllowedInEnvvar); AddOption("--trace-tls", "prints TLS packet trace information to stderr", &EnvironmentOptions::trace_tls, kAllowedInEnvvar); AddOption("--trace-uncaught", "show stack traces for the `throw` behind uncaught exceptions", &EnvironmentOptions::trace_uncaught, kAllowedInEnvvar); AddOption("--trace-warnings", "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, kAllowedInEnvvar); AddOption("--trace-promises", "show stack traces on promise initialization and resolution", &EnvironmentOptions::trace_promises, kAllowedInEnvvar); AddOption("--trace-env", "Print accesses to the environment variables", &EnvironmentOptions::trace_env, kAllowedInEnvvar); Implies("--trace-env-js-stack", "--trace-env"); Implies("--trace-env-native-stack", "--trace-env"); AddOption("--trace-env-js-stack", "Print accesses to the environment variables and the JavaScript " "stack trace", &EnvironmentOptions::trace_env_js_stack, kAllowedInEnvvar); AddOption( "--trace-env-native-stack", "Print accesses to the environment variables and the native stack trace", &EnvironmentOptions::trace_env_native_stack, kAllowedInEnvvar); #if HAVE_OPENSSL AddOption("--use-system-ca", "use system's CA store", &EnvironmentOptions::use_system_ca, kAllowedInEnvvar); #endif // HAVE_OPENSSL AddOption( "--trace-require-module", "Print access to require(esm). Options are 'all' (print all usage) and " "'no-node-modules' (excluding usage from the node_modules folder)", &EnvironmentOptions::trace_require_module, kAllowedInEnvvar); AddOption("--extra-info-on-fatal-exception", "hide extra information on fatal exception that causes exit", &EnvironmentOptions::extra_info_on_fatal_exception, kAllowedInEnvvar, true); AddOption("--unhandled-rejections", "define unhandled rejections behavior. Options are 'strict' " "(always raise an error), 'throw' (raise an error unless " "'unhandledRejection' hook is set), 'warn' (log a warning), 'none' " "(silence warnings), 'warn-with-error-code' (log a warning and set " "exit code 1 unless 'unhandledRejection' hook is set). (default: " "throw)", &EnvironmentOptions::unhandled_rejections, kAllowedInEnvvar); AddOption("--verify-base-objects", "", /* undocumented, only for debugging */ &EnvironmentOptions::verify_base_objects, kAllowedInEnvvar); AddOption("--watch", "run in watch mode", &EnvironmentOptions::watch_mode, kAllowedInEnvvar, false, OptionNamespaces::kWatchNamespace); AddOption("--watch-path", "path to watch", &EnvironmentOptions::watch_mode_paths, kAllowedInEnvvar, OptionNamespaces::kWatchNamespace); AddOption("--watch-kill-signal", "kill signal to send to the process on watch mode restarts" "(default: SIGTERM)", &EnvironmentOptions::watch_mode_kill_signal, kAllowedInEnvvar, OptionNamespaces::kWatchNamespace); AddOption("--watch-preserve-output", "preserve outputs on watch mode restart", &EnvironmentOptions::watch_mode_preserve_output, kAllowedInEnvvar, false, OptionNamespaces::kWatchNamespace); Implies("--watch-path", "--watch"); AddOption("--check", "syntax check script without executing", &EnvironmentOptions::syntax_check_only); AddAlias("-c", "--check"); // This option is only so that we can tell --eval with an empty string from // no eval at all. Having it not start with a dash makes it inaccessible // from the parser itself, but available for using Implies(). // TODO(addaleax): When moving --help over to something generated from the // programmatic descriptions, this will need some special care. // (See also [ssl_openssl_cert_store] below.) AddOption("[has_eval_string]", "", &EnvironmentOptions::has_eval_string); AddOption("--eval", "evaluate script", &EnvironmentOptions::eval_string); Implies("--eval", "[has_eval_string]"); AddOption("--print", "evaluate script and print result", &EnvironmentOptions::print_eval); AddAlias("-e", "--eval"); AddAlias("--print ", "-pe"); AddAlias("-pe", { "--print", "--eval" }); AddAlias("-p", "--print"); AddOption("--require", "CommonJS module to preload (option can be repeated)", &EnvironmentOptions::preload_cjs_modules, kAllowedInEnvvar); AddAlias("-r", "--require"); AddOption("--import", "ES module to preload (option can be repeated)", &EnvironmentOptions::preload_esm_modules, kAllowedInEnvvar); AddOption("--strip-types", "Type-stripping for TypeScript files.", &EnvironmentOptions::strip_types, kAllowedInEnvvar, HAVE_AMARO); AddAlias("--experimental-strip-types", "--strip-types"); AddOption("--interactive", "always enter the REPL even if stdin does not appear " "to be a terminal", &EnvironmentOptions::force_repl); AddAlias("-i", "--interactive"); AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--tls-keylog", "log TLS decryption keys to named file for traffic analysis", &EnvironmentOptions::tls_keylog, kAllowedInEnvvar); AddOption("--tls-min-v1.0", "set default TLS minimum to TLSv1.0 (default: TLSv1.2)", &EnvironmentOptions::tls_min_v1_0, kAllowedInEnvvar); AddOption("--tls-min-v1.1", "set default TLS minimum to TLSv1.1 (default: TLSv1.2)", &EnvironmentOptions::tls_min_v1_1, kAllowedInEnvvar); AddOption("--tls-min-v1.2", "set default TLS minimum to TLSv1.2 (default: TLSv1.2)", &EnvironmentOptions::tls_min_v1_2, kAllowedInEnvvar); AddOption("--tls-min-v1.3", "set default TLS minimum to TLSv1.3 (default: TLSv1.2)", &EnvironmentOptions::tls_min_v1_3, kAllowedInEnvvar); AddOption("--tls-max-v1.2", "set default TLS maximum to TLSv1.2 (default: TLSv1.3)", &EnvironmentOptions::tls_max_v1_2, kAllowedInEnvvar); // Current plan is: // - 11.x and below: TLS1.3 is opt-in with --tls-max-v1.3 // - 12.x: TLS1.3 is opt-out with --tls-max-v1.2 // In either case, support both options they are uniformly available. AddOption("--tls-max-v1.3", "set default TLS maximum to TLSv1.3 (default: TLSv1.3)", &EnvironmentOptions::tls_max_v1_3, kAllowedInEnvvar); AddOption("--report-exclude-env", "Exclude environment variables when generating report" " (default: false)", &EnvironmentOptions::report_exclude_env, kAllowedInEnvvar); AddOption("--report-exclude-network", "exclude network interface diagnostics." " (default: false)", &EnvironmentOptions::report_exclude_network, kAllowedInEnvvar); } PerIsolateOptionsParser::PerIsolateOptionsParser( const EnvironmentOptionsParser& eop) { AddOption("--track-heap-objects", "track heap object allocations for heap snapshots", &PerIsolateOptions::track_heap_objects, kAllowedInEnvvar); // Explicitly add some V8 flags to mark them as allowed in NODE_OPTIONS. AddOption("--abort-on-uncaught-exception", "aborting instead of exiting causes a core file to be generated " "for analysis", V8Option{}, kAllowedInEnvvar); AddOption("--interpreted-frames-native-stack", "help system profilers to translate JavaScript interpreted frames", V8Option{}, kAllowedInEnvvar); AddOption("--max-heap-size", "", V8Option{}, kAllowedInEnvvar); AddOption("--max-old-space-size", "", V8Option{}, kAllowedInEnvvar); AddOption("--max-old-space-size-percentage", "set V8's max old space size as a percentage of available memory " "(e.g., '50%'). Takes precedence over --max-old-space-size.", &PerIsolateOptions::max_old_space_size_percentage, kAllowedInEnvvar); AddOption("--max-semi-space-size", "", V8Option{}, kAllowedInEnvvar); AddOption("--perf-basic-prof", "", V8Option{}, kAllowedInEnvvar); AddOption( "--perf-basic-prof-only-functions", "", V8Option{}, kAllowedInEnvvar); AddOption("--perf-prof", "", V8Option{}, kAllowedInEnvvar); AddOption("--perf-prof-unwinding-info", "", V8Option{}, kAllowedInEnvvar); AddOption("--stack-trace-limit", "", &PerIsolateOptions::stack_trace_limit, kAllowedInEnvvar); AddOption("--disallow-code-generation-from-strings", "disallow eval and friends", V8Option{}, kAllowedInEnvvar); AddOption("--jitless", "disable runtime allocation of executable memory", V8Option{}, kAllowedInEnvvar); AddOption("--report-uncaught-exception", "generate diagnostic report on uncaught exceptions", &PerIsolateOptions::report_uncaught_exception, kAllowedInEnvvar); AddOption("--report-on-signal", "generate diagnostic report upon receiving signals", &PerIsolateOptions::report_on_signal, kAllowedInEnvvar); AddOption("--report-signal", "causes diagnostic report to be produced on provided signal," " unsupported in Windows. (default: SIGUSR2)", &PerIsolateOptions::report_signal, kAllowedInEnvvar); Implies("--report-signal", "--report-on-signal"); AddOption("--enable-etw-stack-walking", "provides heap data to ETW Windows native tracing", V8Option{}, kAllowedInEnvvar); AddOption("--experimental-top-level-await", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-shadow-realm", "", &PerIsolateOptions::experimental_shadow_realm, kAllowedInEnvvar); AddOption("--harmony-shadow-realm", "", V8Option{}); Implies("--experimental-shadow-realm", "--harmony-shadow-realm"); Implies("--harmony-shadow-realm", "--experimental-shadow-realm"); ImpliesNot("--no-harmony-shadow-realm", "--experimental-shadow-realm"); AddOption("--build-snapshot", "Generate a snapshot blob when the process exits.", &PerIsolateOptions::build_snapshot, kDisallowedInEnvvar); AddOption("--build-snapshot-config", "Generate a snapshot blob when the process exits using a" "JSON configuration in the specified path.", &PerIsolateOptions::build_snapshot_config, kDisallowedInEnvvar); Implies("--build-snapshot-config", "--build-snapshot"); Insert(eop, &PerIsolateOptions::get_per_env_options); } PerProcessOptionsParser::PerProcessOptionsParser( const PerIsolateOptionsParser& iop) { AddOption("--title", "the process title to use on startup", &PerProcessOptions::title, kAllowedInEnvvar); AddOption("--trace-event-categories", "comma separated list of trace event categories to record", &PerProcessOptions::trace_event_categories, kAllowedInEnvvar); AddOption("--trace-event-file-pattern", "Template string specifying the filepath for the trace-events " "data, it supports ${rotation} and ${pid}.", &PerProcessOptions::trace_event_file_pattern, kAllowedInEnvvar); AddAlias("--trace-events-enabled", { "--trace-event-categories", "v8,node,node.async_hooks" }); AddOption("--v8-pool-size", "set V8's thread pool size", &PerProcessOptions::v8_thread_pool_size, kAllowedInEnvvar); AddOption("--zero-fill-buffers", "automatically zero-fill all newly allocated Buffer instances", &PerProcessOptions::zero_fill_all_buffers, kAllowedInEnvvar); AddOption("--debug-arraybuffer-allocations", "", /* undocumented, only for debugging */ &PerProcessOptions::debug_arraybuffer_allocations, kAllowedInEnvvar); AddOption("--disable-proto", "disable Object.prototype.__proto__", &PerProcessOptions::disable_proto, kAllowedInEnvvar); AddOption("--node-snapshot", "", // It's a debug-only option. &PerProcessOptions::node_snapshot, kAllowedInEnvvar); AddOption("--snapshot-blob", "Path to the snapshot blob that's either the result of snapshot" "building, or the blob that is used to restore the application " "state", &PerProcessOptions::snapshot_blob, kAllowedInEnvvar); // 12.x renamed this inadvertently, so alias it for consistency within the // release line, while using the original name for consistency with older // release lines. AddOption("--security-revert", "", &PerProcessOptions::security_reverts); AddAlias("--security-reverts", "--security-revert"); AddOption("--completion-bash", "print source-able bash completion script", &PerProcessOptions::print_bash_completion); AddOption("--help", "print node command line options", &PerProcessOptions::print_help); AddAlias("-h", "--help"); AddOption( "--version", "print Node.js version", &PerProcessOptions::print_version); AddAlias("-v", "--version"); AddOption("--v8-options", "print V8 command line options", &PerProcessOptions::print_v8_help); AddOption("--report-compact", "output compact single-line JSON", &PerProcessOptions::report_compact, kAllowedInEnvvar); AddOption("--report-dir", "define custom report pathname." " (default: current working directory)", &PerProcessOptions::report_directory, kAllowedInEnvvar); AddAlias("--report-directory", "--report-dir"); AddOption("--report-filename", "define custom report file name." " (default: YYYYMMDD.HHMMSS.PID.SEQUENCE#.txt)", &PerProcessOptions::report_filename, kAllowedInEnvvar); AddOption("--report-on-fatalerror", "generate diagnostic report on fatal (internal) errors", &PerProcessOptions::report_on_fatalerror, kAllowedInEnvvar); #ifdef NODE_HAVE_I18N_SUPPORT AddOption("--icu-data-dir", "set ICU data load path to dir (overrides NODE_ICU_DATA)" #ifndef NODE_HAVE_SMALL_ICU " (note: linked-in ICU data is present)" #endif , &PerProcessOptions::icu_data_dir, kAllowedInEnvvar); #endif #if HAVE_OPENSSL AddOption("--openssl-config", "load OpenSSL configuration from the specified file " "(overrides OPENSSL_CONF)", &PerProcessOptions::openssl_config, kAllowedInEnvvar); AddOption("--tls-cipher-list", "use an alternative default TLS cipher list", &PerProcessOptions::tls_cipher_list, kAllowedInEnvvar); AddOption("--use-openssl-ca", "use OpenSSL's default CA store" #if defined(NODE_OPENSSL_CERT_STORE) " (default)" #endif , &PerProcessOptions::use_openssl_ca, kAllowedInEnvvar); AddOption("--use-bundled-ca", "use bundled CA store" #if !defined(NODE_OPENSSL_CERT_STORE) " (default)" #endif , &PerProcessOptions::use_bundled_ca, kAllowedInEnvvar); // Similar to [has_eval_string] above, except that the separation between // this and use_openssl_ca only exists for option validation after parsing. // This is not ideal. AddOption("[ssl_openssl_cert_store]", "", &PerProcessOptions::ssl_openssl_cert_store); Implies("--use-openssl-ca", "[ssl_openssl_cert_store]"); ImpliesNot("--use-bundled-ca", "[ssl_openssl_cert_store]"); AddOption("--enable-fips", "enable FIPS crypto at startup", &PerProcessOptions::enable_fips_crypto, kAllowedInEnvvar); AddOption("--force-fips", "force FIPS crypto (cannot be disabled)", &PerProcessOptions::force_fips_crypto, kAllowedInEnvvar); #ifndef V8_ENABLE_SANDBOX AddOption("--secure-heap", "total size of the OpenSSL secure heap", &PerProcessOptions::secure_heap, kAllowedInEnvvar); AddOption("--secure-heap-min", "minimum allocation size from the OpenSSL secure heap", &PerProcessOptions::secure_heap_min, kAllowedInEnvvar); #endif // V8_ENABLE_SANDBOX #endif // HAVE_OPENSSL #if OPENSSL_VERSION_MAJOR >= 3 AddOption("--openssl-legacy-provider", "enable OpenSSL 3.0 legacy provider", &PerProcessOptions::openssl_legacy_provider, kAllowedInEnvvar); AddOption("--openssl-shared-config", "enable OpenSSL shared configuration", &PerProcessOptions::openssl_shared_config, kAllowedInEnvvar); #endif // OPENSSL_VERSION_MAJOR AddOption("--use-largepages", "Map the Node.js static code to large pages. Options are " "'off' (the default value, meaning do not map), " "'on' (map and ignore failure, reporting it to stderr), " "or 'silent' (map and silently ignore failure)", &PerProcessOptions::use_largepages, kAllowedInEnvvar); AddOption("--trace-sigint", "enable printing JavaScript stacktrace on SIGINT", &PerProcessOptions::trace_sigint, kAllowedInEnvvar); Insert(iop, &PerProcessOptions::get_per_isolate_options); AddOption("--node-memory-debug", "Run with extra debug checks for memory leaks in Node.js itself", NoOp{}, kAllowedInEnvvar); Implies("--node-memory-debug", "--debug-arraybuffer-allocations"); Implies("--node-memory-debug", "--verify-base-objects"); AddOption("--experimental-sea-config", "Generate a blob that can be embedded into the single executable " "application", &PerProcessOptions::experimental_sea_config); AddOption("--run", "Run a script specified in package.json", &PerProcessOptions::run); AddOption( "--disable-wasm-trap-handler", "Disable trap-handler-based WebAssembly bound checks. V8 will insert " "inline bound checks when compiling WebAssembly which may slow down " "performance.", &PerProcessOptions::disable_wasm_trap_handler, kAllowedInEnvvar); AddOption("--build-sea", "Build a Node.js single executable application", &PerProcessOptions::build_sea); } inline std::string RemoveBrackets(const std::string& host) { if (!host.empty() && host.front() == '[' && host.back() == ']') return host.substr(1, host.size() - 2); else return host; } inline uint16_t ParseAndValidatePort(const std::string_view port, std::vector* errors) { uint16_t result{}; auto r = std::from_chars(port.data(), port.data() + port.size(), result); if (r.ec == std::errc::result_out_of_range || (result != 0 && result < 1024)) { errors->push_back("must be 0 or in range 1024 to 65535."); } return result; } HostPort SplitHostPort(const std::string& arg, std::vector* errors) { // remove_brackets only works if no port is specified // so if it has an effect only an IPv6 address was specified. std::string host = RemoveBrackets(arg); if (host.length() < arg.length()) return HostPort{host, DebugOptions::kDefaultInspectorPort}; size_t colon = arg.rfind(':'); if (colon == std::string::npos) { // Either a port number or a host name. Assume that // if it's not all decimal digits, it's a host name. for (char c : arg) { if (c < '0' || c > '9') { return HostPort{arg, DebugOptions::kDefaultInspectorPort}; } } return HostPort { "", ParseAndValidatePort(arg, errors) }; } // Host and port found: return HostPort { RemoveBrackets(arg.substr(0, colon)), ParseAndValidatePort(arg.substr(colon + 1), errors) }; } std::string GetBashCompletion() { Mutex::ScopedLock lock(per_process::cli_options_mutex); const auto& parser = _ppop_instance; std::ostringstream out; out << "_node_complete() {\n" " local cur_word options\n" " cur_word=\"${COMP_WORDS[COMP_CWORD]}\"\n" " if [[ \"${cur_word}\" == -* ]] ; then\n" " COMPREPLY=( $(compgen -W '"; for (const auto& item : parser.options_) { if (item.first[0] != '[') { out << item.first << " "; } } for (const auto& item : parser.aliases_) { if (item.first[0] != '[') { out << item.first << " "; } } if (parser.aliases_.size() > 0) { out.seekp(-1, out.cur); // Strip the trailing space } out << "' -- \"${cur_word}\") )\n" " return 0\n" " else\n" " COMPREPLY=( $(compgen -f \"${cur_word}\") )\n" " return 0\n" " fi\n" "}\n" "complete -o filenames -o nospace -o bashdefault " "-F _node_complete node node_g"; return out.str(); } std::unordered_map MapEnvOptionsFlagInputType() { std::unordered_map type_map; const auto& parser = _ppop_instance; for (const auto& item : parser.options_) { if (!item.first.empty() && !item.first.starts_with('[') && item.second.env_setting == kAllowedInEnvvar) { const auto mapping_details = options_parser::OptionMappingDetails{ item.second.type, item.second.help_text, }; type_map[item.first] = mapping_details; } } return type_map; } std::vector MapAvailableNamespaces() { std::vector namespaceNames; auto availableNamespaces = AllNamespaces(); for (size_t i = 1; i < availableNamespaces.size(); i++) { OptionNamespaces ns = availableNamespaces[i]; std::string ns_string = NamespaceEnumToString(ns); if (!ns_string.empty()) { namespaceNames.push_back(ns_string); } } return namespaceNames; } std::unordered_map MapOptionsByNamespace(std::string namespace_name) { std::unordered_map type_map; const auto& parser = _ppop_instance; for (const auto& item : parser.options_) { if (!item.first.empty() && !item.first.starts_with('[') && item.second.namespace_id == namespace_name) { const auto mapping_details = options_parser::OptionMappingDetails{ item.second.type, item.second.help_text, }; type_map[item.first] = mapping_details; } } return type_map; } std::unordered_map< std::string, std::unordered_map> MapNamespaceOptionsAssociations() { std::vector available_namespaces = options_parser::MapAvailableNamespaces(); std::unordered_map< std::string, std::unordered_map> namespace_option_mapping; for (const std::string& available_namespace : available_namespaces) { namespace_option_mapping[available_namespace] = options_parser::MapOptionsByNamespace(available_namespace); } return namespace_option_mapping; } struct IterateCLIOptionsScope { explicit IterateCLIOptionsScope(Environment* env) { // Temporarily act as if the current Environment's/IsolateData's options // were the default options, i.e. like they are the ones we'd access for // global options parsing, so that all options are available from the main // parser. original_per_isolate = per_process::cli_options->per_isolate; per_process::cli_options->per_isolate = env->isolate_data()->options(); original_per_env = per_process::cli_options->per_isolate->per_env; per_process::cli_options->per_isolate->per_env = env->options(); } ~IterateCLIOptionsScope() { per_process::cli_options->per_isolate->per_env = original_per_env; per_process::cli_options->per_isolate = original_per_isolate; } std::shared_ptr original_per_env; std::shared_ptr original_per_isolate; }; // Return a map containing all the options and their metadata as well // as the aliases void GetCLIOptionsValues(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->has_run_bootstrapping_code()) { // No code because this is an assertion. THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( isolate, "Should not query options before bootstrapping is done"); } env->set_has_serialized_options(true); Mutex::ScopedLock lock(per_process::cli_options_mutex); IterateCLIOptionsScope s(env); LocalVector option_names(isolate); LocalVector option_values(isolate); option_names.reserve(_ppop_instance.options_.size() * 2); option_values.reserve(_ppop_instance.options_.size() * 2); Local undefined_value = Undefined(isolate); Local null_value = Null(isolate); for (const auto& item : _ppop_instance.options_) { Local value; const auto& option_info = item.second; auto field = option_info.field; PerProcessOptions* opts = per_process::cli_options.get(); switch (option_info.type) { case kNoOp: case kV8Option: // Special case for --abort-on-uncaught-exception which is also // respected by Node.js internals if (item.first == "--abort-on-uncaught-exception") { value = Boolean::New(isolate, s.original_per_env->abort_on_uncaught_exception); } else if (item.first == "--stack-trace-limit") { value = Number::New(isolate, static_cast( *_ppop_instance.Lookup(field, opts))); } else { value = undefined_value; } break; case kBoolean: { bool original_value = *_ppop_instance.Lookup(field, opts); value = Boolean::New(isolate, original_value); // Add --no-* entries. std::string negated_name = "--no" + item.first.substr(1, item.first.size()); Local negated_value = Boolean::New(isolate, !original_value); Local negated_name_v8; if (!ToV8Value(context, negated_name).ToLocal(&negated_name_v8)) { return; } option_names.push_back(negated_name_v8.As()); option_values.push_back(negated_value); break; } case kInteger: value = Number::New( isolate, static_cast(*_ppop_instance.Lookup(field, opts))); break; case kUInteger: value = Number::New( isolate, static_cast(*_ppop_instance.Lookup(field, opts))); break; case kString: if (!ToV8Value(context, *_ppop_instance.Lookup(field, opts)) .ToLocal(&value)) { return; } break; case kStringList: if (!ToV8Value(context, *_ppop_instance.Lookup(field, opts)) .ToLocal(&value)) { return; } break; case kHostPort: { const HostPort& host_port = *_ppop_instance.Lookup(field, opts); Local host; if (!ToV8Value(context, host_port.host()).ToLocal(&host)) { return; } constexpr size_t kHostPortLength = 2; std::array, kHostPortLength> names = {env->host_string(), env->port_string()}; std::array, kHostPortLength> values = { host, Integer::New(isolate, host_port.port())}; value = Object::New( isolate, null_value, names.data(), values.data(), kHostPortLength); break; } default: UNREACHABLE(); } CHECK(!value.IsEmpty()); Local name; if (!ToV8Value(context, item.first).ToLocal(&name)) { return; } option_names.push_back(name.As()); option_values.push_back(value); } Local options = Object::New(isolate, null_value, option_names.data(), option_values.data(), option_values.size()); args.GetReturnValue().Set(options); } void GetCLIOptionsInfo(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->has_run_bootstrapping_code()) { // No code because this is an assertion. THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( isolate, "Should not query options before bootstrapping is done"); } Mutex::ScopedLock lock(per_process::cli_options_mutex); IterateCLIOptionsScope s(env); Local options = Map::New(isolate); if (options ->SetPrototypeV2(context, env->primordials_safe_map_prototype_object()) .IsNothing()) { return; } Local null_value = Null(isolate); for (const auto& item : _ppop_instance.options_) { const auto& option_info = item.second; auto field = option_info.field; Local name; Local help_text; if (!ToV8Value(context, item.first).ToLocal(&name) || !ToV8Value(context, option_info.help_text).ToLocal(&help_text)) { return; } constexpr size_t kInfoSize = 4; std::array, kInfoSize> names = { env->help_text_string(), env->env_var_settings_string(), env->type_string(), env->default_is_true_string(), }; std::array, kInfoSize> values = { help_text, Integer::New(isolate, static_cast(option_info.env_setting)), Integer::New(isolate, static_cast(option_info.type)), Boolean::New(isolate, option_info.default_is_true), }; Local info = Object::New( isolate, null_value, names.data(), values.data(), kInfoSize); if (options->Set(context, name, info).IsEmpty()) { return; } } Local aliases; if (!ToV8Value(context, _ppop_instance.aliases_).ToLocal(&aliases)) return; if (aliases.As() ->SetPrototypeV2(context, env->primordials_safe_map_prototype_object()) .IsNothing()) { return; } constexpr size_t kRetLength = 2; std::array, kRetLength> names = {env->options_string(), env->aliases_string()}; std::array, kRetLength> values = {options, aliases}; Local ret = Object::New(isolate, null_value, names.data(), values.data(), kRetLength); args.GetReturnValue().Set(ret); } void GetEmbedderOptions(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!env->has_run_bootstrapping_code()) { // No code because this is an assertion. THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( env->isolate(), "Should not query options before bootstrapping is done"); } Isolate* isolate = args.GetIsolate(); constexpr size_t kOptionsSize = 4; std::array, kOptionsSize> names = { FIXED_ONE_BYTE_STRING(env->isolate(), "shouldNotRegisterESMLoader"), FIXED_ONE_BYTE_STRING(env->isolate(), "noGlobalSearchPaths"), FIXED_ONE_BYTE_STRING(env->isolate(), "noBrowserGlobals"), FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload")}; std::array, kOptionsSize> values = { Boolean::New(isolate, env->should_not_register_esm_loader()), Boolean::New(isolate, env->no_global_search_paths()), Boolean::New(isolate, env->no_browser_globals()), Boolean::New(isolate, env->embedder_preload() != nullptr)}; Local ret = Object::New( isolate, Null(isolate), names.data(), values.data(), kOptionsSize); args.GetReturnValue().Set(ret); } // This function returns an object containing all the options available // as NODE_OPTIONS and their metadata (input type and help text) // Example --experimental-transform metadata: // { type: kBoolean, help_text: "..." } // This is used to determine the type of the input for each option // to generate the config file json schema void GetEnvOptionsInputType(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->has_run_bootstrapping_code()) { // No code because this is an assertion. THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( isolate, "Should not query options before bootstrapping is done"); } Mutex::ScopedLock lock(per_process::cli_options_mutex); Local options_metadata = Object::New(isolate); for (const auto& item : _ppop_instance.options_) { if (!item.first.empty() && !item.first.starts_with('[') && item.second.env_setting == kAllowedInEnvvar) { const auto mapping_details = options_parser::OptionMappingDetails{ item.second.type, item.second.help_text, }; if (!AddOptionTypeToObject(isolate, context, options_metadata, item.first, mapping_details)) { return; } } } args.GetReturnValue().Set(options_metadata); } // This function returns a two-level nested map where: // - Keys are namespace names (e.g., "testRunner") // - Values are objects mapping option names to their metadata // This is used for config file JSON schema generation void GetNamespaceOptionsInputType(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->has_run_bootstrapping_code()) { // No code because this is an assertion. THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( isolate, "Should not query options before bootstrapping is done"); } Mutex::ScopedLock lock(per_process::cli_options_mutex); Local namespaces_metadata = Map::New(isolate); // Get the mapping of namespaces to their options and metadata auto namespace_options = options_parser::MapNamespaceOptionsAssociations(); for (const auto& ns_entry : namespace_options) { const std::string& namespace_name = ns_entry.first; const auto& options_map = ns_entry.second; Local options_metadata = Object::New(isolate); for (const auto& opt_entry : options_map) { const std::string& option_name = opt_entry.first; const options_parser::OptionMappingDetails& option_details = opt_entry.second; if (!AddOptionTypeToObject(isolate, context, options_metadata, option_name, option_details)) { return; } } // Only add namespaces that have options if (!options_metadata.IsEmpty()) { Local namespace_key; if (!String::NewFromUtf8(isolate, namespace_name.data(), v8::NewStringType::kNormal, namespace_name.size()) .ToLocal(&namespace_key)) { continue; } if (namespaces_metadata->Set(context, namespace_key, options_metadata) .IsEmpty()) { return; } } } args.GetReturnValue().Set(namespaces_metadata); } // Return an array containing all currently active options as flag // strings from all sources (command line, NODE_OPTIONS, config file) void GetOptionsAsFlags(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->has_run_bootstrapping_code()) { // No code because this is an assertion. THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING( isolate, "Should not query options before bootstrapping is done"); } env->set_has_serialized_options(true); Mutex::ScopedLock lock(per_process::cli_options_mutex); IterateCLIOptionsScope s(env); std::vector flags; PerProcessOptions* opts = per_process::cli_options.get(); for (const auto& item : _ppop_instance.options_) { const std::string& option_name = item.first; const auto& option_info = item.second; auto field = option_info.field; // TODO(pmarchini): Skip internal options for the moment as probably not // required if (option_name.empty() || option_name.starts_with('[')) { continue; } // Skip V8 options and NoOp options - only Node.js-specific options if (option_info.type == kNoOp || option_info.type == kV8Option) { continue; } switch (option_info.type) { case kBoolean: { bool current_value = *_ppop_instance.Lookup(field, opts); // For boolean options with default_is_true, we want the opposite logic if (option_info.default_is_true) { if (!current_value) { // If default is true and current is false, add --no-* flag flags.push_back("--no-" + option_name.substr(2)); } } else { if (current_value) { // If default is false and current is true, add --flag flags.push_back(option_name); } } break; } case kInteger: { int64_t current_value = *_ppop_instance.Lookup(field, opts); flags.push_back(option_name + "=" + std::to_string(current_value)); break; } case kUInteger: { uint64_t current_value = *_ppop_instance.Lookup(field, opts); flags.push_back(option_name + "=" + std::to_string(current_value)); break; } case kString: { const std::string& current_value = *_ppop_instance.Lookup(field, opts); // Only include if not empty if (!current_value.empty()) { flags.push_back(option_name + "=" + current_value); } break; } case kStringList: { const std::vector& current_values = *_ppop_instance.Lookup(field, opts); // Add each string in the list as a separate flag for (const std::string& value : current_values) { flags.push_back(option_name + "=" + value); } break; } case kHostPort: { const HostPort& host_port = *_ppop_instance.Lookup(field, opts); // Only include if host is not empty or port is not default if (!host_port.host().empty() || host_port.port() != 0) { std::string host_port_str = host_port.host(); if (host_port.port() != 0) { if (!host_port_str.empty()) { host_port_str += ":"; } host_port_str += std::to_string(host_port.port()); } if (!host_port_str.empty()) { flags.push_back(option_name + "=" + host_port_str); } } break; } default: // Skip unknown types break; } } Local result; CHECK(ToV8Value(context, flags).ToLocal(&result)); args.GetReturnValue().Set(result); } void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); SetMethodNoSideEffect( context, target, "getCLIOptionsValues", GetCLIOptionsValues); SetMethodNoSideEffect( context, target, "getCLIOptionsInfo", GetCLIOptionsInfo); SetMethodNoSideEffect( context, target, "getOptionsAsFlags", GetOptionsAsFlags); SetMethodNoSideEffect( context, target, "getEmbedderOptions", GetEmbedderOptions); SetMethodNoSideEffect( context, target, "getEnvOptionsInputType", GetEnvOptionsInputType); SetMethodNoSideEffect(context, target, "getNamespaceOptionsInputType", GetNamespaceOptionsInputType); Local env_settings = Object::New(isolate); NODE_DEFINE_CONSTANT(env_settings, kAllowedInEnvvar); NODE_DEFINE_CONSTANT(env_settings, kDisallowedInEnvvar); target ->Set( context, FIXED_ONE_BYTE_STRING(isolate, "envSettings"), env_settings) .Check(); Local types = Object::New(isolate); NODE_DEFINE_CONSTANT(types, kNoOp); NODE_DEFINE_CONSTANT(types, kV8Option); NODE_DEFINE_CONSTANT(types, kBoolean); NODE_DEFINE_CONSTANT(types, kInteger); NODE_DEFINE_CONSTANT(types, kUInteger); NODE_DEFINE_CONSTANT(types, kString); NODE_DEFINE_CONSTANT(types, kHostPort); NODE_DEFINE_CONSTANT(types, kStringList); target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "types"), types) .Check(); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetCLIOptionsValues); registry->Register(GetCLIOptionsInfo); registry->Register(GetOptionsAsFlags); registry->Register(GetEmbedderOptions); registry->Register(GetEnvOptionsInputType); registry->Register(GetNamespaceOptionsInputType); } } // namespace options_parser void HandleEnvOptions(std::shared_ptr env_options) { HandleEnvOptions(env_options, [](const char* name) { std::string text; return credentials::SafeGetenv(name, &text) ? text : ""; }); } void HandleEnvOptions(std::shared_ptr env_options, std::function opt_getter) { env_options->pending_deprecation = opt_getter("NODE_PENDING_DEPRECATION") == "1"; env_options->preserve_symlinks = opt_getter("NODE_PRESERVE_SYMLINKS") == "1"; env_options->preserve_symlinks_main = opt_getter("NODE_PRESERVE_SYMLINKS_MAIN") == "1"; env_options->use_env_proxy = opt_getter("NODE_USE_ENV_PROXY") == "1"; #if HAVE_OPENSSL env_options->use_system_ca = opt_getter("NODE_USE_SYSTEM_CA") == "1"; #endif // HAVE_OPENSSL if (env_options->redirect_warnings.empty()) env_options->redirect_warnings = opt_getter("NODE_REDIRECT_WARNINGS"); } std::vector ParseNodeOptionsEnvVar( const std::string& node_options, std::vector* errors) { std::vector env_argv; bool is_in_string = false; bool will_start_new_arg = true; for (std::string::size_type index = 0; index < node_options.size(); ++index) { char c = node_options.at(index); // Backslashes escape the following character if (c == '\\' && is_in_string) { if (index + 1 == node_options.size()) { errors->push_back("invalid value for NODE_OPTIONS " "(invalid escape)\n"); return env_argv; } else { c = node_options.at(++index); } } else if (c == ' ' && !is_in_string) { will_start_new_arg = true; continue; } else if (c == '"') { is_in_string = !is_in_string; continue; } if (will_start_new_arg) { env_argv.emplace_back(std::string(1, c)); will_start_new_arg = false; } else { env_argv.back() += c; } } if (is_in_string) { errors->push_back("invalid value for NODE_OPTIONS " "(unterminated string)\n"); } return env_argv; } } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(options, node::options_parser::Initialize) NODE_BINDING_EXTERNAL_REFERENCE( options, node::options_parser::RegisterExternalReferences)