From b8299585bcad00f9539fc32303cff7580204cdd3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 23 Aug 2018 16:41:05 +0200 Subject: [PATCH] src: make CLI options programatically accesible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide `internalBinding('options')` with some utilities around making the options parser and current options values programatically accessible. Backport-PR-URL: https://github.com/nodejs/node/pull/22644 PR-URL: https://github.com/nodejs/node/pull/22490 Reviewed-By: Michaƫl Zasso --- src/env.h | 4 + src/node.cc | 26 +-- src/node_internals.h | 2 + src/node_options-inl.h | 90 ++++++----- src/node_options.cc | 357 +++++++++++++++++++++++++++++++++++------ src/node_options.h | 20 +++ src/util-inl.h | 59 ++++++- src/util.cc | 11 +- src/util.h | 12 +- 9 files changed, 478 insertions(+), 103 deletions(-) diff --git a/src/env.h b/src/env.h index 497ae18a47f919..a0d9f714310515 100644 --- a/src/env.h +++ b/src/env.h @@ -118,6 +118,7 @@ struct PackageConfig { // for the sake of convenience. Strings should be ASCII-only. #define PER_ISOLATE_STRING_PROPERTIES(V) \ V(address_string, "address") \ + V(aliases_string, "aliases") \ V(args_string, "args") \ V(async, "async") \ V(async_ids_stack_string, "async_ids_stack") \ @@ -156,6 +157,7 @@ struct PackageConfig { V(entries_string, "entries") \ V(entry_type_string, "entryType") \ V(env_pairs_string, "envPairs") \ + V(env_var_settings_string, "envVarSettings") \ V(errno_string, "errno") \ V(error_string, "error") \ V(exit_code_string, "exitCode") \ @@ -176,6 +178,7 @@ struct PackageConfig { V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \ V(gid_string, "gid") \ V(handle_string, "handle") \ + V(help_text_string, "helpText") \ V(homedir_string, "homedir") \ V(host_string, "host") \ V(hostmaster_string, "hostmaster") \ @@ -233,6 +236,7 @@ struct PackageConfig { V(onunpipe_string, "onunpipe") \ V(onwrite_string, "onwrite") \ V(openssl_error_stack, "opensslErrorStack") \ + V(options_string, "options") \ V(output_string, "output") \ V(order_string, "order") \ V(parse_error_string, "Parse Error") \ diff --git a/src/node.cc b/src/node.cc index c0883bfaa3252f..0edca9760d80f4 100644 --- a/src/node.cc +++ b/src/node.cc @@ -126,6 +126,8 @@ typedef int mode_t; namespace node { +using options_parser::kAllowedInEnvironment; +using options_parser::kDisallowedInEnvironment; using v8::Array; using v8::ArrayBuffer; using v8::Boolean; @@ -183,6 +185,7 @@ bool linux_at_secure = false; // process-relative uptime base, initialized at start-up double prog_start_time; +Mutex per_process_opts_mutex; std::shared_ptr per_process_opts { new PerProcessOptions() }; @@ -2346,8 +2349,6 @@ void LoadEnvironment(Environment* env) { } static void PrintHelp() { - // XXX: If you add an option here, please also add it to doc/node.1 and - // doc/api/cli.md printf("Usage: node [options] [ -e script | script.js | - ] [arguments]\n" " node inspect script.js [arguments]\n" "\n" @@ -2747,13 +2748,20 @@ void ProcessArgv(std::vector* args, // Parse a few arguments which are specific to Node. std::vector v8_args; std::string error; - PerProcessOptionsParser::instance.Parse( - args, - exec_args, - &v8_args, - per_process_opts.get(), - is_env ? kAllowedInEnvironment : kDisallowedInEnvironment, - &error); + + { + // TODO(addaleax): The mutex here should ideally be held during the + // entire function, but that doesn't play well with the exit() calls below. + Mutex::ScopedLock lock(per_process_opts_mutex); + options_parser::PerProcessOptionsParser::instance.Parse( + args, + exec_args, + &v8_args, + per_process_opts.get(), + is_env ? kAllowedInEnvironment : kDisallowedInEnvironment, + &error); + } + if (!error.empty()) { fprintf(stderr, "%s: %s\n", args->at(0).c_str(), error.c_str()); exit(9); diff --git a/src/node_internals.h b/src/node_internals.h index b8ab492c44a675..b643de78ecb423 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -119,6 +119,7 @@ struct sockaddr; V(js_stream) \ V(messaging) \ V(module_wrap) \ + V(options) \ V(os) \ V(performance) \ V(pipe_wrap) \ @@ -176,6 +177,7 @@ extern Mutex environ_mutex; // Tells whether it is safe to call v8::Isolate::GetCurrent(). extern bool v8_initialized; +extern Mutex per_process_opts_mutex; extern std::shared_ptr per_process_opts; extern const char* const environment_flags[]; diff --git a/src/node_options-inl.h b/src/node_options-inl.h index e610cd50d11436..ba18e7c19b03c7 100644 --- a/src/node_options-inl.h +++ b/src/node_options-inl.h @@ -21,73 +21,88 @@ EnvironmentOptions* PerIsolateOptions::get_per_env_options() { return per_env.get(); } +namespace options_parser { + template void OptionsParser::AddOption(const std::string& name, + const std::string& help_text, bool Options::* field, OptionEnvvarSettings env_setting) { - options_.emplace(name, OptionInfo { - kBoolean, - std::make_shared>(field), - env_setting - }); + options_.emplace(name, + OptionInfo{kBoolean, + std::make_shared>(field), + env_setting, + help_text}); } template void OptionsParser::AddOption(const std::string& name, + const std::string& help_text, int64_t Options::* field, OptionEnvvarSettings env_setting) { - options_.emplace(name, OptionInfo { - kInteger, - std::make_shared>(field), - env_setting - }); + options_.emplace( + name, + OptionInfo{kInteger, + std::make_shared>(field), + env_setting, + help_text}); } template void OptionsParser::AddOption(const std::string& name, + const std::string& help_text, std::string Options::* field, OptionEnvvarSettings env_setting) { - options_.emplace(name, OptionInfo { - kString, - std::make_shared>(field), - env_setting - }); + options_.emplace( + name, + OptionInfo{kString, + std::make_shared>(field), + env_setting, + help_text}); } template void OptionsParser::AddOption( const std::string& name, + const std::string& help_text, std::vector Options::* field, OptionEnvvarSettings env_setting) { options_.emplace(name, OptionInfo { kStringList, std::make_shared>>(field), - env_setting + env_setting, + help_text }); } template void OptionsParser::AddOption(const std::string& name, + const std::string& help_text, HostPort Options::* field, OptionEnvvarSettings env_setting) { - options_.emplace(name, OptionInfo { - kHostPort, - std::make_shared>(field), - env_setting - }); + options_.emplace( + name, + OptionInfo{kHostPort, + std::make_shared>(field), + env_setting, + help_text}); } template -void OptionsParser::AddOption(const std::string& name, NoOp no_op_tag, +void OptionsParser::AddOption(const std::string& name, + const std::string& help_text, + NoOp no_op_tag, OptionEnvvarSettings env_setting) { - options_.emplace(name, OptionInfo { kNoOp, nullptr, env_setting }); + options_.emplace(name, OptionInfo{kNoOp, nullptr, env_setting, help_text}); } template void OptionsParser::AddOption(const std::string& name, + const std::string& help_text, V8Option v8_option_tag, OptionEnvvarSettings env_setting) { - options_.emplace(name, OptionInfo { kV8Option, nullptr, env_setting }); + options_.emplace(name, + OptionInfo{kV8Option, nullptr, env_setting, help_text}); } template @@ -161,11 +176,10 @@ template auto OptionsParser::Convert( typename OptionsParser::OptionInfo original, ChildOptions* (Options::* get_child)()) { - return OptionInfo { - original.type, - Convert(original.field, get_child), - original.env_setting - }; + return OptionInfo{original.type, + Convert(original.field, get_child), + original.env_setting, + original.help_text}; } template @@ -385,24 +399,21 @@ void OptionsParser::Parse( switch (info.type) { case kBoolean: - *std::static_pointer_cast>(info.field) - ->Lookup(options) = true; + *Lookup(info.field, options) = true; break; case kInteger: - *std::static_pointer_cast>(info.field) - ->Lookup(options) = std::atoll(value.c_str()); + *Lookup(info.field, options) = std::atoll(value.c_str()); break; case kString: - *std::static_pointer_cast>(info.field) - ->Lookup(options) = value; + *Lookup(info.field, options) = value; break; case kStringList: - std::static_pointer_cast>>( - info.field)->Lookup(options)->emplace_back(std::move(value)); + Lookup>(info.field, options) + ->emplace_back(std::move(value)); break; case kHostPort: - std::static_pointer_cast>(info.field) - ->Lookup(options)->Update(SplitHostPort(value, error)); + Lookup(info.field, options) + ->Update(SplitHostPort(value, error)); break; case kNoOp: break; @@ -415,6 +426,7 @@ void OptionsParser::Parse( } } +} // namespace options_parser } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node_options.cc b/src/node_options.cc index 78998fbea4cbd9..2897e69ac16c50 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1,30 +1,55 @@ -#include "node_options-inl.h" #include +#include "node_internals.h" +#include "node_options-inl.h" + +using v8::Boolean; +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Map; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Undefined; +using v8::Value; namespace node { +namespace options_parser { + +// 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", &DebugOptions::host_port, + AddOption("--inspect-port", + "set host:port for inspector", + &DebugOptions::host_port, kAllowedInEnvironment); AddAlias("--debug-port", "--inspect-port"); - AddOption("--inspect", &DebugOptions::inspector_enabled, + AddOption("--inspect", + "activate inspector on host:port (default: 127.0.0.1:9229)", + &DebugOptions::inspector_enabled, kAllowedInEnvironment); AddAlias("--inspect=", { "--inspect-port", "--inspect" }); - AddOption("--debug", &DebugOptions::deprecated_debug); + AddOption("--debug", "", &DebugOptions::deprecated_debug); AddAlias("--debug=", { "--inspect-port", "--debug" }); - AddOption("--inspect-brk", &DebugOptions::break_first_line, + AddOption("--inspect-brk", + "activate inspector on host:port and break at start of user script", + &DebugOptions::break_first_line, kAllowedInEnvironment); Implies("--inspect-brk", "--inspect"); AddAlias("--inspect-brk=", { "--inspect-port", "--inspect-brk" }); - AddOption("--inspect-brk-node", &DebugOptions::break_node_first_line); + AddOption("--inspect-brk-node", "", &DebugOptions::break_node_first_line); Implies("--inspect-brk-node", "--inspect"); AddAlias("--inspect-brk-node=", { "--inspect-port", "--inspect-brk-node" }); - AddOption("--debug-brk", &DebugOptions::break_first_line); + AddOption("--debug-brk", "", &DebugOptions::break_first_line); Implies("--debug-brk", "--debug"); AddAlias("--debug-brk=", { "--inspect-port", "--debug-brk" }); } @@ -32,46 +57,80 @@ DebugOptionsParser::DebugOptionsParser() { DebugOptionsParser DebugOptionsParser::instance; EnvironmentOptionsParser::EnvironmentOptionsParser() { - AddOption("--experimental-modules", &EnvironmentOptions::experimental_modules, + AddOption("--experimental-modules", + "experimental ES Module support and caching modules", + &EnvironmentOptions::experimental_modules, kAllowedInEnvironment); AddOption("--experimental-repl-await", + "experimental await keyword support in REPL", &EnvironmentOptions::experimental_repl_await, kAllowedInEnvironment); AddOption("--experimental-vm-modules", + "experimental ES Module support in vm module", &EnvironmentOptions::experimental_vm_modules, kAllowedInEnvironment); - AddOption("--experimental-worker", &EnvironmentOptions::experimental_worker, + AddOption("--experimental-worker", + "experimental threaded Worker support", + &EnvironmentOptions::experimental_worker, kAllowedInEnvironment); - AddOption("--expose-internals", &EnvironmentOptions::expose_internals); + AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals); // TODO(addaleax): Remove this when adding -/_ canonicalization to the parser. AddAlias("--expose_internals", "--expose-internals"); - AddOption("--loader", &EnvironmentOptions::userland_loader, + AddOption("--loader", + "(with --experimental-modules) use the specified file as a " + "custom loader", + &EnvironmentOptions::userland_loader, kAllowedInEnvironment); - AddOption("--no-deprecation", &EnvironmentOptions::no_deprecation, + AddOption("--no-deprecation", + "silence deprecation warnings", + &EnvironmentOptions::no_deprecation, kAllowedInEnvironment); AddOption("--no-force-async-hooks-checks", + "disable checks for async_hooks", &EnvironmentOptions::no_force_async_hooks_checks, kAllowedInEnvironment); - AddOption("--no-warnings", &EnvironmentOptions::no_warnings, + AddOption("--no-warnings", + "silence all process warnings", + &EnvironmentOptions::no_warnings, kAllowedInEnvironment); - AddOption("--pending-deprecation", &EnvironmentOptions::pending_deprecation, + AddOption("--pending-deprecation", + "emit pending deprecation warnings", + &EnvironmentOptions::pending_deprecation, kAllowedInEnvironment); - AddOption("--preserve-symlinks", &EnvironmentOptions::preserve_symlinks); + AddOption("--preserve-symlinks", + "preserve symbolic links when resolving", + &EnvironmentOptions::preserve_symlinks); AddOption("--preserve-symlinks-main", + "preserve symbolic links when resolving the main module", &EnvironmentOptions::preserve_symlinks_main); - AddOption("--prof-process", &EnvironmentOptions::prof_process); - AddOption("--redirect-warnings", &EnvironmentOptions::redirect_warnings, + AddOption("--prof-process", + "process V8 profiler output generated using --prof", + &EnvironmentOptions::prof_process); + AddOption("--redirect-warnings", + "write warnings to file instead of stderr", + &EnvironmentOptions::redirect_warnings, kAllowedInEnvironment); - AddOption("--throw-deprecation", &EnvironmentOptions::throw_deprecation, + AddOption("--throw-deprecation", + "throw an exception on deprecations", + &EnvironmentOptions::throw_deprecation, kAllowedInEnvironment); - AddOption("--trace-deprecation", &EnvironmentOptions::trace_deprecation, + AddOption("--trace-deprecation", + "show stack traces on deprecations", + &EnvironmentOptions::trace_deprecation, kAllowedInEnvironment); - AddOption("--trace-sync-io", &EnvironmentOptions::trace_sync_io, + AddOption("--trace-sync-io", + "show stack trace when use of sync IO is detected after the " + "first tick", + &EnvironmentOptions::trace_sync_io, kAllowedInEnvironment); - AddOption("--trace-warnings", &EnvironmentOptions::trace_warnings, + AddOption("--trace-warnings", + "show stack traces on process warnings", + &EnvironmentOptions::trace_warnings, kAllowedInEnvironment); - AddOption("--check", &EnvironmentOptions::syntax_check_only); + AddOption("--check", + "syntax check script without executing", + &EnvironmentOptions::syntax_check_only); AddAlias("-c", "--check"); // This option is only so that we can tell --eval with an empty string from // no eval at all. Having it not start with a dash makes it inaccessible @@ -79,23 +138,30 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { // TODO(addaleax): When moving --help over to something generated from the // programmatic descriptions, this will need some special care. // (See also [ssl_openssl_cert_store] below.) - AddOption("[has_eval_string]", &EnvironmentOptions::has_eval_string); - AddOption("--eval", &EnvironmentOptions::eval_string); + AddOption("[has_eval_string]", "", &EnvironmentOptions::has_eval_string); + AddOption("--eval", "evaluate script", &EnvironmentOptions::eval_string); Implies("--eval", "[has_eval_string]"); - AddOption("--print", &EnvironmentOptions::print_eval); + AddOption("--print", + "evaluate script and print result", + &EnvironmentOptions::print_eval); AddAlias("-e", "--eval"); AddAlias("--print ", "-pe"); AddAlias("-pe", { "--print", "--eval" }); AddAlias("-p", "--print"); - AddOption("--require", &EnvironmentOptions::preload_modules, + AddOption("--require", + "module to preload (option can be repeated)", + &EnvironmentOptions::preload_modules, kAllowedInEnvironment); AddAlias("-r", "--require"); - AddOption("--interactive", &EnvironmentOptions::force_repl); + AddOption("--interactive", + "always enter the REPL even if stdin does not appear " + "to be a terminal", + &EnvironmentOptions::force_repl); AddAlias("-i", "--interactive"); - AddOption("--napi-modules", NoOp {}, kAllowedInEnvironment); - AddOption("--expose-http2", NoOp {}, kAllowedInEnvironment); - AddOption("--expose_http2", NoOp {}, kAllowedInEnvironment); + AddOption("--napi-modules", "", NoOp {}, kAllowedInEnvironment); + AddOption("--expose-http2", "", NoOp {}, kAllowedInEnvironment); + AddOption("--expose_http2", "", NoOp {}, kAllowedInEnvironment); Insert(&DebugOptionsParser::instance, &EnvironmentOptions::get_debug_options); @@ -104,16 +170,21 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { EnvironmentOptionsParser EnvironmentOptionsParser::instance; PerIsolateOptionsParser::PerIsolateOptionsParser() { - AddOption("--track-heap-objects", &PerIsolateOptions::track_heap_objects, + AddOption("--track-heap-objects", + "track heap object allocations for heap snapshots", + &PerIsolateOptions::track_heap_objects, kAllowedInEnvironment); // Explicitly add some V8 flags to mark them as allowed in NODE_OPTIONS. - AddOption("--abort_on_uncaught_exception", V8Option {}, + AddOption("--abort_on_uncaught_exception", + "aborting instead of exiting causes a core file to be generated " + "for analysis", + V8Option{}, kAllowedInEnvironment); - AddOption("--max_old_space_size", V8Option {}, kAllowedInEnvironment); - AddOption("--perf_basic_prof", V8Option {}, kAllowedInEnvironment); - AddOption("--perf_prof", V8Option {}, kAllowedInEnvironment); - AddOption("--stack_trace_limit", V8Option {}, kAllowedInEnvironment); + AddOption("--max_old_space_size", "", V8Option{}, kAllowedInEnvironment); + AddOption("--perf_basic_prof", "", V8Option{}, kAllowedInEnvironment); + AddOption("--perf_prof", "", V8Option{}, kAllowedInEnvironment); + AddOption("--stack_trace_limit", "", V8Option{}, kAllowedInEnvironment); Insert(&EnvironmentOptionsParser::instance, &PerIsolateOptions::get_per_env_options); @@ -122,52 +193,89 @@ PerIsolateOptionsParser::PerIsolateOptionsParser() { PerIsolateOptionsParser PerIsolateOptionsParser::instance; PerProcessOptionsParser::PerProcessOptionsParser() { - AddOption("--title", &PerProcessOptions::title, kAllowedInEnvironment); + AddOption("--title", + "the process title to use on startup", + &PerProcessOptions::title, + kAllowedInEnvironment); AddOption("--trace-event-categories", + "comma separated list of trace event categories to record", &PerProcessOptions::trace_event_categories, kAllowedInEnvironment); AddOption("--trace-event-file-pattern", + "Template string specifying the filepath for the trace-events " + "data, it supports ${rotation} and ${pid} log-rotation id. %2$u " + "is the pid.", &PerProcessOptions::trace_event_file_pattern, kAllowedInEnvironment); AddAlias("--trace-events-enabled", { "--trace-event-categories", "v8,node,node.async_hooks" }); - AddOption("--v8-pool-size", &PerProcessOptions::v8_thread_pool_size, + AddOption("--v8-pool-size", + "set V8's thread pool size", + &PerProcessOptions::v8_thread_pool_size, kAllowedInEnvironment); - AddOption("--zero-fill-buffers", &PerProcessOptions::zero_fill_all_buffers, + AddOption("--zero-fill-buffers", + "automatically zero-fill all newly allocated Buffer and " + "SlowBuffer instances", + &PerProcessOptions::zero_fill_all_buffers, kAllowedInEnvironment); - AddOption("--security-reverts", &PerProcessOptions::security_reverts); - AddOption("--help", &PerProcessOptions::print_help); + AddOption("--security-reverts", "", &PerProcessOptions::security_reverts); + AddOption("--help", + "print node command line options", + &PerProcessOptions::print_help); AddAlias("-h", "--help"); - AddOption("--version", &PerProcessOptions::print_version); + AddOption( + "--version", "print Node.js version", &PerProcessOptions::print_version); AddAlias("-v", "--version"); - AddOption("--v8-options", &PerProcessOptions::print_v8_help); + AddOption("--v8-options", + "print V8 command line options", + &PerProcessOptions::print_v8_help); #ifdef NODE_HAVE_I18N_SUPPORT - AddOption("--icu-data-dir", &PerProcessOptions::icu_data_dir, + 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)\n" +#endif + , + &PerProcessOptions::icu_data_dir, kAllowedInEnvironment); #endif #if HAVE_OPENSSL - AddOption("--openssl-config", &PerProcessOptions::openssl_config, + AddOption("--openssl-config", + "load OpenSSL configuration from the specified file " + "(overrides OPENSSL_CONF)", + &PerProcessOptions::openssl_config, kAllowedInEnvironment); - AddOption("--tls-cipher-list", &PerProcessOptions::tls_cipher_list, + AddOption("--tls-cipher-list", + "use an alternative default TLS cipher list", + &PerProcessOptions::tls_cipher_list, kAllowedInEnvironment); - AddOption("--use-openssl-ca", &PerProcessOptions::use_openssl_ca, + AddOption("--use-openssl-ca", + "use OpenSSL's default CA store", + &PerProcessOptions::use_openssl_ca, kAllowedInEnvironment); - AddOption("--use-bundled-ca", &PerProcessOptions::use_bundled_ca, + AddOption("--use-bundled-ca", + "use bundled CA store", + &PerProcessOptions::use_bundled_ca, kAllowedInEnvironment); // Similar to [has_eval_string] above, except that the separation between // this and use_openssl_ca only exists for option validation after parsing. // This is not ideal. AddOption("[ssl_openssl_cert_store]", + "", &PerProcessOptions::ssl_openssl_cert_store); Implies("--use-openssl-ca", "[ssl_openssl_cert_store]"); ImpliesNot("--use-bundled-ca", "[ssl_openssl_cert_store]"); #if NODE_FIPS_MODE - AddOption("--enable-fips", &PerProcessOptions::enable_fips_crypto, + AddOption("--enable-fips", + "enable FIPS crypto at startup", + &PerProcessOptions::enable_fips_crypto, kAllowedInEnvironment); - AddOption("--force-fips", &PerProcessOptions::force_fips_crypto, + AddOption("--force-fips", + "force FIPS crypto (cannot be disabled)", + &PerProcessOptions::force_fips_crypto, kAllowedInEnvironment); #endif #endif @@ -218,4 +326,153 @@ HostPort SplitHostPort(const std::string& arg, std::string* error) { return HostPort { RemoveBrackets(arg.substr(0, colon)), ParseAndValidatePort(arg.substr(colon + 1), error) }; } + +// Usage: Either: +// - getOptions() to get all options + metadata or +// - getOptions(string) to get the value of a particular option +void GetOptions(const FunctionCallbackInfo& args) { + Mutex::ScopedLock lock(per_process_opts_mutex); + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + Local context = env->context(); + + // 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. + auto original_per_isolate = per_process_opts->per_isolate; + per_process_opts->per_isolate = env->isolate_data()->options(); + auto original_per_env = per_process_opts->per_isolate->per_env; + per_process_opts->per_isolate->per_env = env->options(); + OnScopeLeave on_scope_leave([&]() { + per_process_opts->per_isolate->per_env = original_per_env; + per_process_opts->per_isolate = original_per_isolate; + }); + + const auto& parser = PerProcessOptionsParser::instance; + + std::string filter; + if (args[0]->IsString()) filter = *node::Utf8Value(isolate, args[0]); + + Local options = Map::New(isolate); + for (const auto& item : parser.options_) { + if (!filter.empty() && item.first != filter) continue; + + Local value; + const auto& option_info = item.second; + auto field = option_info.field; + PerProcessOptions* opts = per_process_opts.get(); + switch (option_info.type) { + case kNoOp: + case kV8Option: + value = Undefined(isolate); + break; + case kBoolean: + value = Boolean::New(isolate, *parser.Lookup(field, opts)); + break; + case kInteger: + value = Number::New(isolate, *parser.Lookup(field, opts)); + break; + case kString: + if (!ToV8Value(context, *parser.Lookup(field, opts)) + .ToLocal(&value)) { + return; + } + break; + case kStringList: + if (!ToV8Value(context, + *parser.Lookup>(field, opts)) + .ToLocal(&value)) { + return; + } + break; + case kHostPort: { + const HostPort& host_port = *parser.Lookup(field, opts); + Local obj = Object::New(isolate); + Local host; + if (!ToV8Value(context, host_port.host_name).ToLocal(&host) || + obj->Set(context, env->host_string(), host).IsNothing() || + obj->Set(context, + env->port_string(), + Integer::New(isolate, host_port.port)) + .IsNothing()) { + return; + } + value = obj; + break; + } + default: + UNREACHABLE(); + } + CHECK(!value.IsEmpty()); + + if (!filter.empty()) { + args.GetReturnValue().Set(value); + return; + } + + Local name = ToV8Value(context, item.first).ToLocalChecked(); + Local info = Object::New(isolate); + Local help_text; + if (!ToV8Value(context, option_info.help_text).ToLocal(&help_text) || + !info->Set(context, env->help_text_string(), help_text) + .FromMaybe(false) || + !info->Set(context, + env->env_var_settings_string(), + Integer::New(isolate, + static_cast(option_info.env_setting))) + .FromMaybe(false) || + !info->Set(context, + env->type_string(), + Integer::New(isolate, static_cast(option_info.type))) + .FromMaybe(false) || + info->Set(context, env->value_string(), value).IsNothing() || + options->Set(context, name, info).IsEmpty()) { + return; + } + } + + if (!filter.empty()) return; + + Local aliases; + if (!ToV8Value(context, parser.aliases_).ToLocal(&aliases)) return; + + Local ret = Object::New(isolate); + if (ret->Set(context, env->options_string(), options).IsNothing() || + ret->Set(context, env->aliases_string(), aliases).IsNothing()) { + return; + } + + args.GetReturnValue().Set(ret); +} + +void Initialize(Local target, + Local unused, + Local context) { + Environment* env = Environment::GetCurrent(context); + Isolate* isolate = env->isolate(); + env->SetMethodNoSideEffect(target, "getOptions", GetOptions); + + Local env_settings = Object::New(isolate); + NODE_DEFINE_CONSTANT(env_settings, kAllowedInEnvironment); + NODE_DEFINE_CONSTANT(env_settings, kDisallowedInEnvironment); + target + ->Set( + context, FIXED_ONE_BYTE_STRING(isolate, "envSettings"), env_settings) + .FromJust(); + + 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, kString); + NODE_DEFINE_CONSTANT(types, kHostPort); + NODE_DEFINE_CONSTANT(types, kStringList); + target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "types"), types) + .FromJust(); +} + +} // namespace options_parser } // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(options, node::options_parser::Initialize) diff --git a/src/node_options.h b/src/node_options.h index 957e2b729d9ff4..7891d550ae3dad 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -141,7 +141,10 @@ class PerProcessOptions { // The actual options parser, as opposed to the structs containing them: +namespace options_parser { + HostPort SplitHostPort(const std::string& arg, std::string* error); +void GetOptions(const v8::FunctionCallbackInfo& args); enum OptionEnvvarSettings { kAllowedInEnvironment, @@ -176,24 +179,31 @@ class OptionsParser { // specified whether the option should be allowed from environment variable // sources (i.e. NODE_OPTIONS). void AddOption(const std::string& name, + const std::string& help_text, bool Options::* field, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); void AddOption(const std::string& name, + const std::string& help_text, int64_t Options::* field, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); void AddOption(const std::string& name, + const std::string& help_text, std::string Options::* field, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); void AddOption(const std::string& name, + const std::string& help_text, std::vector Options::* field, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); void AddOption(const std::string& name, + const std::string& help_text, HostPort Options::* field, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); void AddOption(const std::string& name, + const std::string& help_text, NoOp no_op_tag, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); void AddOption(const std::string& name, + const std::string& help_text, V8Option v8_option_tag, OptionEnvvarSettings env_setting = kDisallowedInEnvironment); @@ -281,6 +291,12 @@ class OptionsParser { T Options::* field_; }; + template + inline T* Lookup(std::shared_ptr field, + Options* options) const { + return std::static_pointer_cast>(field)->Lookup(options); + } + // An option consists of: // - A type. // - A way to store/access the property value. @@ -289,6 +305,7 @@ class OptionsParser { OptionType type; std::shared_ptr field; OptionEnvvarSettings env_setting; + std::string help_text; }; // An implied option is composed of the information on where to store a @@ -319,6 +336,8 @@ class OptionsParser { template friend class OptionsParser; + + friend void GetOptions(const v8::FunctionCallbackInfo& args); }; class DebugOptionsParser : public OptionsParser { @@ -349,6 +368,7 @@ class PerProcessOptionsParser : public OptionsParser { static PerProcessOptionsParser instance; }; +} // namespace options_parser } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/util-inl.h b/src/util-inl.h index c6cd263aa2714a..bbee32615ff729 100644 --- a/src/util-inl.h +++ b/src/util-inl.h @@ -24,8 +24,9 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "util.h" +#include // INT_MAX #include +#include "util.h" #if defined(_MSC_VER) #include @@ -397,6 +398,62 @@ inline char* Calloc(size_t n) { return Calloc(n); } inline char* UncheckedMalloc(size_t n) { return UncheckedMalloc(n); } inline char* UncheckedCalloc(size_t n) { return UncheckedCalloc(n); } +// This is a helper in the .cc file so including util-inl.h doesn't include more +// headers than we really need to. +void ThrowErrStringTooLong(v8::Isolate* isolate); + +v8::MaybeLocal ToV8Value(v8::Local context, + const std::string& str) { + v8::Isolate* isolate = context->GetIsolate(); + if (UNLIKELY(str.size() >= static_cast(v8::String::kMaxLength))) { + // V8 only has a TODO comment about adding an exception when the maximum + // string size is exceeded. + ThrowErrStringTooLong(isolate); + return v8::MaybeLocal(); + } + + return v8::String::NewFromUtf8( + isolate, str.data(), v8::NewStringType::kNormal, str.size()) + .FromMaybe(v8::Local()); +} + +template +v8::MaybeLocal ToV8Value(v8::Local context, + const std::vector& vec) { + v8::Isolate* isolate = context->GetIsolate(); + v8::EscapableHandleScope handle_scope(isolate); + + v8::Local arr = v8::Array::New(isolate, vec.size()); + for (size_t i = 0; i < vec.size(); ++i) { + v8::Local val; + if (!ToV8Value(context, vec[i]).ToLocal(&val) || + arr->Set(context, i, val).IsNothing()) { + return v8::MaybeLocal(); + } + } + + return handle_scope.Escape(arr); +} + +template +v8::MaybeLocal ToV8Value(v8::Local context, + const std::unordered_map& map) { + v8::Isolate* isolate = context->GetIsolate(); + v8::EscapableHandleScope handle_scope(isolate); + + v8::Local ret = v8::Map::New(isolate); + for (const auto& item : map) { + v8::Local first, second; + if (!ToV8Value(context, item.first).ToLocal(&first) || + !ToV8Value(context, item.second).ToLocal(&second) || + ret->Set(context, first, second).IsEmpty()) { + return v8::MaybeLocal(); + } + } + + return handle_scope.Escape(ret); +} + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/util.cc b/src/util.cc index a9d26fe86ae2a6..a0f0b0bf89657d 100644 --- a/src/util.cc +++ b/src/util.cc @@ -19,12 +19,13 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "string_bytes.h" +#include +#include #include "node_buffer.h" +#include "node_errors.h" #include "node_internals.h" +#include "string_bytes.h" #include "uv.h" -#include -#include namespace node { @@ -133,4 +134,8 @@ std::set ParseCommaSeparatedSet(const std::string& in) { return out; } +void ThrowErrStringTooLong(v8::Isolate* isolate) { + isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); +} + } // namespace node diff --git a/src/util.h b/src/util.h index a9fce79ebeaec1..057346a2d04fca 100644 --- a/src/util.h +++ b/src/util.h @@ -34,9 +34,10 @@ #include #include -#include #include // std::function #include +#include +#include namespace node { @@ -479,6 +480,15 @@ using DeleteFnPtr = typename FunctionDeleter::Pointer; std::set ParseCommaSeparatedSet(const std::string& in); +inline v8::MaybeLocal ToV8Value(v8::Local context, + const std::string& str); +template +inline v8::MaybeLocal ToV8Value(v8::Local context, + const std::vector& vec); +template +inline v8::MaybeLocal ToV8Value(v8::Local context, + const std::unordered_map& map); + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS