diff --git a/lib/internal/test_runner/reporter/utils.js b/lib/internal/test_runner/reporter/utils.js index 995155c533c5..e1ab92afcd81 100644 --- a/lib/internal/test_runner/reporter/utils.js +++ b/lib/internal/test_runner/reporter/utils.js @@ -1,110 +1,115 @@ -'use strict'; -const { - ArrayPrototypeJoin, - RegExpPrototypeSymbolSplit, - SafeMap, - StringPrototypeRepeat, - hardenRegExp, -} = primordials; -const colors = require('internal/util/colors'); -const { inspectWithNoCustomRetry } = require('internal/errors'); -const indentMemo = new SafeMap(); - -const inspectOptions = { - __proto__: null, - colors: colors.shouldColorize(process.stdout), - breakLength: Infinity, -}; - -const reporterUnicodeSymbolMap = { - '__proto__': null, - 'test:fail': '\u2716 ', - 'test:pass': '\u2714 ', - 'test:diagnostic': '\u2139 ', - 'test:log': '\u2139 ', - 'test:coverage': '\u2139 ', - 'arrow:right': '\u25B6 ', - 'hyphen:minus': '\uFE63 ', - 'warning:alert': '\u26A0 ', -}; - -const reporterColorMap = { - '__proto__': null, - get 'test:fail'() { - return colors.red; - }, - get 'test:pass'() { - return colors.green; - }, - get 'test:diagnostic'() { - return colors.blue; - }, - get 'test:log'() { - return colors.blue; - }, - get 'info'() { - return colors.blue; - }, - get 'warn'() { - return colors.yellow; - }, - get 'error'() { - return colors.red; - }, -}; - -function indent(nesting) { - let value = indentMemo.get(nesting); - if (value === undefined) { - value = StringPrototypeRepeat(' ', nesting); - indentMemo.set(nesting, value); - } - return value; -} - -function formatError(error, indent) { - const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error; - const message = ArrayPrototypeJoin( - RegExpPrototypeSymbolSplit( - hardenRegExp(/\r?\n/), - inspectWithNoCustomRetry(err, inspectOptions), - ), `\n${indent} `); - return `\n${indent} ${message}\n`; -} - -function formatTestReport(type, data, showErrorDetails = true, prefix = '', indent = '') { - let color = reporterColorMap[type] ?? colors.white; - let symbol = reporterUnicodeSymbolMap[type] ?? ' '; - const { skip, todo, expectFailure } = data; - const duration_ms = data.details?.duration_ms ? ` ${colors.gray}(${data.details.duration_ms}ms)${colors.white}` : ''; - const replayed = data.details?.passed_on_attempt !== undefined ? - ` ${colors.gray}(passed on attempt ${data.details.passed_on_attempt})${colors.white}` : - ''; - let title = `${data.name}${duration_ms}${replayed}`; - - if (skip !== undefined) { - title += ` # ${typeof skip === 'string' && skip.length ? skip : 'SKIP'}`; - color = colors.gray; - symbol = reporterUnicodeSymbolMap['hyphen:minus']; - } else if (todo !== undefined) { - title += ` # ${typeof todo === 'string' && todo.length ? todo : 'TODO'}`; - if (type === 'test:fail') { - color = colors.yellow; - symbol = reporterUnicodeSymbolMap['warning:alert']; - } - } else if (expectFailure !== undefined) { - title += ` # EXPECTED FAILURE`; - } - - const err = showErrorDetails && data.details?.error ? formatError(data.details.error, indent) : ''; - - return `${prefix}${indent}${color}${symbol}${title}${colors.white}${err}`; -} - -module.exports = { - __proto__: null, - reporterUnicodeSymbolMap, - reporterColorMap, - formatTestReport, - indent, -}; +'use strict'; +const { + ArrayPrototypeJoin, + RegExpPrototypeSymbolSplit, + SafeMap, + StringPrototypeRepeat, + hardenRegExp, +} = primordials; +const colors = require('internal/util/colors'); +const { inspectWithNoCustomRetry } = require('internal/errors'); +const { getOptionValue } = require('internal/options'); +const indentMemo = new SafeMap(); + +const testInspectDepth = getOptionValue('--test-inspect-depth'); +const inspectOptions = { + __proto__: null, + colors: colors.shouldColorize(process.stdout), + breakLength: Infinity, +}; +if (testInspectDepth >= 0) { + inspectOptions.depth = testInspectDepth; +} + +const reporterUnicodeSymbolMap = { + '__proto__': null, + 'test:fail': '\u2716 ', + 'test:pass': '\u2714 ', + 'test:diagnostic': '\u2139 ', + 'test:log': '\u2139 ', + 'test:coverage': '\u2139 ', + 'arrow:right': '\u25B6 ', + 'hyphen:minus': '\uFE63 ', + 'warning:alert': '\u26A0 ', +}; + +const reporterColorMap = { + '__proto__': null, + get 'test:fail'() { + return colors.red; + }, + get 'test:pass'() { + return colors.green; + }, + get 'test:diagnostic'() { + return colors.blue; + }, + get 'test:log'() { + return colors.blue; + }, + get 'info'() { + return colors.blue; + }, + get 'warn'() { + return colors.yellow; + }, + get 'error'() { + return colors.red; + }, +}; + +function indent(nesting) { + let value = indentMemo.get(nesting); + if (value === undefined) { + value = StringPrototypeRepeat(' ', nesting); + indentMemo.set(nesting, value); + } + return value; +} + +function formatError(error, indent) { + const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error; + const message = ArrayPrototypeJoin( + RegExpPrototypeSymbolSplit( + hardenRegExp(/\r?\n/), + inspectWithNoCustomRetry(err, inspectOptions), + ), `\n${indent} `); + return `\n${indent} ${message}\n`; +} + +function formatTestReport(type, data, showErrorDetails = true, prefix = '', indent = '') { + let color = reporterColorMap[type] ?? colors.white; + let symbol = reporterUnicodeSymbolMap[type] ?? ' '; + const { skip, todo, expectFailure } = data; + const duration_ms = data.details?.duration_ms ? ` ${colors.gray}(${data.details.duration_ms}ms)${colors.white}` : ''; + const replayed = data.details?.passed_on_attempt !== undefined ? + ` ${colors.gray}(passed on attempt ${data.details.passed_on_attempt})${colors.white}` : + ''; + let title = `${data.name}${duration_ms}${replayed}`; + + if (skip !== undefined) { + title += ` # ${typeof skip === 'string' && skip.length ? skip : 'SKIP'}`; + color = colors.gray; + symbol = reporterUnicodeSymbolMap['hyphen:minus']; + } else if (todo !== undefined) { + title += ` # ${typeof todo === 'string' && todo.length ? todo : 'TODO'}`; + if (type === 'test:fail') { + color = colors.yellow; + symbol = reporterUnicodeSymbolMap['warning:alert']; + } + } else if (expectFailure !== undefined) { + title += ` # EXPECTED FAILURE`; + } + + const err = showErrorDetails && data.details?.error ? formatError(data.details.error, indent) : ''; + + return `${prefix}${indent}${color}${symbol}${title}${colors.white}${err}`; +} + +module.exports = { + __proto__: null, + reporterUnicodeSymbolMap, + reporterColorMap, + formatTestReport, + indent, +}; diff --git a/src/node_options.cc b/src/node_options.cc index 2eeb268ef542..091db966af1c 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1,2434 +1,2439 @@ -#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 "ncrypto.h" // Defines OPENSSL_VERSION_PREREQ for BoringSSL. -#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 { - -constexpr bool kStrictOptionParsing = true; - -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"); - } - - if (force_fips_crypto_policy != "provider" && - force_fips_crypto_policy != "strict") { - errors->push_back( - "invalid value for --force-fips; expected 'provider' or 'strict'"); - } - -#if defined(OPENSSL_IS_BORINGSSL) || !OPENSSL_VERSION_PREREQ(3, 4) - if (enable_fips_indicator_events) { - errors->push_back( - "--enable-fips-indicator-events requires OpenSSL 3.4 or later"); - } - - if (force_fips_crypto && force_fips_crypto_policy == "strict") { - errors->push_back("--force-fips=strict requires OpenSSL 3.4 or later"); - } -#endif - - // 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"); - } - - // NOTE: `has_bench_options` is deliberately *not* validated against - // `experimental_bench` here; see CheckBenchOptions(), which callers invoke - // once every option source has been parsed. - - if (bench_runner && test_runner) { - errors->push_back("either --bench or --test can be used, not both"); - } - - if (bench_runner) { - if (bench_isolation == "none") { - debug_options_.allow_attaching_debugger = true; - } else { - if (bench_isolation != "process") { - errors->push_back("invalid value for --bench-isolation"); - } - - debug_options_.allow_attaching_debugger = false; - } - if (has_bench_samples && bench_samples == 0) { - errors->push_back("--bench-samples must be greater than 0"); - } - if (syntax_check_only) { - errors->push_back("either --bench or --check can be used, not both"); - } - if (has_eval_string) { - errors->push_back("either --bench or --eval can be used, not both"); - } - if (force_repl) { - errors->push_back( - "either --bench or --interactive can be used, not both"); - } - if (watch_mode || !watch_mode_paths.empty()) { - errors->push_back("either --bench or --watch can be used, not both"); - } - } - - 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 -} - -void EnvironmentOptions::CheckBenchOptions( - std::vector* errors) const { - if (has_bench_options && !experimental_bench) { - errors->push_back( - "--experimental-bench is required to use --bench or related options"); - } -} - -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)", - BOOL_FIELD(inspector_enabled), - kAllowedInEnvvar); - AddAlias("--inspect=", { "--inspect-port", "--inspect" }); - - AddOption("--debug", "", BOOL_FIELD(deprecated_debug)); - AddAlias("--debug=", "--debug"); - AddOption("--debug-brk", "", BOOL_FIELD(deprecated_debug)); - AddAlias("--debug-brk=", "--debug-brk"); - - AddOption("--inspect-brk", - "activate inspector on host:port and break at start of user script", - BOOL_FIELD(break_first_line), - kAllowedInEnvvar); - Implies("--inspect-brk", "--inspect"); - AddAlias("--inspect-brk=", { "--inspect-port", "--inspect-brk" }); - - AddOption("--inspect-brk-node", "", BOOL_FIELD(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", - BOOL_FIELD(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", - BOOL_FIELD(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 find and print " - "top-level await in the graph", - BOOL_FIELD(print_required_tla), - kAllowedInEnvvar); - AddOption("--require-module", - "Allow loading synchronous ES Modules in require().", - BOOL_FIELD(require_module), - kAllowedInEnvvar, - true); - AddOption("--experimental-require-module", - "Legacy alias for --require-module", - BOOL_FIELD(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", - BOOL_FIELD(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", - BOOL_FIELD(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", - BOOL_FIELD(enable_source_maps), - kAllowedInEnvvar); - AddOption("--entry-url", - "Treat the entrypoint as a URL", - BOOL_FIELD(entry_is_url), - kAllowedInEnvvar); - AddOption("--experimental-addon-modules", - "experimental import support for addons", - BOOL_FIELD(experimental_addon_modules), - kAllowedInEnvvar); - AddOption("--experimental-bench", - "experimental node:bench module and benchmark runner", - BOOL_FIELD(experimental_bench), - kAllowedInEnvvar); - AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar); - AddOption("--experimental-eventsource", - "experimental EventSource API", - BOOL_FIELD(experimental_eventsource), - kAllowedInEnvvar, - false); - AddOption("--experimental-fetch", "", NoOp{}, kAllowedInEnvvar); -#if HAVE_FFI - AddOption("--experimental-ffi", - "experimental node:ffi module", - BOOL_FIELD(experimental_ffi), - kAllowedInEnvvar, - HAVE_FFI); -#endif // HAVE_FFI - AddOption("--experimental-web-worker", - "experimental Web Worker API", - BOOL_FIELD(experimental_web_worker), - kAllowedInEnvvar, - false); - AddOption("--experimental-websocket", "", NoOp{}, kAllowedInEnvvar); - AddOption("--experimental-global-customevent", "", NoOp{}, kAllowedInEnvvar); - AddOption("--experimental-sqlite", - "experimental node:sqlite module", - BOOL_FIELD(experimental_sqlite), - kAllowedInEnvvar, - HAVE_SQLITE); - AddOption("--experimental-stream-iter", - "experimental iterable streams API (node:stream/iter)", - BOOL_FIELD(experimental_stream_iter), - kAllowedInEnvvar); - AddOption("--experimental-dtls", -#if HAVE_DTLS - "experimental DTLS support", - BOOL_FIELD(experimental_dtls), -#else - "" /* undocumented when no-op */, - NoOp{}, -#endif - kAllowedInEnvvar); - AddOption("--experimental-vfs", - "experimental node:vfs module", - BOOL_FIELD(experimental_vfs), - kAllowedInEnvvar); - AddOption("--experimental-quic", -#ifndef OPENSSL_NO_QUIC - "experimental QUIC support", - BOOL_FIELD(experimental_quic), -#else - "" /* undocumented when no-op */, - NoOp{}, -#endif - kAllowedInEnvvar); - AddOption("--experimental-webstorage", - "experimental Web Storage API", - BOOL_FIELD(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", - BOOL_FIELD(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-text", - "experimental support for importing source as text with import " - "attributes", - BOOL_FIELD(experimental_import_text), - kAllowedInEnvvar); - AddOption("--experimental-import-meta-resolve", - "experimental ES Module import.meta.resolve() parentURL support", - BOOL_FIELD(experimental_import_meta_resolve), - kAllowedInEnvvar); - AddOption("--permission", - "enable the permission system", - BOOL_FIELD(permission), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--permission-audit", - "enable audit only for the permission system", - BOOL_FIELD(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-fs-vfs", - "allow mounting a virtual file system when any permissions are set", - BOOL_FIELD(allow_fs_vfs), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--allow-addons", - "allow use of addons when any permissions are set", - BOOL_FIELD(allow_addons), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--allow-child-process", - "allow use of child process when any permissions are set", - BOOL_FIELD(allow_child_process), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); -#if HAVE_FFI - AddOption("--allow-ffi", - "allow use of FFI when any permissions are set", - BOOL_FIELD(allow_ffi), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); -#endif // HAVE_FFI - AddOption("--allow-inspector", - "allow use of inspector when any permissions are set", - BOOL_FIELD(allow_inspector), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--allow-net", - "allow use of network when any permissions are set", - BOOL_FIELD(allow_net), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--allow-wasi", - "allow wasi when any permissions are set", - BOOL_FIELD(allow_wasi), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--allow-worker", - "allow worker threads when any permissions are set", - BOOL_FIELD(allow_worker_threads), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--allow-openssl-store", - "allow use of OpenSSL STORE loaders when any permissions are set", - BOOL_FIELD(allow_openssl_store), - kAllowedInEnvvar, - false, - OptionNamespaces::kPermissionNamespace); - AddOption("--experimental-repl-await", "", NoOp{}, kAllowedInEnvvar); - AddOption("--experimental-vm-modules", - "experimental ES Module support in vm module", - BOOL_FIELD(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", - BOOL_FIELD(async_context_frame), - kAllowedInEnvvar, - true); - AddOption("--expose-internals", "", BOOL_FIELD(expose_internals)); - AddOption("--frozen-intrinsics", - "experimental frozen intrinsics support", - BOOL_FIELD(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", - BOOL_FIELD(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", - BOOL_FIELD(deprecation), - kAllowedInEnvvar, - true); - AddOption("--force-async-hooks-checks", - "disable checks for async_hooks", - BOOL_FIELD(force_async_hooks_checks), - kAllowedInEnvvar, - true); - AddOption( - "--force-node-api-uncaught-exceptions-policy", - "enforces 'uncaughtException' event on Node API asynchronous callbacks", - BOOL_FIELD(force_node_api_uncaught_exceptions_policy), - kAllowedInEnvvar, - false); - AddOption("--addons", - "disable loading native addons", - BOOL_FIELD(allow_native_addons), - kAllowedInEnvvar, - true); - AddOption("--global-search-paths", - "disable global module search paths", - BOOL_FIELD(global_search_paths), - kAllowedInEnvvar, - true); - AddOption("--warnings", - "silence all process warnings", - BOOL_FIELD(warnings), - kAllowedInEnvvar, - true); - AddOption("--disable-warning", - "silence specific process warnings", - &EnvironmentOptions::disable_warnings, - kAllowedInEnvvar); - AddOption("--force-context-aware", - "disable loading non-context-aware addons", - BOOL_FIELD(force_context_aware), - kAllowedInEnvvar); - AddOption("--pending-deprecation", - "emit pending deprecation warnings", - BOOL_FIELD(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", - BOOL_FIELD(use_env_proxy), - kAllowedInEnvvar); - AddOption("--preserve-symlinks", - "preserve symbolic links when resolving", - BOOL_FIELD(preserve_symlinks), - kAllowedInEnvvar); - AddOption("--preserve-symlinks-main", - "preserve symbolic links when resolving the main module", - BOOL_FIELD(preserve_symlinks_main), - kAllowedInEnvvar); - AddOption("--prof", - "Generate V8 profiler output.", - V8Option{}); - AddOption("--prof-process", - "process V8 profiler output generated using --prof", - BOOL_FIELD(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.", - BOOL_FIELD(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", - BOOL_FIELD(experimental_network_inspection)); - AddOption("--experimental-storage-inspection", - "experimental storage inspection support", - BOOL_FIELD(experimental_storage_inspection)); - AddOption("--experimental-worker-inspection", - "experimental worker inspection support", - BOOL_FIELD(experimental_worker_inspection)); - AddOption("--experimental-inspector-network-resource", - "experimental load network resources via the inspector", - BOOL_FIELD(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.", - BOOL_FIELD(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]", "", BOOL_FIELD(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 path", - &EnvironmentOptions::experimental_config_file_path, - kDisallowedInEnvvar); - AddAlias("--experimental-default-config-file", "--experimental-config-file"); - AddOption("--experimental-package-map", - "use the specified file for package map resolution", - &EnvironmentOptions::experimental_package_map_path, - kAllowedInEnvvar); - AddOption("[has_bench_options]", "", BOOL_FIELD(has_bench_options)); - AddOption("--bench", - "launch benchmark runner on startup", - BOOL_FIELD(bench_runner), - kDisallowedInEnvvar, - false, - OptionNamespaces::kBenchRunnerNamespace); - Implies("--bench", "[has_bench_options]"); - AddOption("--bench-isolation", - "configures the type of benchmark isolation used in the benchmark " - "runner", - &EnvironmentOptions::bench_isolation, - kAllowedInEnvvar, - OptionNamespaces::kBenchRunnerNamespace); - Implies("--bench-isolation", "[has_bench_options]"); - AddOption("--bench-name-pattern", - "run benchmarks whose name matches this regular expression", - &EnvironmentOptions::bench_name_pattern, - kAllowedInEnvvar, - OptionNamespaces::kBenchRunnerNamespace); - Implies("--bench-name-pattern", "[has_bench_options]"); - AddOption("--bench-reporter", - "report benchmark output using the given reporter", - &EnvironmentOptions::bench_reporter, - kAllowedInEnvvar, - OptionNamespaces::kBenchRunnerNamespace); - Implies("--bench-reporter", "[has_bench_options]"); - AddOption("--bench-reporter-destination", - "report the given benchmark reporter to the given destination", - &EnvironmentOptions::bench_reporter_destination, - kAllowedInEnvvar, - OptionNamespaces::kBenchRunnerNamespace); - Implies("--bench-reporter-destination", "[has_bench_options]"); - AddOption("[has_bench_samples]", "", BOOL_FIELD(has_bench_samples)); - AddOption("--bench-samples", - "specify the number of measured benchmark samples", - &EnvironmentOptions::bench_samples, - kAllowedInEnvvar, - OptionNamespaces::kBenchRunnerNamespace, - kStrictOptionParsing); - Implies("--bench-samples", "[has_bench_options]"); - Implies("--bench-samples", "[has_bench_samples]"); - AddOption("[has_bench_warmup]", "", BOOL_FIELD(has_bench_warmup)); - AddOption("--bench-warmup", - "specify the number of unreported benchmark warmup samples", - &EnvironmentOptions::bench_warmup, - kAllowedInEnvvar, - OptionNamespaces::kBenchRunnerNamespace, - kStrictOptionParsing); - Implies("--bench-warmup", "[has_bench_options]"); - Implies("--bench-warmup", "[has_bench_warmup]"); - AddOption("--test", - "launch test runner on startup", - BOOL_FIELD(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", - BOOL_FIELD(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", - BOOL_FIELD(test_runner_update_snapshots), - kDisallowedInEnvvar, - false, - OptionNamespaces::kTestRunnerNamespace); - AddOption("--experimental-test-coverage", - "enable code coverage in the test runner", - BOOL_FIELD(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", - BOOL_FIELD(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", - BOOL_FIELD(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("--experimental-test-tag-filter", - "run tests matching the given tag filter expression", - &EnvironmentOptions::experimental_test_tag_filter, - kDisallowedInEnvvar, - 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-include-all", - "include source files that were never loaded in the coverage " - "report", - BOOL_FIELD(coverage_include_all), - kAllowedInEnvvar, - false, - 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-randomize", - "run tests in a random order", - BOOL_FIELD(test_randomize), - kAllowedInEnvvar, - false, - OptionNamespaces::kTestRunnerNamespace); - AddOption("[has_test_random_seed]", "", BOOL_FIELD(has_test_random_seed)); - AddOption("--test-random-seed", - "seed used to randomize test execution order", - &EnvironmentOptions::test_random_seed, - kAllowedInEnvvar, - OptionNamespaces::kTestRunnerNamespace); - Implies("--test-random-seed", "[has_test_random_seed]"); - 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. - BOOL_FIELD(test_udp_no_try_send), - kDisallowedInEnvvar); - AddOption("--throw-deprecation", - "throw an exception on deprecations", - BOOL_FIELD(throw_deprecation), - kAllowedInEnvvar); - AddOption("--trace-deprecation", - "show stack traces on deprecations", - BOOL_FIELD(trace_deprecation), - kAllowedInEnvvar); - AddOption("--trace-exit", - "show stack trace when an environment exits", - BOOL_FIELD(trace_exit), - kAllowedInEnvvar); - AddOption("--trace-sync-io", - "show stack trace when use of sync IO is detected after the " - "first tick", - BOOL_FIELD(trace_sync_io), - kAllowedInEnvvar); - AddOption("--trace-tls", - "prints TLS packet trace information to stderr", - BOOL_FIELD(trace_tls), - kAllowedInEnvvar); - AddOption("--trace-uncaught", - "show stack traces for the `throw` behind uncaught exceptions", - BOOL_FIELD(trace_uncaught), - kAllowedInEnvvar); - AddOption("--trace-warnings", - "show stack traces on process warnings", - BOOL_FIELD(trace_warnings), - kAllowedInEnvvar); - AddOption("--trace-promises", - "show stack traces on promise initialization and resolution", - BOOL_FIELD(trace_promises), - kAllowedInEnvvar); - - AddOption("--trace-env", - "Print accesses to the environment variables", - BOOL_FIELD(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", - BOOL_FIELD(trace_env_js_stack), - kAllowedInEnvvar); - AddOption( - "--trace-env-native-stack", - "Print accesses to the environment variables and the native stack trace", - BOOL_FIELD(trace_env_native_stack), - kAllowedInEnvvar); - -#if HAVE_OPENSSL - AddOption("--use-system-ca", - "use system's CA store", - BOOL_FIELD(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", - BOOL_FIELD(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 */ - BOOL_FIELD(verify_base_objects), - kAllowedInEnvvar); - AddOption("--watch", - "run in watch mode", - BOOL_FIELD(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", - BOOL_FIELD(watch_mode_preserve_output), - kAllowedInEnvvar, - false, - OptionNamespaces::kWatchNamespace); - Implies("--watch-path", "--watch"); - AddOption("--check", - "syntax check script without executing", - BOOL_FIELD(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]", "", BOOL_FIELD(has_eval_string)); - AddOption("--eval", "evaluate script", &EnvironmentOptions::eval_string); - Implies("--eval", "[has_eval_string]"); - AddOption( - "--print", "evaluate script and print result", BOOL_FIELD(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.", - BOOL_FIELD(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", - BOOL_FIELD(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)", - BOOL_FIELD(tls_min_v1_0), - kAllowedInEnvvar); - AddOption("--tls-min-v1.1", - "set default TLS minimum to TLSv1.1 (default: TLSv1.2)", - BOOL_FIELD(tls_min_v1_1), - kAllowedInEnvvar); - AddOption("--tls-min-v1.2", - "set default TLS minimum to TLSv1.2 (default: TLSv1.2)", - BOOL_FIELD(tls_min_v1_2), - kAllowedInEnvvar); - AddOption("--tls-min-v1.3", - "set default TLS minimum to TLSv1.3 (default: TLSv1.2)", - BOOL_FIELD(tls_min_v1_3), - kAllowedInEnvvar); - AddOption("--tls-max-v1.2", - "set default TLS maximum to TLSv1.2 (default: TLSv1.3)", - BOOL_FIELD(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)", - BOOL_FIELD(tls_max_v1_3), - kAllowedInEnvvar); - - AddOption("--report-exclude-env", - "Exclude environment variables when generating report" - " (default: false)", - BOOL_FIELD(report_exclude_env), - kAllowedInEnvvar); - AddOption("--report-exclude-network", - "exclude network interface diagnostics." - " (default: false)", - BOOL_FIELD(report_exclude_network), - kAllowedInEnvvar); -} - -PerIsolateOptionsParser::PerIsolateOptionsParser( - const EnvironmentOptionsParser& eop) { - AddOption("--worker-snapshot", - "start worker threads from the bootstrapped context in the " - "built-in startup snapshot", - BOOL_FIELD(worker_snapshot), - kAllowedInEnvvar, - true); - AddOption("--track-heap-objects", - "track heap object allocations for heap snapshots", - BOOL_FIELD(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", - BOOL_FIELD(report_uncaught_exception), - kAllowedInEnvvar); - AddOption("--report-on-signal", - "generate diagnostic report upon receiving signals", - BOOL_FIELD(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", - "", - BOOL_FIELD(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.", - BOOL_FIELD(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", - BOOL_FIELD(zero_fill_all_buffers), - kAllowedInEnvvar); - AddOption("--debug-arraybuffer-allocations", - "", /* undocumented, only for debugging */ - BOOL_FIELD(debug_arraybuffer_allocations), - kAllowedInEnvvar); - AddOption("--disable-proto", - "disable Object.prototype.__proto__", - &PerProcessOptions::disable_proto, - kAllowedInEnvvar); - AddOption("--node-snapshot", - "", // It's a debug-only option. - BOOL_FIELD(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", - BOOL_FIELD(print_bash_completion)); - AddOption( - "--help", "print node command line options", BOOL_FIELD(print_help)); - AddAlias("-h", "--help"); - AddOption("--version", "print Node.js version", BOOL_FIELD(print_version)); - AddAlias("-v", "--version"); - AddOption("--v8-options", - "print V8 command line options", - BOOL_FIELD(print_v8_help)); - AddOption("--report-compact", - "output compact single-line JSON", - BOOL_FIELD(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", - BOOL_FIELD(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 - , - BOOL_FIELD(use_openssl_ca), - kAllowedInEnvvar); - AddOption("--use-bundled-ca", - "use bundled CA store" -#if !defined(NODE_OPENSSL_CERT_STORE) - " (default)" -#endif - , - BOOL_FIELD(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]", "", BOOL_FIELD(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", - BOOL_FIELD(enable_fips_crypto), - kAllowedInEnvvar); - AddOption("--enable-fips-indicator-events", - "publish FIPS indicator results to the " - "crypto.fips.indicator diagnostics channel", - BOOL_FIELD(enable_fips_indicator_events), - kAllowedInEnvvar); - AddOption("--force-fips", - "force FIPS crypto (optional mode: provider or strict)", - BOOL_FIELD(force_fips_crypto), - kAllowedInEnvvar); - AddOption("[force_fips_crypto_policy]", - "", - &PerProcessOptions::force_fips_crypto_policy, - kAllowedInEnvvar); - AddAlias("--force-fips=", {"[force_fips_crypto_policy]", "--force-fips"}); -#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", - BOOL_FIELD(openssl_legacy_provider), - kAllowedInEnvvar); - AddOption("--openssl-shared-config", - "enable OpenSSL shared configuration", - BOOL_FIELD(openssl_shared_config), - kAllowedInEnvvar); - -#endif // OPENSSL_VERSION_MAJOR - AddOption("--use-largepages", - "This option is no longer supported and a no-op. It still accepts" - " these values for compatibility: 'off' (default), 'on' (report a " - "warning to stderr), or 'silent' (same as 'off').", - &PerProcessOptions::use_largepages, - kAllowedInEnvvar); - - AddOption("--trace-sigint", - "enable printing JavaScript stacktrace on SIGINT", - BOOL_FIELD(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.", - BOOL_FIELD(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 = field->GetBool(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; - - if (option_info.namespace_id == - NamespaceEnumToString(OptionNamespaces::kBenchRunnerNamespace) && - !opts->per_isolate->per_env->experimental_bench) { - continue; - } - - // 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 = field->GetBool(opts); -#if HAVE_OPENSSL - if (option_name == "--force-fips" && current_value) { - flags.push_back(option_name + "=" + opts->force_fips_crypto_policy); - break; - } -#endif - // 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) +#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 "ncrypto.h" // Defines OPENSSL_VERSION_PREREQ for BoringSSL. +#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 { + +constexpr bool kStrictOptionParsing = true; + +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"); + } + + if (force_fips_crypto_policy != "provider" && + force_fips_crypto_policy != "strict") { + errors->push_back( + "invalid value for --force-fips; expected 'provider' or 'strict'"); + } + +#if defined(OPENSSL_IS_BORINGSSL) || !OPENSSL_VERSION_PREREQ(3, 4) + if (enable_fips_indicator_events) { + errors->push_back( + "--enable-fips-indicator-events requires OpenSSL 3.4 or later"); + } + + if (force_fips_crypto && force_fips_crypto_policy == "strict") { + errors->push_back("--force-fips=strict requires OpenSSL 3.4 or later"); + } +#endif + + // 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"); + } + + // NOTE: `has_bench_options` is deliberately *not* validated against + // `experimental_bench` here; see CheckBenchOptions(), which callers invoke + // once every option source has been parsed. + + if (bench_runner && test_runner) { + errors->push_back("either --bench or --test can be used, not both"); + } + + if (bench_runner) { + if (bench_isolation == "none") { + debug_options_.allow_attaching_debugger = true; + } else { + if (bench_isolation != "process") { + errors->push_back("invalid value for --bench-isolation"); + } + + debug_options_.allow_attaching_debugger = false; + } + if (has_bench_samples && bench_samples == 0) { + errors->push_back("--bench-samples must be greater than 0"); + } + if (syntax_check_only) { + errors->push_back("either --bench or --check can be used, not both"); + } + if (has_eval_string) { + errors->push_back("either --bench or --eval can be used, not both"); + } + if (force_repl) { + errors->push_back( + "either --bench or --interactive can be used, not both"); + } + if (watch_mode || !watch_mode_paths.empty()) { + errors->push_back("either --bench or --watch can be used, not both"); + } + } + + 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 +} + +void EnvironmentOptions::CheckBenchOptions( + std::vector* errors) const { + if (has_bench_options && !experimental_bench) { + errors->push_back( + "--experimental-bench is required to use --bench or related options"); + } +} + +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)", + BOOL_FIELD(inspector_enabled), + kAllowedInEnvvar); + AddAlias("--inspect=", { "--inspect-port", "--inspect" }); + + AddOption("--debug", "", BOOL_FIELD(deprecated_debug)); + AddAlias("--debug=", "--debug"); + AddOption("--debug-brk", "", BOOL_FIELD(deprecated_debug)); + AddAlias("--debug-brk=", "--debug-brk"); + + AddOption("--inspect-brk", + "activate inspector on host:port and break at start of user script", + BOOL_FIELD(break_first_line), + kAllowedInEnvvar); + Implies("--inspect-brk", "--inspect"); + AddAlias("--inspect-brk=", { "--inspect-port", "--inspect-brk" }); + + AddOption("--inspect-brk-node", "", BOOL_FIELD(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", + BOOL_FIELD(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", + BOOL_FIELD(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 find and print " + "top-level await in the graph", + BOOL_FIELD(print_required_tla), + kAllowedInEnvvar); + AddOption("--require-module", + "Allow loading synchronous ES Modules in require().", + BOOL_FIELD(require_module), + kAllowedInEnvvar, + true); + AddOption("--experimental-require-module", + "Legacy alias for --require-module", + BOOL_FIELD(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", + BOOL_FIELD(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", + BOOL_FIELD(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", + BOOL_FIELD(enable_source_maps), + kAllowedInEnvvar); + AddOption("--entry-url", + "Treat the entrypoint as a URL", + BOOL_FIELD(entry_is_url), + kAllowedInEnvvar); + AddOption("--experimental-addon-modules", + "experimental import support for addons", + BOOL_FIELD(experimental_addon_modules), + kAllowedInEnvvar); + AddOption("--experimental-bench", + "experimental node:bench module and benchmark runner", + BOOL_FIELD(experimental_bench), + kAllowedInEnvvar); + AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar); + AddOption("--experimental-eventsource", + "experimental EventSource API", + BOOL_FIELD(experimental_eventsource), + kAllowedInEnvvar, + false); + AddOption("--experimental-fetch", "", NoOp{}, kAllowedInEnvvar); +#if HAVE_FFI + AddOption("--experimental-ffi", + "experimental node:ffi module", + BOOL_FIELD(experimental_ffi), + kAllowedInEnvvar, + HAVE_FFI); +#endif // HAVE_FFI + AddOption("--experimental-web-worker", + "experimental Web Worker API", + BOOL_FIELD(experimental_web_worker), + kAllowedInEnvvar, + false); + AddOption("--experimental-websocket", "", NoOp{}, kAllowedInEnvvar); + AddOption("--experimental-global-customevent", "", NoOp{}, kAllowedInEnvvar); + AddOption("--experimental-sqlite", + "experimental node:sqlite module", + BOOL_FIELD(experimental_sqlite), + kAllowedInEnvvar, + HAVE_SQLITE); + AddOption("--experimental-stream-iter", + "experimental iterable streams API (node:stream/iter)", + BOOL_FIELD(experimental_stream_iter), + kAllowedInEnvvar); + AddOption("--experimental-dtls", +#if HAVE_DTLS + "experimental DTLS support", + BOOL_FIELD(experimental_dtls), +#else + "" /* undocumented when no-op */, + NoOp{}, +#endif + kAllowedInEnvvar); + AddOption("--experimental-vfs", + "experimental node:vfs module", + BOOL_FIELD(experimental_vfs), + kAllowedInEnvvar); + AddOption("--experimental-quic", +#ifndef OPENSSL_NO_QUIC + "experimental QUIC support", + BOOL_FIELD(experimental_quic), +#else + "" /* undocumented when no-op */, + NoOp{}, +#endif + kAllowedInEnvvar); + AddOption("--experimental-webstorage", + "experimental Web Storage API", + BOOL_FIELD(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", + BOOL_FIELD(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-text", + "experimental support for importing source as text with import " + "attributes", + BOOL_FIELD(experimental_import_text), + kAllowedInEnvvar); + AddOption("--experimental-import-meta-resolve", + "experimental ES Module import.meta.resolve() parentURL support", + BOOL_FIELD(experimental_import_meta_resolve), + kAllowedInEnvvar); + AddOption("--permission", + "enable the permission system", + BOOL_FIELD(permission), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--permission-audit", + "enable audit only for the permission system", + BOOL_FIELD(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-fs-vfs", + "allow mounting a virtual file system when any permissions are set", + BOOL_FIELD(allow_fs_vfs), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--allow-addons", + "allow use of addons when any permissions are set", + BOOL_FIELD(allow_addons), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--allow-child-process", + "allow use of child process when any permissions are set", + BOOL_FIELD(allow_child_process), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); +#if HAVE_FFI + AddOption("--allow-ffi", + "allow use of FFI when any permissions are set", + BOOL_FIELD(allow_ffi), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); +#endif // HAVE_FFI + AddOption("--allow-inspector", + "allow use of inspector when any permissions are set", + BOOL_FIELD(allow_inspector), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--allow-net", + "allow use of network when any permissions are set", + BOOL_FIELD(allow_net), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--allow-wasi", + "allow wasi when any permissions are set", + BOOL_FIELD(allow_wasi), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--allow-worker", + "allow worker threads when any permissions are set", + BOOL_FIELD(allow_worker_threads), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--allow-openssl-store", + "allow use of OpenSSL STORE loaders when any permissions are set", + BOOL_FIELD(allow_openssl_store), + kAllowedInEnvvar, + false, + OptionNamespaces::kPermissionNamespace); + AddOption("--experimental-repl-await", "", NoOp{}, kAllowedInEnvvar); + AddOption("--experimental-vm-modules", + "experimental ES Module support in vm module", + BOOL_FIELD(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", + BOOL_FIELD(async_context_frame), + kAllowedInEnvvar, + true); + AddOption("--expose-internals", "", BOOL_FIELD(expose_internals)); + AddOption("--frozen-intrinsics", + "experimental frozen intrinsics support", + BOOL_FIELD(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", + BOOL_FIELD(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", + BOOL_FIELD(deprecation), + kAllowedInEnvvar, + true); + AddOption("--force-async-hooks-checks", + "disable checks for async_hooks", + BOOL_FIELD(force_async_hooks_checks), + kAllowedInEnvvar, + true); + AddOption( + "--force-node-api-uncaught-exceptions-policy", + "enforces 'uncaughtException' event on Node API asynchronous callbacks", + BOOL_FIELD(force_node_api_uncaught_exceptions_policy), + kAllowedInEnvvar, + false); + AddOption("--addons", + "disable loading native addons", + BOOL_FIELD(allow_native_addons), + kAllowedInEnvvar, + true); + AddOption("--global-search-paths", + "disable global module search paths", + BOOL_FIELD(global_search_paths), + kAllowedInEnvvar, + true); + AddOption("--warnings", + "silence all process warnings", + BOOL_FIELD(warnings), + kAllowedInEnvvar, + true); + AddOption("--disable-warning", + "silence specific process warnings", + &EnvironmentOptions::disable_warnings, + kAllowedInEnvvar); + AddOption("--force-context-aware", + "disable loading non-context-aware addons", + BOOL_FIELD(force_context_aware), + kAllowedInEnvvar); + AddOption("--pending-deprecation", + "emit pending deprecation warnings", + BOOL_FIELD(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", + BOOL_FIELD(use_env_proxy), + kAllowedInEnvvar); + AddOption("--preserve-symlinks", + "preserve symbolic links when resolving", + BOOL_FIELD(preserve_symlinks), + kAllowedInEnvvar); + AddOption("--preserve-symlinks-main", + "preserve symbolic links when resolving the main module", + BOOL_FIELD(preserve_symlinks_main), + kAllowedInEnvvar); + AddOption("--prof", + "Generate V8 profiler output.", + V8Option{}); + AddOption("--prof-process", + "process V8 profiler output generated using --prof", + BOOL_FIELD(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.", + BOOL_FIELD(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", + BOOL_FIELD(experimental_network_inspection)); + AddOption("--experimental-storage-inspection", + "experimental storage inspection support", + BOOL_FIELD(experimental_storage_inspection)); + AddOption("--experimental-worker-inspection", + "experimental worker inspection support", + BOOL_FIELD(experimental_worker_inspection)); + AddOption("--experimental-inspector-network-resource", + "experimental load network resources via the inspector", + BOOL_FIELD(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.", + BOOL_FIELD(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]", "", BOOL_FIELD(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 path", + &EnvironmentOptions::experimental_config_file_path, + kDisallowedInEnvvar); + AddAlias("--experimental-default-config-file", "--experimental-config-file"); + AddOption("--experimental-package-map", + "use the specified file for package map resolution", + &EnvironmentOptions::experimental_package_map_path, + kAllowedInEnvvar); + AddOption("[has_bench_options]", "", BOOL_FIELD(has_bench_options)); + AddOption("--bench", + "launch benchmark runner on startup", + BOOL_FIELD(bench_runner), + kDisallowedInEnvvar, + false, + OptionNamespaces::kBenchRunnerNamespace); + Implies("--bench", "[has_bench_options]"); + AddOption("--bench-isolation", + "configures the type of benchmark isolation used in the benchmark " + "runner", + &EnvironmentOptions::bench_isolation, + kAllowedInEnvvar, + OptionNamespaces::kBenchRunnerNamespace); + Implies("--bench-isolation", "[has_bench_options]"); + AddOption("--bench-name-pattern", + "run benchmarks whose name matches this regular expression", + &EnvironmentOptions::bench_name_pattern, + kAllowedInEnvvar, + OptionNamespaces::kBenchRunnerNamespace); + Implies("--bench-name-pattern", "[has_bench_options]"); + AddOption("--bench-reporter", + "report benchmark output using the given reporter", + &EnvironmentOptions::bench_reporter, + kAllowedInEnvvar, + OptionNamespaces::kBenchRunnerNamespace); + Implies("--bench-reporter", "[has_bench_options]"); + AddOption("--bench-reporter-destination", + "report the given benchmark reporter to the given destination", + &EnvironmentOptions::bench_reporter_destination, + kAllowedInEnvvar, + OptionNamespaces::kBenchRunnerNamespace); + Implies("--bench-reporter-destination", "[has_bench_options]"); + AddOption("[has_bench_samples]", "", BOOL_FIELD(has_bench_samples)); + AddOption("--bench-samples", + "specify the number of measured benchmark samples", + &EnvironmentOptions::bench_samples, + kAllowedInEnvvar, + OptionNamespaces::kBenchRunnerNamespace, + kStrictOptionParsing); + Implies("--bench-samples", "[has_bench_options]"); + Implies("--bench-samples", "[has_bench_samples]"); + AddOption("[has_bench_warmup]", "", BOOL_FIELD(has_bench_warmup)); + AddOption("--bench-warmup", + "specify the number of unreported benchmark warmup samples", + &EnvironmentOptions::bench_warmup, + kAllowedInEnvvar, + OptionNamespaces::kBenchRunnerNamespace, + kStrictOptionParsing); + Implies("--bench-warmup", "[has_bench_options]"); + Implies("--bench-warmup", "[has_bench_warmup]"); + AddOption("--test", + "launch test runner on startup", + BOOL_FIELD(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", + BOOL_FIELD(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", + BOOL_FIELD(test_runner_update_snapshots), + kDisallowedInEnvvar, + false, + OptionNamespaces::kTestRunnerNamespace); + AddOption("--experimental-test-coverage", + "enable code coverage in the test runner", + BOOL_FIELD(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); + AddOption("--test-inspect-depth", + "specify the inspect depth for error formatting in test reporters", + &EnvironmentOptions::test_inspect_depth, + kDisallowedInEnvvar, + 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", + BOOL_FIELD(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", + BOOL_FIELD(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("--experimental-test-tag-filter", + "run tests matching the given tag filter expression", + &EnvironmentOptions::experimental_test_tag_filter, + kDisallowedInEnvvar, + 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-include-all", + "include source files that were never loaded in the coverage " + "report", + BOOL_FIELD(coverage_include_all), + kAllowedInEnvvar, + false, + 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-randomize", + "run tests in a random order", + BOOL_FIELD(test_randomize), + kAllowedInEnvvar, + false, + OptionNamespaces::kTestRunnerNamespace); + AddOption("[has_test_random_seed]", "", BOOL_FIELD(has_test_random_seed)); + AddOption("--test-random-seed", + "seed used to randomize test execution order", + &EnvironmentOptions::test_random_seed, + kAllowedInEnvvar, + OptionNamespaces::kTestRunnerNamespace); + Implies("--test-random-seed", "[has_test_random_seed]"); + 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. + BOOL_FIELD(test_udp_no_try_send), + kDisallowedInEnvvar); + AddOption("--throw-deprecation", + "throw an exception on deprecations", + BOOL_FIELD(throw_deprecation), + kAllowedInEnvvar); + AddOption("--trace-deprecation", + "show stack traces on deprecations", + BOOL_FIELD(trace_deprecation), + kAllowedInEnvvar); + AddOption("--trace-exit", + "show stack trace when an environment exits", + BOOL_FIELD(trace_exit), + kAllowedInEnvvar); + AddOption("--trace-sync-io", + "show stack trace when use of sync IO is detected after the " + "first tick", + BOOL_FIELD(trace_sync_io), + kAllowedInEnvvar); + AddOption("--trace-tls", + "prints TLS packet trace information to stderr", + BOOL_FIELD(trace_tls), + kAllowedInEnvvar); + AddOption("--trace-uncaught", + "show stack traces for the `throw` behind uncaught exceptions", + BOOL_FIELD(trace_uncaught), + kAllowedInEnvvar); + AddOption("--trace-warnings", + "show stack traces on process warnings", + BOOL_FIELD(trace_warnings), + kAllowedInEnvvar); + AddOption("--trace-promises", + "show stack traces on promise initialization and resolution", + BOOL_FIELD(trace_promises), + kAllowedInEnvvar); + + AddOption("--trace-env", + "Print accesses to the environment variables", + BOOL_FIELD(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", + BOOL_FIELD(trace_env_js_stack), + kAllowedInEnvvar); + AddOption( + "--trace-env-native-stack", + "Print accesses to the environment variables and the native stack trace", + BOOL_FIELD(trace_env_native_stack), + kAllowedInEnvvar); + +#if HAVE_OPENSSL + AddOption("--use-system-ca", + "use system's CA store", + BOOL_FIELD(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", + BOOL_FIELD(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 */ + BOOL_FIELD(verify_base_objects), + kAllowedInEnvvar); + AddOption("--watch", + "run in watch mode", + BOOL_FIELD(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", + BOOL_FIELD(watch_mode_preserve_output), + kAllowedInEnvvar, + false, + OptionNamespaces::kWatchNamespace); + Implies("--watch-path", "--watch"); + AddOption("--check", + "syntax check script without executing", + BOOL_FIELD(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]", "", BOOL_FIELD(has_eval_string)); + AddOption("--eval", "evaluate script", &EnvironmentOptions::eval_string); + Implies("--eval", "[has_eval_string]"); + AddOption( + "--print", "evaluate script and print result", BOOL_FIELD(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.", + BOOL_FIELD(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", + BOOL_FIELD(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)", + BOOL_FIELD(tls_min_v1_0), + kAllowedInEnvvar); + AddOption("--tls-min-v1.1", + "set default TLS minimum to TLSv1.1 (default: TLSv1.2)", + BOOL_FIELD(tls_min_v1_1), + kAllowedInEnvvar); + AddOption("--tls-min-v1.2", + "set default TLS minimum to TLSv1.2 (default: TLSv1.2)", + BOOL_FIELD(tls_min_v1_2), + kAllowedInEnvvar); + AddOption("--tls-min-v1.3", + "set default TLS minimum to TLSv1.3 (default: TLSv1.2)", + BOOL_FIELD(tls_min_v1_3), + kAllowedInEnvvar); + AddOption("--tls-max-v1.2", + "set default TLS maximum to TLSv1.2 (default: TLSv1.3)", + BOOL_FIELD(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)", + BOOL_FIELD(tls_max_v1_3), + kAllowedInEnvvar); + + AddOption("--report-exclude-env", + "Exclude environment variables when generating report" + " (default: false)", + BOOL_FIELD(report_exclude_env), + kAllowedInEnvvar); + AddOption("--report-exclude-network", + "exclude network interface diagnostics." + " (default: false)", + BOOL_FIELD(report_exclude_network), + kAllowedInEnvvar); +} + +PerIsolateOptionsParser::PerIsolateOptionsParser( + const EnvironmentOptionsParser& eop) { + AddOption("--worker-snapshot", + "start worker threads from the bootstrapped context in the " + "built-in startup snapshot", + BOOL_FIELD(worker_snapshot), + kAllowedInEnvvar, + true); + AddOption("--track-heap-objects", + "track heap object allocations for heap snapshots", + BOOL_FIELD(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", + BOOL_FIELD(report_uncaught_exception), + kAllowedInEnvvar); + AddOption("--report-on-signal", + "generate diagnostic report upon receiving signals", + BOOL_FIELD(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", + "", + BOOL_FIELD(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.", + BOOL_FIELD(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", + BOOL_FIELD(zero_fill_all_buffers), + kAllowedInEnvvar); + AddOption("--debug-arraybuffer-allocations", + "", /* undocumented, only for debugging */ + BOOL_FIELD(debug_arraybuffer_allocations), + kAllowedInEnvvar); + AddOption("--disable-proto", + "disable Object.prototype.__proto__", + &PerProcessOptions::disable_proto, + kAllowedInEnvvar); + AddOption("--node-snapshot", + "", // It's a debug-only option. + BOOL_FIELD(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", + BOOL_FIELD(print_bash_completion)); + AddOption( + "--help", "print node command line options", BOOL_FIELD(print_help)); + AddAlias("-h", "--help"); + AddOption("--version", "print Node.js version", BOOL_FIELD(print_version)); + AddAlias("-v", "--version"); + AddOption("--v8-options", + "print V8 command line options", + BOOL_FIELD(print_v8_help)); + AddOption("--report-compact", + "output compact single-line JSON", + BOOL_FIELD(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", + BOOL_FIELD(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 + , + BOOL_FIELD(use_openssl_ca), + kAllowedInEnvvar); + AddOption("--use-bundled-ca", + "use bundled CA store" +#if !defined(NODE_OPENSSL_CERT_STORE) + " (default)" +#endif + , + BOOL_FIELD(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]", "", BOOL_FIELD(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", + BOOL_FIELD(enable_fips_crypto), + kAllowedInEnvvar); + AddOption("--enable-fips-indicator-events", + "publish FIPS indicator results to the " + "crypto.fips.indicator diagnostics channel", + BOOL_FIELD(enable_fips_indicator_events), + kAllowedInEnvvar); + AddOption("--force-fips", + "force FIPS crypto (optional mode: provider or strict)", + BOOL_FIELD(force_fips_crypto), + kAllowedInEnvvar); + AddOption("[force_fips_crypto_policy]", + "", + &PerProcessOptions::force_fips_crypto_policy, + kAllowedInEnvvar); + AddAlias("--force-fips=", {"[force_fips_crypto_policy]", "--force-fips"}); +#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", + BOOL_FIELD(openssl_legacy_provider), + kAllowedInEnvvar); + AddOption("--openssl-shared-config", + "enable OpenSSL shared configuration", + BOOL_FIELD(openssl_shared_config), + kAllowedInEnvvar); + +#endif // OPENSSL_VERSION_MAJOR + AddOption("--use-largepages", + "This option is no longer supported and a no-op. It still accepts" + " these values for compatibility: 'off' (default), 'on' (report a " + "warning to stderr), or 'silent' (same as 'off').", + &PerProcessOptions::use_largepages, + kAllowedInEnvvar); + + AddOption("--trace-sigint", + "enable printing JavaScript stacktrace on SIGINT", + BOOL_FIELD(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.", + BOOL_FIELD(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 = field->GetBool(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; + + if (option_info.namespace_id == + NamespaceEnumToString(OptionNamespaces::kBenchRunnerNamespace) && + !opts->per_isolate->per_env->experimental_bench) { + continue; + } + + // 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 = field->GetBool(opts); +#if HAVE_OPENSSL + if (option_name == "--force-fips" && current_value) { + flags.push_back(option_name + "=" + opts->force_fips_crypto_policy); + break; + } +#endif + // 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) diff --git a/src/node_options.h b/src/node_options.h index f0d9eb3d3140..814b964a4468 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -1,805 +1,806 @@ -#ifndef SRC_NODE_OPTIONS_H_ -#define SRC_NODE_OPTIONS_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include -#include -#include -#include -#include "node_constants.h" -#include "node_mutex.h" -#include "util.h" - -#if HAVE_OPENSSL -#include "openssl/opensslv.h" -#include "quic/guard.h" -#endif - -namespace node { - -// Helper macro for defining a 1-bit boolean field in a struct. -#define DEFINE_BOOL_FIELD(field) bool field : 1 - -class HostPort { - public: - HostPort(const std::string& host_name, int port) - : host_name_(host_name), port_(port) {} - HostPort(const HostPort&) = default; - HostPort& operator=(const HostPort&) = default; - HostPort(HostPort&&) = default; - HostPort& operator=(HostPort&&) = default; - - void set_host(const std::string& host) { host_name_ = host; } - - void set_port(uint16_t port) { port_ = port; } - - const std::string& host() const { return host_name_; } - - uint16_t port() const { return port_; } - - void Update(const HostPort& other) { - if (!other.host_name_.empty()) host_name_ = other.host_name_; - port_ = other.port_; - } - - private: - std::string host_name_; - uint16_t port_; -}; - -class Options { - public: - virtual void CheckOptions(std::vector* errors, - std::vector* argv) {} - virtual ~Options() = default; -}; - -struct InspectPublishUid { - DEFINE_BOOL_FIELD(console); - DEFINE_BOOL_FIELD(http); -}; - -// These options are currently essentially per-Environment, but it can be nice -// to keep them separate since they are a group of options applying to a very -// specific part of Node. It might also make more sense for them to be -// per-Isolate, rather than per-Environment. -class DebugOptions : public Options { - public: - DebugOptions() = default; - DebugOptions(const DebugOptions&) = default; - DebugOptions& operator=(const DebugOptions&) = default; - DebugOptions(DebugOptions&&) = default; - DebugOptions& operator=(DebugOptions&&) = default; - - // --inspect-publish-uid - std::string inspect_publish_uid_string = "stderr,http"; - - DEFINE_BOOL_FIELD(allow_attaching_debugger) = true; - // --inspect - DEFINE_BOOL_FIELD(inspector_enabled) = false; - // --inspect-wait - DEFINE_BOOL_FIELD(inspect_wait) = false; - // --debug - DEFINE_BOOL_FIELD(deprecated_debug) = false; - // --inspect-brk - DEFINE_BOOL_FIELD(break_first_line) = false; - // --inspect-brk-node - DEFINE_BOOL_FIELD(break_node_first_line) = false; - - InspectPublishUid inspect_publish_uid; - - enum { kDefaultInspectorPort = 9229 }; - - HostPort host_port{"127.0.0.1", kDefaultInspectorPort}; - - // Used to patch the options as if --inspect-brk is passed. - void EnableBreakFirstLine() { - inspector_enabled = true; - break_first_line = true; - } - - void DisableWaitOrBreakFirstLine() { - inspect_wait = false; - break_first_line = false; - } - - bool wait_for_connect() const { - return break_first_line || break_node_first_line || inspect_wait; - } - - bool should_break_first_line() const { - return break_first_line || break_node_first_line; - } - - void CheckOptions(std::vector* errors, - std::vector* argv) override; -}; - -#ifndef EXPERIMENTALS_DEFAULT_VALUE -#define EXPERIMENTALS_DEFAULT_VALUE false -#endif - -class EnvironmentOptions : public Options { - public: -#if HAVE_INSPECTOR - static constexpr uint64_t kDefaultCpuProfInterval = 1000; - static constexpr uint64_t kDefaultHeapProfInterval = 512 * 1024; -#endif - static constexpr bool IS_DEBUG = -#ifdef DEBUG - true; -#else - false; -#endif // DEBUG - - std::string dns_result_order; - std::string localstorage_file; - std::string input_type; // Value of --input-type - std::string heap_snapshot_signal; - std::string redirect_warnings; - std::string diagnostic_dir; - std::string bench_isolation = "process"; - std::string bench_name_pattern; - std::string test_rerun_failures_path; - std::string test_global_setup_path; - std::string test_isolation = "process"; - std::string test_shard; - std::string trace_require_module; - std::string unhandled_rejections; - std::string watch_mode_kill_signal = "SIGTERM"; - std::string eval_string; - std::string tls_keylog; - std::string experimental_config_file_path; - std::string experimental_package_map_path; -#if HAVE_INSPECTOR - std::string cpu_prof_dir; - std::string cpu_prof_name; - std::string heap_prof_dir; - std::string heap_prof_name; -#endif // HAVE_INSPECTOR - - std::vector conditions; - std::vector allow_fs_read; - std::vector allow_fs_write; - std::vector disable_warnings; - std::vector bench_reporter; - std::vector bench_reporter_destination; - std::vector env_file; - std::vector optional_env_file; - std::vector test_name_pattern; - std::vector test_reporter; - std::vector test_reporter_destination; - std::vector test_skip_pattern; - std::vector experimental_test_tag_filter; - std::vector coverage_include_pattern; - std::vector coverage_exclude_pattern; - std::vector userland_loaders; - std::vector watch_mode_paths; - std::vector preload_cjs_modules; - std::vector preload_esm_modules; - std::vector user_argv; - - int64_t heap_snapshot_near_heap_limit = 0; - uint64_t network_family_autoselection_attempt_timeout = 500; - uint64_t max_http_header_size = 16 * 1024; - uint64_t bench_samples = 0; - uint64_t bench_warmup = 0; - uint64_t test_runner_concurrency = 0; - uint64_t test_runner_timeout = 0; - uint64_t test_coverage_branches = 0; - uint64_t test_coverage_functions = 0; - uint64_t test_coverage_lines = 0; - uint64_t test_random_seed = 0; -#if HAVE_INSPECTOR - uint64_t cpu_prof_interval = kDefaultCpuProfInterval; - uint64_t heap_prof_interval = kDefaultHeapProfInterval; -#endif // HAVE_INSPECTOR - - // These are intentionally not bools. They are 1 bit packed flags in - // order to reduce the overall size of the struct. - - DEFINE_BOOL_FIELD(abort_on_uncaught_exception) = false; - DEFINE_BOOL_FIELD(detect_module) = true; - DEFINE_BOOL_FIELD(disable_sigusr1) = false; - DEFINE_BOOL_FIELD(print_required_tla) = false; - DEFINE_BOOL_FIELD(require_module) = true; - DEFINE_BOOL_FIELD(enable_source_maps) = false; - DEFINE_BOOL_FIELD(experimental_addon_modules) = true; - DEFINE_BOOL_FIELD(experimental_bench) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_eventsource) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_ffi) = HAVE_FFI; - DEFINE_BOOL_FIELD(experimental_web_worker) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_websocket) = true; - DEFINE_BOOL_FIELD(experimental_sqlite) = HAVE_SQLITE; - DEFINE_BOOL_FIELD(experimental_stream_iter) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_vfs) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(webstorage) = HAVE_SQLITE; - DEFINE_BOOL_FIELD(experimental_dtls) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_quic) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_global_navigator) = true; - DEFINE_BOOL_FIELD(experimental_global_web_crypto) = true; - DEFINE_BOOL_FIELD(experimental_import_text) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_import_meta_resolve) = - EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(entry_is_url) = false; - DEFINE_BOOL_FIELD(permission) = false; - DEFINE_BOOL_FIELD(permission_audit) = false; - DEFINE_BOOL_FIELD(allow_addons) = false; - DEFINE_BOOL_FIELD(allow_fs_vfs) = false; - DEFINE_BOOL_FIELD(allow_inspector) = false; - DEFINE_BOOL_FIELD(allow_child_process) = false; - DEFINE_BOOL_FIELD(allow_net) = false; - DEFINE_BOOL_FIELD(allow_wasi) = false; - DEFINE_BOOL_FIELD(allow_ffi) = false; - DEFINE_BOOL_FIELD(allow_openssl_store) = false; - DEFINE_BOOL_FIELD(allow_worker_threads) = false; - DEFINE_BOOL_FIELD(experimental_vm_modules) = EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(async_context_frame) = true; - DEFINE_BOOL_FIELD(expose_internals) = false; - DEFINE_BOOL_FIELD(force_node_api_uncaught_exceptions_policy) = false; - DEFINE_BOOL_FIELD(frozen_intrinsics) = false; - DEFINE_BOOL_FIELD(network_family_autoselection) = true; - DEFINE_BOOL_FIELD(deprecation) = true; - DEFINE_BOOL_FIELD(force_async_hooks_checks) = true; - DEFINE_BOOL_FIELD(allow_native_addons) = true; - DEFINE_BOOL_FIELD(global_search_paths) = true; - DEFINE_BOOL_FIELD(warnings) = true; - DEFINE_BOOL_FIELD(force_context_aware) = false; - DEFINE_BOOL_FIELD(pending_deprecation) = false; - DEFINE_BOOL_FIELD(preserve_symlinks) = false; - DEFINE_BOOL_FIELD(preserve_symlinks_main) = false; - DEFINE_BOOL_FIELD(prof_process) = false; - DEFINE_BOOL_FIELD(has_env_file_string) = false; - DEFINE_BOOL_FIELD(bench_runner) = false; - DEFINE_BOOL_FIELD(has_bench_options) = false; - DEFINE_BOOL_FIELD(has_bench_samples) = false; - DEFINE_BOOL_FIELD(has_bench_warmup) = false; - DEFINE_BOOL_FIELD(test_runner) = false; - DEFINE_BOOL_FIELD(test_runner_coverage) = false; - DEFINE_BOOL_FIELD(test_runner_force_exit) = false; - DEFINE_BOOL_FIELD(test_runner_module_mocks) = false; - DEFINE_BOOL_FIELD(test_runner_update_snapshots) = false; - DEFINE_BOOL_FIELD(test_randomize) = false; - DEFINE_BOOL_FIELD(has_test_random_seed) = false; - DEFINE_BOOL_FIELD(test_only) = false; - DEFINE_BOOL_FIELD(test_udp_no_try_send) = false; - DEFINE_BOOL_FIELD(coverage_include_all) = false; - DEFINE_BOOL_FIELD(throw_deprecation) = false; - DEFINE_BOOL_FIELD(trace_deprecation) = false; - DEFINE_BOOL_FIELD(trace_exit) = false; - DEFINE_BOOL_FIELD(trace_sync_io) = false; - DEFINE_BOOL_FIELD(trace_tls) = false; - DEFINE_BOOL_FIELD(trace_uncaught) = false; - DEFINE_BOOL_FIELD(trace_warnings) = false; - DEFINE_BOOL_FIELD(trace_promises) = false; - DEFINE_BOOL_FIELD(trace_env) = false; - DEFINE_BOOL_FIELD(trace_env_js_stack) = false; - DEFINE_BOOL_FIELD(trace_env_native_stack) = false; - DEFINE_BOOL_FIELD(use_system_ca) = false; - DEFINE_BOOL_FIELD(extra_info_on_fatal_exception) = true; - DEFINE_BOOL_FIELD(verify_base_objects) = IS_DEBUG; - DEFINE_BOOL_FIELD(watch_mode) = false; - DEFINE_BOOL_FIELD(watch_mode_report_to_parent) = false; - DEFINE_BOOL_FIELD(watch_mode_preserve_output) = false; - DEFINE_BOOL_FIELD(syntax_check_only) = false; - DEFINE_BOOL_FIELD(has_eval_string) = false; - DEFINE_BOOL_FIELD(print_eval) = false; - DEFINE_BOOL_FIELD(force_repl) = false; - DEFINE_BOOL_FIELD(insecure_http_parser) = false; - DEFINE_BOOL_FIELD(use_env_proxy) = false; - DEFINE_BOOL_FIELD(tls_min_v1_0) = false; - DEFINE_BOOL_FIELD(tls_min_v1_1) = false; - DEFINE_BOOL_FIELD(tls_min_v1_2) = false; - DEFINE_BOOL_FIELD(tls_min_v1_3) = false; - DEFINE_BOOL_FIELD(tls_max_v1_2) = false; - DEFINE_BOOL_FIELD(tls_max_v1_3) = false; - DEFINE_BOOL_FIELD(strip_types) = HAVE_AMARO; - DEFINE_BOOL_FIELD(report_exclude_env) = false; - DEFINE_BOOL_FIELD(report_exclude_network) = false; -#if HAVE_INSPECTOR - DEFINE_BOOL_FIELD(cpu_prof) = false; - DEFINE_BOOL_FIELD(experimental_network_inspection) = - EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_worker_inspection) = - EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_storage_inspection) = - EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(experimental_inspector_network_resource) = - EXPERIMENTALS_DEFAULT_VALUE; - DEFINE_BOOL_FIELD(heap_prof) = false; -#endif // HAVE_INSPECTOR - - inline DebugOptions* get_debug_options() { return &debug_options_; } - inline const DebugOptions& debug_options() const { return debug_options_; } - - void CheckOptions(std::vector* errors, - std::vector* argv) override; - - // `--bench` and the other benchmark runner options are gated behind - // `--experimental-bench`, but the gate and the options it guards can come - // from different option sources, each of which is parsed in its own - // options_parser::Parse() pass. CheckOptions() runs at the end of every - // pass, so this constraint cannot be validated there: the gate may still - // arrive in a later pass. Callers must invoke this once all of their option - // sources have been parsed. - void CheckBenchOptions(std::vector* errors) const; - - private: - DebugOptions debug_options_; -}; - -class PerIsolateOptions : public Options { - public: - bool worker_snapshot = true; // --[no-]worker-snapshot - PerIsolateOptions() = default; - PerIsolateOptions(PerIsolateOptions&&) = default; - - std::shared_ptr per_env{new EnvironmentOptions()}; - - std::string max_old_space_size_percentage; - std::string max_old_space_size; - std::string report_signal = "SIGUSR2"; - std::string build_snapshot_config; - - int64_t stack_trace_limit = 10; - - DEFINE_BOOL_FIELD(track_heap_objects) = false; - DEFINE_BOOL_FIELD(report_uncaught_exception) = false; - DEFINE_BOOL_FIELD(report_on_signal) = false; - DEFINE_BOOL_FIELD(experimental_shadow_realm) = false; - DEFINE_BOOL_FIELD(build_snapshot) = false; - - inline EnvironmentOptions* get_per_env_options(); - void CheckOptions(std::vector* errors, - std::vector* argv) override; - void HandleMaxOldSpaceSizePercentage(std::vector* errors, - std::string* max_old_space_size); - - inline std::shared_ptr Clone() const; - - private: - PerIsolateOptions(const PerIsolateOptions&) = default; -}; - -class PerProcessOptions : public Options { - public: - // Options shouldn't be here unless they affect the entire process scope, and - // that should avoided when possible. - // - // When an option is used during process initialization, it does not need - // protection, but any use after that will likely require synchronization - // using the node::per_process::cli_options_mutex, typically: - // - // Mutex::ScopedLock lock(node::per_process::cli_options_mutex); - std::shared_ptr per_isolate{new PerIsolateOptions()}; - - std::string title; - std::string trace_event_categories; -#if defined(V8_USE_PERFETTO) - std::string trace_event_file_pattern = "node_trace.${rotation}.pftrace"; -#else - std::string trace_event_file_pattern = "node_trace.${rotation}.log"; -#endif - std::string disable_proto; - std::string snapshot_blob; - std::string experimental_sea_config; - std::string run; - std::string build_sea; -#ifdef NODE_HAVE_I18N_SUPPORT - std::string icu_data_dir; -#endif -// Per-process because they affect singleton OpenSSL shared library state, -// or are used once during process initialization. -#if HAVE_OPENSSL - std::string openssl_config; - std::string tls_cipher_list = DEFAULT_CIPHER_LIST_CORE; -#endif // HAVE_OPENSSL - std::string report_directory; - std::string report_filename; - // TODO(addaleax): Some of these could probably be per-Environment. - std::string use_largepages = "off"; - - std::vector security_reverts; - std::vector cmdline; - - int64_t v8_thread_pool_size = 4; -#if HAVE_OPENSSL - int64_t secure_heap = 0; - int64_t secure_heap_min = 2; -#endif // HAVE_OPENSSL - - DEFINE_BOOL_FIELD(zero_fill_all_buffers) = false; - DEFINE_BOOL_FIELD(debug_arraybuffer_allocations) = false; - - // We enable the shared read-only heap which currently requires that the - // snapshot used in different isolates in the same process to be the same. - // Therefore --node-snapshot is a per-process option. - DEFINE_BOOL_FIELD(node_snapshot) = true; - - DEFINE_BOOL_FIELD(print_bash_completion) = false; - DEFINE_BOOL_FIELD(print_help) = false; - DEFINE_BOOL_FIELD(print_v8_help) = false; - DEFINE_BOOL_FIELD(print_version) = false; - -#if HAVE_OPENSSL -#ifdef NODE_OPENSSL_CERT_STORE - DEFINE_BOOL_FIELD(ssl_openssl_cert_store) = true; -#else - DEFINE_BOOL_FIELD(ssl_openssl_cert_store) = false; -#endif // NODE_OPENSSL_CERT_STORE - DEFINE_BOOL_FIELD(use_openssl_ca) = false; - DEFINE_BOOL_FIELD(use_bundled_ca) = false; - DEFINE_BOOL_FIELD(enable_fips_crypto) = false; - DEFINE_BOOL_FIELD(enable_fips_indicator_events) = false; - DEFINE_BOOL_FIELD(force_fips_crypto) = false; - std::string force_fips_crypto_policy = "provider"; -#endif // HAVE_OPENSSL -#if OPENSSL_VERSION_MAJOR >= 3 - DEFINE_BOOL_FIELD(openssl_legacy_provider) = false; - DEFINE_BOOL_FIELD(openssl_shared_config) = false; -#endif - - DEFINE_BOOL_FIELD(disable_wasm_trap_handler) = false; - DEFINE_BOOL_FIELD(report_on_fatalerror) = false; - DEFINE_BOOL_FIELD(report_compact) = false; - DEFINE_BOOL_FIELD(trace_sigint) = false; - // Tracks whether `--run` was passed, since an empty `run` is ambiguous - // between "not passed" and "passed without a script name" (lists scripts). - DEFINE_BOOL_FIELD(has_run) = false; - - inline PerIsolateOptions* get_per_isolate_options(); - void CheckOptions(std::vector* errors, - std::vector* argv) override; -}; - -// The actual options parser, as opposed to the structs containing them: - -namespace options_parser { - -HostPort SplitHostPort(const std::string& arg, - std::vector* errors); -void GetOptions(const v8::FunctionCallbackInfo& args); -std::string GetBashCompletion(); - -enum OptionType { - kNoOp, - kV8Option, - kBoolean, - kInteger, - kUInteger, - kString, - kHostPort, - kStringList, -}; -struct OptionMappingDetails { - OptionType type; - std::string help_text; -}; -std::unordered_map -MapEnvOptionsFlagInputType(); -std::unordered_map MapOptionsByNamespace( - std::string namespace_name); -std::unordered_map< - std::string, - std::unordered_map> -MapNamespaceOptionsAssociations(); -std::vector MapAvailableNamespaces(); - -// Define all namespace entries -#define OPTION_NAMESPACE_LIST(V) \ - V(kNoNamespace, "") \ - V(kBenchRunnerNamespace, "bench") \ - V(kTestRunnerNamespace, "test") \ - V(kWatchNamespace, "watch") \ - V(kPermissionNamespace, "permission") - -enum class OptionNamespaces { -#define V(name, _) name, - OPTION_NAMESPACE_LIST(V) -#undef V -}; - -inline const std::string NamespaceEnumToString(OptionNamespaces ns) { - switch (ns) { -#define V(name, string_value) \ - case OptionNamespaces::name: \ - return string_value; - OPTION_NAMESPACE_LIST(V) -#undef V - default: - return ""; - } -} - -inline constexpr auto AllNamespaces() { - return std::array{ -#define V(name, _) OptionNamespaces::name, - OPTION_NAMESPACE_LIST(V) -#undef V - }; -} - -template -class OptionsParser { - public: - virtual ~OptionsParser() = default; - - typedef Options TargetType; - - struct NoOp {}; - struct V8Option {}; - - // These methods add a single option to the parser. Optionally, it can be - // specified whether the option should be allowed from environment variable - // sources (i.e. NODE_OPTIONS). - - // default_is_true is only a hint in printing help text, it does not - // affect the default value of the option. Set the default value in the - // Options struct instead. - void AddOption( - const char* name, - const char* help_text, - bool (*getter)(Options*), - void (*setter)(Options*, bool), - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - bool default_is_true = false, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - void AddOption(const char* name, - const char* help_text, - uint64_t Options::*field, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace, - bool strict = false); - void AddOption( - const char* name, - const char* help_text, - int64_t Options::*field, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - void AddOption( - const char* name, - const char* help_text, - std::string Options::*field, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - void AddOption( - const char* name, - const char* help_text, - std::vector Options::*field, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - void AddOption( - const char* name, - const char* help_text, - HostPort Options::*field, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - void AddOption( - const char* name, - const char* help_text, - NoOp no_op_tag, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - void AddOption( - const char* name, - const char* help_text, - V8Option v8_option_tag, - OptionEnvvarSettings env_setting = kDisallowedInEnvvar, - OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); - - // Adds aliases. An alias can be of the form "--option-a" -> "--option-b", - // or have a more complex group expansion, like - // "--option-a" -> { "--option-b", "--harmony-foobar", "--eval", "42" } - // If `from` has the form "--option-a=", the alias will only be expanded if - // the option is presented in that form (i.e. with a '='). - // If `from` has the form "--option-a ", the alias will only be expanded - // if the option has a non-option argument (not starting with -) following it. - void AddAlias(const char* from, const char* to); - void AddAlias(const char* from, const std::vector& to); - void AddAlias(const char* from, const std::initializer_list& to); - - // Add implications from some arbitrary option to a boolean one, either - // in a way that makes `from` set `to` to true or to false. - void Implies(const char* from, const char* to); - void ImpliesNot(const char* from, const char* to); - - // Insert options from another options parser into this one, along with - // a method that yields the target options type from this parser's options - // type. - template - void Insert(const OptionsParser& child_options_parser, - ChildOptions* (Options::*get_child)()); - - // Parse a sequence of options into an options struct, a list of - // arguments that were parsed as options, a list of unknown/JS engine options, - // and leave the remainder in the input `args` vector. - // - // For example, an `args` input of - // - // node --foo --harmony-bar --fizzle=42 -- /path/to/cow moo - // - // expands as - // - // - `args` -> { "node", "/path/to/cow", "moo" } - // - `exec_args` -> { "--foo", "--harmony-bar", "--fizzle=42" } - // - `v8_args` -> `{ "node", "--harmony-bar" } - // - `options->foo == true`, `options->fizzle == 42`. - // - // If `*error` is set, the result of the parsing should be discarded and the - // contents of any of the argument vectors should be considered undefined. - void Parse(std::vector* const args, - std::vector* const exec_args, - std::vector* const v8_args, - Options* const options, - OptionEnvvarSettings required_env_settings, - std::vector* const errors) const; - - private: - // We support the wide variety of different option types by remembering - // how to access them, given a certain `Options` struct. - - // Represents a field within `Options`. - class BaseOptionField { - public: - virtual ~BaseOptionField() = default; - virtual void* LookupImpl(Options* options) const = 0; - - // For boolean bit-field options, we can't return a pointer so we - // use virtual getter/setter methods instead. The default - // implementations go through LookupImpl for non-bit-field types. - virtual bool GetBool(Options* options) const { - return *static_cast(LookupImpl(options)); - } - virtual void SetBool(Options* options, bool value) { - *static_cast(LookupImpl(options)) = value; - } - - template - inline T* Lookup(Options* options) const { - return static_cast(LookupImpl(options)); - } - }; - - // Represents a field of type T within `Options` that can be looked up - // as a C++ member field. - template - class SimpleOptionField : public BaseOptionField { - public: - explicit SimpleOptionField(T Options::* field) : field_(field) {} - void* LookupImpl(Options* options) const override { - return static_cast(&(options->*field_)); - } - - private: - T Options::* field_; - }; - - // Represents a boolean bit-field within `Options`. Bit-fields cannot - // have their address taken, so we use getter/setter function pointers - // instead of a pointer-to-member. - class BitFieldOptionField : public BaseOptionField { - public: - using Getter = bool (*)(Options*); - using Setter = void (*)(Options*, bool); - - BitFieldOptionField(Getter get, Setter set) : get_(get), set_(set) {} - - void* LookupImpl(Options* options) const override { UNREACHABLE(); } - - bool GetBool(Options* options) const override { return get_(options); } - void SetBool(Options* options, bool value) override { - set_(options, value); - } - - private: - Getter get_; - Setter set_; - }; - - template - inline T* Lookup(std::shared_ptr field, - Options* options) const { - return field->template Lookup(options); - } - - // An option consists of: - // - A type. - // - A way to store/access the property value. - // - The information of whether it may occur in an env var or not. - // - A default value (if applicable). - // - A namespace ID (optional) to allow for namespacing of options. - struct OptionInfo { - OptionType type; - std::shared_ptr field; - OptionEnvvarSettings env_setting; - std::string help_text; - bool default_is_true = false; - std::string namespace_id; - bool strict = false; - }; - - // An implied option is composed of the information on where to store a - // specific boolean value (if another specific option is encountered). - struct Implication { - OptionType type; - std::string name; - std::shared_ptr target_field; - bool target_value; - }; - - // These are helpers that make `Insert()` support properties of other - // options structs, if we know how to access them. - template - static auto Convert(std::shared_ptr original, - ChildOptions* (Options::*get_child)()); - template - static auto Convert(typename OptionsParser::OptionInfo original, - ChildOptions* (Options::*get_child)()); - template - static auto Convert( - typename OptionsParser::Implication original, - ChildOptions* (Options::*get_child)()); - - std::unordered_map options_; - std::unordered_map> aliases_; - std::unordered_multimap implications_; - - template - friend class OptionsParser; - - friend void GetCLIOptionsValues( - const v8::FunctionCallbackInfo& args); - friend void GetCLIOptionsInfo( - const v8::FunctionCallbackInfo& args); - friend std::string GetBashCompletion(); - friend std::unordered_map - MapEnvOptionsFlagInputType(); - friend std::unordered_map - MapOptionsByNamespace(std::string namespace_name); - friend std::vector MapAvailableNamespaces(); - friend void GetEnvOptionsInputType( - const v8::FunctionCallbackInfo& args); - friend void GetOptionsAsFlags( - const v8::FunctionCallbackInfo& args); -}; - -using StringVector = std::vector; -template -void Parse(StringVector* const args, - StringVector* const exec_args, - StringVector* const v8_args, - OptionsType* const options, - OptionEnvvarSettings required_env_settings, - StringVector* const errors); - -} // namespace options_parser - -// Helper macro for passing bit-field members to AddOption(). Expands to a -// getter and setter function pointer pair that can read/write the named -// bit-field without requiring a pointer-to-member (which is illegal for -// bit-fields). -#define BOOL_FIELD(field) \ - +[](TargetType* opts) -> bool { return opts->field; }, \ - +[](TargetType* opts, bool v) { opts->field = v; } - -namespace per_process { - -extern Mutex cli_options_mutex; -extern NODE_EXTERN_PRIVATE std::shared_ptr cli_options; - -} // namespace per_process - -void HandleEnvOptions(std::shared_ptr env_options); -void HandleEnvOptions(std::shared_ptr env_options, - std::function opt_getter); - -std::vector ParseNodeOptionsEnvVar( - const std::string& node_options, std::vector* errors); - -#undef DEFINE_BOOL_FIELD -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_NODE_OPTIONS_H_ +#ifndef SRC_NODE_OPTIONS_H_ +#define SRC_NODE_OPTIONS_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include +#include +#include +#include +#include "node_constants.h" +#include "node_mutex.h" +#include "util.h" + +#if HAVE_OPENSSL +#include "openssl/opensslv.h" +#include "quic/guard.h" +#endif + +namespace node { + +// Helper macro for defining a 1-bit boolean field in a struct. +#define DEFINE_BOOL_FIELD(field) bool field : 1 + +class HostPort { + public: + HostPort(const std::string& host_name, int port) + : host_name_(host_name), port_(port) {} + HostPort(const HostPort&) = default; + HostPort& operator=(const HostPort&) = default; + HostPort(HostPort&&) = default; + HostPort& operator=(HostPort&&) = default; + + void set_host(const std::string& host) { host_name_ = host; } + + void set_port(uint16_t port) { port_ = port; } + + const std::string& host() const { return host_name_; } + + uint16_t port() const { return port_; } + + void Update(const HostPort& other) { + if (!other.host_name_.empty()) host_name_ = other.host_name_; + port_ = other.port_; + } + + private: + std::string host_name_; + uint16_t port_; +}; + +class Options { + public: + virtual void CheckOptions(std::vector* errors, + std::vector* argv) {} + virtual ~Options() = default; +}; + +struct InspectPublishUid { + DEFINE_BOOL_FIELD(console); + DEFINE_BOOL_FIELD(http); +}; + +// These options are currently essentially per-Environment, but it can be nice +// to keep them separate since they are a group of options applying to a very +// specific part of Node. It might also make more sense for them to be +// per-Isolate, rather than per-Environment. +class DebugOptions : public Options { + public: + DebugOptions() = default; + DebugOptions(const DebugOptions&) = default; + DebugOptions& operator=(const DebugOptions&) = default; + DebugOptions(DebugOptions&&) = default; + DebugOptions& operator=(DebugOptions&&) = default; + + // --inspect-publish-uid + std::string inspect_publish_uid_string = "stderr,http"; + + DEFINE_BOOL_FIELD(allow_attaching_debugger) = true; + // --inspect + DEFINE_BOOL_FIELD(inspector_enabled) = false; + // --inspect-wait + DEFINE_BOOL_FIELD(inspect_wait) = false; + // --debug + DEFINE_BOOL_FIELD(deprecated_debug) = false; + // --inspect-brk + DEFINE_BOOL_FIELD(break_first_line) = false; + // --inspect-brk-node + DEFINE_BOOL_FIELD(break_node_first_line) = false; + + InspectPublishUid inspect_publish_uid; + + enum { kDefaultInspectorPort = 9229 }; + + HostPort host_port{"127.0.0.1", kDefaultInspectorPort}; + + // Used to patch the options as if --inspect-brk is passed. + void EnableBreakFirstLine() { + inspector_enabled = true; + break_first_line = true; + } + + void DisableWaitOrBreakFirstLine() { + inspect_wait = false; + break_first_line = false; + } + + bool wait_for_connect() const { + return break_first_line || break_node_first_line || inspect_wait; + } + + bool should_break_first_line() const { + return break_first_line || break_node_first_line; + } + + void CheckOptions(std::vector* errors, + std::vector* argv) override; +}; + +#ifndef EXPERIMENTALS_DEFAULT_VALUE +#define EXPERIMENTALS_DEFAULT_VALUE false +#endif + +class EnvironmentOptions : public Options { + public: +#if HAVE_INSPECTOR + static constexpr uint64_t kDefaultCpuProfInterval = 1000; + static constexpr uint64_t kDefaultHeapProfInterval = 512 * 1024; +#endif + static constexpr bool IS_DEBUG = +#ifdef DEBUG + true; +#else + false; +#endif // DEBUG + + std::string dns_result_order; + std::string localstorage_file; + std::string input_type; // Value of --input-type + std::string heap_snapshot_signal; + std::string redirect_warnings; + std::string diagnostic_dir; + std::string bench_isolation = "process"; + std::string bench_name_pattern; + std::string test_rerun_failures_path; + std::string test_global_setup_path; + std::string test_isolation = "process"; + std::string test_shard; + std::string trace_require_module; + std::string unhandled_rejections; + std::string watch_mode_kill_signal = "SIGTERM"; + std::string eval_string; + std::string tls_keylog; + std::string experimental_config_file_path; + std::string experimental_package_map_path; +#if HAVE_INSPECTOR + std::string cpu_prof_dir; + std::string cpu_prof_name; + std::string heap_prof_dir; + std::string heap_prof_name; +#endif // HAVE_INSPECTOR + + std::vector conditions; + std::vector allow_fs_read; + std::vector allow_fs_write; + std::vector disable_warnings; + std::vector bench_reporter; + std::vector bench_reporter_destination; + std::vector env_file; + std::vector optional_env_file; + std::vector test_name_pattern; + std::vector test_reporter; + std::vector test_reporter_destination; + std::vector test_skip_pattern; + std::vector experimental_test_tag_filter; + std::vector coverage_include_pattern; + std::vector coverage_exclude_pattern; + std::vector userland_loaders; + std::vector watch_mode_paths; + std::vector preload_cjs_modules; + std::vector preload_esm_modules; + std::vector user_argv; + + int64_t heap_snapshot_near_heap_limit = 0; + uint64_t network_family_autoselection_attempt_timeout = 500; + uint64_t max_http_header_size = 16 * 1024; + uint64_t bench_samples = 0; + uint64_t bench_warmup = 0; + uint64_t test_runner_concurrency = 0; + uint64_t test_runner_timeout = 0; + int test_inspect_depth = -1; + uint64_t test_coverage_branches = 0; + uint64_t test_coverage_functions = 0; + uint64_t test_coverage_lines = 0; + uint64_t test_random_seed = 0; +#if HAVE_INSPECTOR + uint64_t cpu_prof_interval = kDefaultCpuProfInterval; + uint64_t heap_prof_interval = kDefaultHeapProfInterval; +#endif // HAVE_INSPECTOR + + // These are intentionally not bools. They are 1 bit packed flags in + // order to reduce the overall size of the struct. + + DEFINE_BOOL_FIELD(abort_on_uncaught_exception) = false; + DEFINE_BOOL_FIELD(detect_module) = true; + DEFINE_BOOL_FIELD(disable_sigusr1) = false; + DEFINE_BOOL_FIELD(print_required_tla) = false; + DEFINE_BOOL_FIELD(require_module) = true; + DEFINE_BOOL_FIELD(enable_source_maps) = false; + DEFINE_BOOL_FIELD(experimental_addon_modules) = true; + DEFINE_BOOL_FIELD(experimental_bench) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_eventsource) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_ffi) = HAVE_FFI; + DEFINE_BOOL_FIELD(experimental_web_worker) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_websocket) = true; + DEFINE_BOOL_FIELD(experimental_sqlite) = HAVE_SQLITE; + DEFINE_BOOL_FIELD(experimental_stream_iter) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_vfs) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(webstorage) = HAVE_SQLITE; + DEFINE_BOOL_FIELD(experimental_dtls) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_quic) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_global_navigator) = true; + DEFINE_BOOL_FIELD(experimental_global_web_crypto) = true; + DEFINE_BOOL_FIELD(experimental_import_text) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_import_meta_resolve) = + EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(entry_is_url) = false; + DEFINE_BOOL_FIELD(permission) = false; + DEFINE_BOOL_FIELD(permission_audit) = false; + DEFINE_BOOL_FIELD(allow_addons) = false; + DEFINE_BOOL_FIELD(allow_fs_vfs) = false; + DEFINE_BOOL_FIELD(allow_inspector) = false; + DEFINE_BOOL_FIELD(allow_child_process) = false; + DEFINE_BOOL_FIELD(allow_net) = false; + DEFINE_BOOL_FIELD(allow_wasi) = false; + DEFINE_BOOL_FIELD(allow_ffi) = false; + DEFINE_BOOL_FIELD(allow_openssl_store) = false; + DEFINE_BOOL_FIELD(allow_worker_threads) = false; + DEFINE_BOOL_FIELD(experimental_vm_modules) = EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(async_context_frame) = true; + DEFINE_BOOL_FIELD(expose_internals) = false; + DEFINE_BOOL_FIELD(force_node_api_uncaught_exceptions_policy) = false; + DEFINE_BOOL_FIELD(frozen_intrinsics) = false; + DEFINE_BOOL_FIELD(network_family_autoselection) = true; + DEFINE_BOOL_FIELD(deprecation) = true; + DEFINE_BOOL_FIELD(force_async_hooks_checks) = true; + DEFINE_BOOL_FIELD(allow_native_addons) = true; + DEFINE_BOOL_FIELD(global_search_paths) = true; + DEFINE_BOOL_FIELD(warnings) = true; + DEFINE_BOOL_FIELD(force_context_aware) = false; + DEFINE_BOOL_FIELD(pending_deprecation) = false; + DEFINE_BOOL_FIELD(preserve_symlinks) = false; + DEFINE_BOOL_FIELD(preserve_symlinks_main) = false; + DEFINE_BOOL_FIELD(prof_process) = false; + DEFINE_BOOL_FIELD(has_env_file_string) = false; + DEFINE_BOOL_FIELD(bench_runner) = false; + DEFINE_BOOL_FIELD(has_bench_options) = false; + DEFINE_BOOL_FIELD(has_bench_samples) = false; + DEFINE_BOOL_FIELD(has_bench_warmup) = false; + DEFINE_BOOL_FIELD(test_runner) = false; + DEFINE_BOOL_FIELD(test_runner_coverage) = false; + DEFINE_BOOL_FIELD(test_runner_force_exit) = false; + DEFINE_BOOL_FIELD(test_runner_module_mocks) = false; + DEFINE_BOOL_FIELD(test_runner_update_snapshots) = false; + DEFINE_BOOL_FIELD(test_randomize) = false; + DEFINE_BOOL_FIELD(has_test_random_seed) = false; + DEFINE_BOOL_FIELD(test_only) = false; + DEFINE_BOOL_FIELD(test_udp_no_try_send) = false; + DEFINE_BOOL_FIELD(coverage_include_all) = false; + DEFINE_BOOL_FIELD(throw_deprecation) = false; + DEFINE_BOOL_FIELD(trace_deprecation) = false; + DEFINE_BOOL_FIELD(trace_exit) = false; + DEFINE_BOOL_FIELD(trace_sync_io) = false; + DEFINE_BOOL_FIELD(trace_tls) = false; + DEFINE_BOOL_FIELD(trace_uncaught) = false; + DEFINE_BOOL_FIELD(trace_warnings) = false; + DEFINE_BOOL_FIELD(trace_promises) = false; + DEFINE_BOOL_FIELD(trace_env) = false; + DEFINE_BOOL_FIELD(trace_env_js_stack) = false; + DEFINE_BOOL_FIELD(trace_env_native_stack) = false; + DEFINE_BOOL_FIELD(use_system_ca) = false; + DEFINE_BOOL_FIELD(extra_info_on_fatal_exception) = true; + DEFINE_BOOL_FIELD(verify_base_objects) = IS_DEBUG; + DEFINE_BOOL_FIELD(watch_mode) = false; + DEFINE_BOOL_FIELD(watch_mode_report_to_parent) = false; + DEFINE_BOOL_FIELD(watch_mode_preserve_output) = false; + DEFINE_BOOL_FIELD(syntax_check_only) = false; + DEFINE_BOOL_FIELD(has_eval_string) = false; + DEFINE_BOOL_FIELD(print_eval) = false; + DEFINE_BOOL_FIELD(force_repl) = false; + DEFINE_BOOL_FIELD(insecure_http_parser) = false; + DEFINE_BOOL_FIELD(use_env_proxy) = false; + DEFINE_BOOL_FIELD(tls_min_v1_0) = false; + DEFINE_BOOL_FIELD(tls_min_v1_1) = false; + DEFINE_BOOL_FIELD(tls_min_v1_2) = false; + DEFINE_BOOL_FIELD(tls_min_v1_3) = false; + DEFINE_BOOL_FIELD(tls_max_v1_2) = false; + DEFINE_BOOL_FIELD(tls_max_v1_3) = false; + DEFINE_BOOL_FIELD(strip_types) = HAVE_AMARO; + DEFINE_BOOL_FIELD(report_exclude_env) = false; + DEFINE_BOOL_FIELD(report_exclude_network) = false; +#if HAVE_INSPECTOR + DEFINE_BOOL_FIELD(cpu_prof) = false; + DEFINE_BOOL_FIELD(experimental_network_inspection) = + EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_worker_inspection) = + EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_storage_inspection) = + EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(experimental_inspector_network_resource) = + EXPERIMENTALS_DEFAULT_VALUE; + DEFINE_BOOL_FIELD(heap_prof) = false; +#endif // HAVE_INSPECTOR + + inline DebugOptions* get_debug_options() { return &debug_options_; } + inline const DebugOptions& debug_options() const { return debug_options_; } + + void CheckOptions(std::vector* errors, + std::vector* argv) override; + + // `--bench` and the other benchmark runner options are gated behind + // `--experimental-bench`, but the gate and the options it guards can come + // from different option sources, each of which is parsed in its own + // options_parser::Parse() pass. CheckOptions() runs at the end of every + // pass, so this constraint cannot be validated there: the gate may still + // arrive in a later pass. Callers must invoke this once all of their option + // sources have been parsed. + void CheckBenchOptions(std::vector* errors) const; + + private: + DebugOptions debug_options_; +}; + +class PerIsolateOptions : public Options { + public: + bool worker_snapshot = true; // --[no-]worker-snapshot + PerIsolateOptions() = default; + PerIsolateOptions(PerIsolateOptions&&) = default; + + std::shared_ptr per_env{new EnvironmentOptions()}; + + std::string max_old_space_size_percentage; + std::string max_old_space_size; + std::string report_signal = "SIGUSR2"; + std::string build_snapshot_config; + + int64_t stack_trace_limit = 10; + + DEFINE_BOOL_FIELD(track_heap_objects) = false; + DEFINE_BOOL_FIELD(report_uncaught_exception) = false; + DEFINE_BOOL_FIELD(report_on_signal) = false; + DEFINE_BOOL_FIELD(experimental_shadow_realm) = false; + DEFINE_BOOL_FIELD(build_snapshot) = false; + + inline EnvironmentOptions* get_per_env_options(); + void CheckOptions(std::vector* errors, + std::vector* argv) override; + void HandleMaxOldSpaceSizePercentage(std::vector* errors, + std::string* max_old_space_size); + + inline std::shared_ptr Clone() const; + + private: + PerIsolateOptions(const PerIsolateOptions&) = default; +}; + +class PerProcessOptions : public Options { + public: + // Options shouldn't be here unless they affect the entire process scope, and + // that should avoided when possible. + // + // When an option is used during process initialization, it does not need + // protection, but any use after that will likely require synchronization + // using the node::per_process::cli_options_mutex, typically: + // + // Mutex::ScopedLock lock(node::per_process::cli_options_mutex); + std::shared_ptr per_isolate{new PerIsolateOptions()}; + + std::string title; + std::string trace_event_categories; +#if defined(V8_USE_PERFETTO) + std::string trace_event_file_pattern = "node_trace.${rotation}.pftrace"; +#else + std::string trace_event_file_pattern = "node_trace.${rotation}.log"; +#endif + std::string disable_proto; + std::string snapshot_blob; + std::string experimental_sea_config; + std::string run; + std::string build_sea; +#ifdef NODE_HAVE_I18N_SUPPORT + std::string icu_data_dir; +#endif +// Per-process because they affect singleton OpenSSL shared library state, +// or are used once during process initialization. +#if HAVE_OPENSSL + std::string openssl_config; + std::string tls_cipher_list = DEFAULT_CIPHER_LIST_CORE; +#endif // HAVE_OPENSSL + std::string report_directory; + std::string report_filename; + // TODO(addaleax): Some of these could probably be per-Environment. + std::string use_largepages = "off"; + + std::vector security_reverts; + std::vector cmdline; + + int64_t v8_thread_pool_size = 4; +#if HAVE_OPENSSL + int64_t secure_heap = 0; + int64_t secure_heap_min = 2; +#endif // HAVE_OPENSSL + + DEFINE_BOOL_FIELD(zero_fill_all_buffers) = false; + DEFINE_BOOL_FIELD(debug_arraybuffer_allocations) = false; + + // We enable the shared read-only heap which currently requires that the + // snapshot used in different isolates in the same process to be the same. + // Therefore --node-snapshot is a per-process option. + DEFINE_BOOL_FIELD(node_snapshot) = true; + + DEFINE_BOOL_FIELD(print_bash_completion) = false; + DEFINE_BOOL_FIELD(print_help) = false; + DEFINE_BOOL_FIELD(print_v8_help) = false; + DEFINE_BOOL_FIELD(print_version) = false; + +#if HAVE_OPENSSL +#ifdef NODE_OPENSSL_CERT_STORE + DEFINE_BOOL_FIELD(ssl_openssl_cert_store) = true; +#else + DEFINE_BOOL_FIELD(ssl_openssl_cert_store) = false; +#endif // NODE_OPENSSL_CERT_STORE + DEFINE_BOOL_FIELD(use_openssl_ca) = false; + DEFINE_BOOL_FIELD(use_bundled_ca) = false; + DEFINE_BOOL_FIELD(enable_fips_crypto) = false; + DEFINE_BOOL_FIELD(enable_fips_indicator_events) = false; + DEFINE_BOOL_FIELD(force_fips_crypto) = false; + std::string force_fips_crypto_policy = "provider"; +#endif // HAVE_OPENSSL +#if OPENSSL_VERSION_MAJOR >= 3 + DEFINE_BOOL_FIELD(openssl_legacy_provider) = false; + DEFINE_BOOL_FIELD(openssl_shared_config) = false; +#endif + + DEFINE_BOOL_FIELD(disable_wasm_trap_handler) = false; + DEFINE_BOOL_FIELD(report_on_fatalerror) = false; + DEFINE_BOOL_FIELD(report_compact) = false; + DEFINE_BOOL_FIELD(trace_sigint) = false; + // Tracks whether `--run` was passed, since an empty `run` is ambiguous + // between "not passed" and "passed without a script name" (lists scripts). + DEFINE_BOOL_FIELD(has_run) = false; + + inline PerIsolateOptions* get_per_isolate_options(); + void CheckOptions(std::vector* errors, + std::vector* argv) override; +}; + +// The actual options parser, as opposed to the structs containing them: + +namespace options_parser { + +HostPort SplitHostPort(const std::string& arg, + std::vector* errors); +void GetOptions(const v8::FunctionCallbackInfo& args); +std::string GetBashCompletion(); + +enum OptionType { + kNoOp, + kV8Option, + kBoolean, + kInteger, + kUInteger, + kString, + kHostPort, + kStringList, +}; +struct OptionMappingDetails { + OptionType type; + std::string help_text; +}; +std::unordered_map +MapEnvOptionsFlagInputType(); +std::unordered_map MapOptionsByNamespace( + std::string namespace_name); +std::unordered_map< + std::string, + std::unordered_map> +MapNamespaceOptionsAssociations(); +std::vector MapAvailableNamespaces(); + +// Define all namespace entries +#define OPTION_NAMESPACE_LIST(V) \ + V(kNoNamespace, "") \ + V(kBenchRunnerNamespace, "bench") \ + V(kTestRunnerNamespace, "test") \ + V(kWatchNamespace, "watch") \ + V(kPermissionNamespace, "permission") + +enum class OptionNamespaces { +#define V(name, _) name, + OPTION_NAMESPACE_LIST(V) +#undef V +}; + +inline const std::string NamespaceEnumToString(OptionNamespaces ns) { + switch (ns) { +#define V(name, string_value) \ + case OptionNamespaces::name: \ + return string_value; + OPTION_NAMESPACE_LIST(V) +#undef V + default: + return ""; + } +} + +inline constexpr auto AllNamespaces() { + return std::array{ +#define V(name, _) OptionNamespaces::name, + OPTION_NAMESPACE_LIST(V) +#undef V + }; +} + +template +class OptionsParser { + public: + virtual ~OptionsParser() = default; + + typedef Options TargetType; + + struct NoOp {}; + struct V8Option {}; + + // These methods add a single option to the parser. Optionally, it can be + // specified whether the option should be allowed from environment variable + // sources (i.e. NODE_OPTIONS). + + // default_is_true is only a hint in printing help text, it does not + // affect the default value of the option. Set the default value in the + // Options struct instead. + void AddOption( + const char* name, + const char* help_text, + bool (*getter)(Options*), + void (*setter)(Options*, bool), + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + bool default_is_true = false, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + void AddOption(const char* name, + const char* help_text, + uint64_t Options::*field, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace, + bool strict = false); + void AddOption( + const char* name, + const char* help_text, + int64_t Options::*field, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + void AddOption( + const char* name, + const char* help_text, + std::string Options::*field, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + void AddOption( + const char* name, + const char* help_text, + std::vector Options::*field, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + void AddOption( + const char* name, + const char* help_text, + HostPort Options::*field, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + void AddOption( + const char* name, + const char* help_text, + NoOp no_op_tag, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + void AddOption( + const char* name, + const char* help_text, + V8Option v8_option_tag, + OptionEnvvarSettings env_setting = kDisallowedInEnvvar, + OptionNamespaces namespace_id = OptionNamespaces::kNoNamespace); + + // Adds aliases. An alias can be of the form "--option-a" -> "--option-b", + // or have a more complex group expansion, like + // "--option-a" -> { "--option-b", "--harmony-foobar", "--eval", "42" } + // If `from` has the form "--option-a=", the alias will only be expanded if + // the option is presented in that form (i.e. with a '='). + // If `from` has the form "--option-a ", the alias will only be expanded + // if the option has a non-option argument (not starting with -) following it. + void AddAlias(const char* from, const char* to); + void AddAlias(const char* from, const std::vector& to); + void AddAlias(const char* from, const std::initializer_list& to); + + // Add implications from some arbitrary option to a boolean one, either + // in a way that makes `from` set `to` to true or to false. + void Implies(const char* from, const char* to); + void ImpliesNot(const char* from, const char* to); + + // Insert options from another options parser into this one, along with + // a method that yields the target options type from this parser's options + // type. + template + void Insert(const OptionsParser& child_options_parser, + ChildOptions* (Options::*get_child)()); + + // Parse a sequence of options into an options struct, a list of + // arguments that were parsed as options, a list of unknown/JS engine options, + // and leave the remainder in the input `args` vector. + // + // For example, an `args` input of + // + // node --foo --harmony-bar --fizzle=42 -- /path/to/cow moo + // + // expands as + // + // - `args` -> { "node", "/path/to/cow", "moo" } + // - `exec_args` -> { "--foo", "--harmony-bar", "--fizzle=42" } + // - `v8_args` -> `{ "node", "--harmony-bar" } + // - `options->foo == true`, `options->fizzle == 42`. + // + // If `*error` is set, the result of the parsing should be discarded and the + // contents of any of the argument vectors should be considered undefined. + void Parse(std::vector* const args, + std::vector* const exec_args, + std::vector* const v8_args, + Options* const options, + OptionEnvvarSettings required_env_settings, + std::vector* const errors) const; + + private: + // We support the wide variety of different option types by remembering + // how to access them, given a certain `Options` struct. + + // Represents a field within `Options`. + class BaseOptionField { + public: + virtual ~BaseOptionField() = default; + virtual void* LookupImpl(Options* options) const = 0; + + // For boolean bit-field options, we can't return a pointer so we + // use virtual getter/setter methods instead. The default + // implementations go through LookupImpl for non-bit-field types. + virtual bool GetBool(Options* options) const { + return *static_cast(LookupImpl(options)); + } + virtual void SetBool(Options* options, bool value) { + *static_cast(LookupImpl(options)) = value; + } + + template + inline T* Lookup(Options* options) const { + return static_cast(LookupImpl(options)); + } + }; + + // Represents a field of type T within `Options` that can be looked up + // as a C++ member field. + template + class SimpleOptionField : public BaseOptionField { + public: + explicit SimpleOptionField(T Options::* field) : field_(field) {} + void* LookupImpl(Options* options) const override { + return static_cast(&(options->*field_)); + } + + private: + T Options::* field_; + }; + + // Represents a boolean bit-field within `Options`. Bit-fields cannot + // have their address taken, so we use getter/setter function pointers + // instead of a pointer-to-member. + class BitFieldOptionField : public BaseOptionField { + public: + using Getter = bool (*)(Options*); + using Setter = void (*)(Options*, bool); + + BitFieldOptionField(Getter get, Setter set) : get_(get), set_(set) {} + + void* LookupImpl(Options* options) const override { UNREACHABLE(); } + + bool GetBool(Options* options) const override { return get_(options); } + void SetBool(Options* options, bool value) override { + set_(options, value); + } + + private: + Getter get_; + Setter set_; + }; + + template + inline T* Lookup(std::shared_ptr field, + Options* options) const { + return field->template Lookup(options); + } + + // An option consists of: + // - A type. + // - A way to store/access the property value. + // - The information of whether it may occur in an env var or not. + // - A default value (if applicable). + // - A namespace ID (optional) to allow for namespacing of options. + struct OptionInfo { + OptionType type; + std::shared_ptr field; + OptionEnvvarSettings env_setting; + std::string help_text; + bool default_is_true = false; + std::string namespace_id; + bool strict = false; + }; + + // An implied option is composed of the information on where to store a + // specific boolean value (if another specific option is encountered). + struct Implication { + OptionType type; + std::string name; + std::shared_ptr target_field; + bool target_value; + }; + + // These are helpers that make `Insert()` support properties of other + // options structs, if we know how to access them. + template + static auto Convert(std::shared_ptr original, + ChildOptions* (Options::*get_child)()); + template + static auto Convert(typename OptionsParser::OptionInfo original, + ChildOptions* (Options::*get_child)()); + template + static auto Convert( + typename OptionsParser::Implication original, + ChildOptions* (Options::*get_child)()); + + std::unordered_map options_; + std::unordered_map> aliases_; + std::unordered_multimap implications_; + + template + friend class OptionsParser; + + friend void GetCLIOptionsValues( + const v8::FunctionCallbackInfo& args); + friend void GetCLIOptionsInfo( + const v8::FunctionCallbackInfo& args); + friend std::string GetBashCompletion(); + friend std::unordered_map + MapEnvOptionsFlagInputType(); + friend std::unordered_map + MapOptionsByNamespace(std::string namespace_name); + friend std::vector MapAvailableNamespaces(); + friend void GetEnvOptionsInputType( + const v8::FunctionCallbackInfo& args); + friend void GetOptionsAsFlags( + const v8::FunctionCallbackInfo& args); +}; + +using StringVector = std::vector; +template +void Parse(StringVector* const args, + StringVector* const exec_args, + StringVector* const v8_args, + OptionsType* const options, + OptionEnvvarSettings required_env_settings, + StringVector* const errors); + +} // namespace options_parser + +// Helper macro for passing bit-field members to AddOption(). Expands to a +// getter and setter function pointer pair that can read/write the named +// bit-field without requiring a pointer-to-member (which is illegal for +// bit-fields). +#define BOOL_FIELD(field) \ + +[](TargetType* opts) -> bool { return opts->field; }, \ + +[](TargetType* opts, bool v) { opts->field = v; } + +namespace per_process { + +extern Mutex cli_options_mutex; +extern NODE_EXTERN_PRIVATE std::shared_ptr cli_options; + +} // namespace per_process + +void HandleEnvOptions(std::shared_ptr env_options); +void HandleEnvOptions(std::shared_ptr env_options, + std::function opt_getter); + +std::vector ParseNodeOptionsEnvVar( + const std::string& node_options, std::vector* errors); + +#undef DEFINE_BOOL_FIELD +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_OPTIONS_H_ diff --git a/test/parallel/test-test-runner-inspect-depth.js b/test/parallel/test-test-runner-inspect-depth.js new file mode 100644 index 000000000000..36693a2a8e43 --- /dev/null +++ b/test/parallel/test-test-runner-inspect-depth.js @@ -0,0 +1,49 @@ +'use strict'; + +// This test verifies that the --test-inspect-depth CLI option controls +// the depth of util.inspect() used when formatting errors in test reporters. + +const { spawnSync } = require('node:child_process'); +const { assertIncludes } = require('../common'); +const { writeFileSync, mkdtempSync } = require('node:fs'); +const { tmpdir } = require('node:os'); +const { join } = require('node:path'); + +const tmp = mkdtempSync(join(tmpdir(), 'node-test-inspect-depth-')); + +// Create a test file that throws an error with a deeply nested object. +const testFile = join(tmp, 'test.js'); +writeFileSync(testFile, ` +const test = require('node:test'); + +test('nested error', () => { + const err = new Error('fail'); + err.data = { level1: { level2: { level3: { level4: { value: 42 } } } } }; + throw err; +}); +`); + +// Run without --test-inspect-depth (default depth = 2, level3+ should be [Object]) +{ + const result = spawnSync(process.execPath, ['--test', testFile], { + encoding: 'utf8', + }); + assertIncludes(result.stderr + result.stdout, '[Object]'); +} + +// Run with --test-inspect-depth=10 (all levels should be expanded) +{ + const result = spawnSync(process.execPath, ['--test', '--test-inspect-depth=10', testFile], { + encoding: 'utf8', + }); + assertIncludes(result.stderr + result.stdout, 'level4'); + assertIncludes(result.stderr + result.stdout, 'value: 42'); +} + +// Run with --test-inspect-depth=0 (no expansion, all objects [Object]) +{ + const result = spawnSync(process.execPath, ['--test', '--test-inspect-depth=0', testFile], { + encoding: 'utf8', + }); + assertIncludes(result.stderr + result.stdout, '[Object]'); +}