diff --git a/node.gyp b/node.gyp index be885f6daaa3f2..f43c0f091c7c40 100644 --- a/node.gyp +++ b/node.gyp @@ -342,7 +342,6 @@ 'src/node_config.cc', 'src/node_constants.cc', 'src/node_contextify.cc', - 'src/node_debug_options.cc', 'src/node_domain.cc', 'src/node_encoding.cc', 'src/node_errors.h', @@ -350,6 +349,7 @@ 'src/node_http2.cc', 'src/node_http_parser.cc', 'src/node_messaging.cc', + 'src/node_options.cc', 'src/node_os.cc', 'src/node_platform.cc', 'src/node_perf.cc', @@ -406,7 +406,6 @@ 'src/node_code_cache.h', 'src/node_constants.h', 'src/node_contextify.h', - 'src/node_debug_options.h', 'src/node_file.h', 'src/node_http2.h', 'src/node_http2_state.h', @@ -414,6 +413,8 @@ 'src/node_javascript.h', 'src/node_messaging.h', 'src/node_mutex.h', + 'src/node_options.h', + 'src/node_options-inl.h', 'src/node_perf.h', 'src/node_perf_common.h', 'src/node_persistent.h', diff --git a/src/env-inl.h b/src/env-inl.h index cf9c60f948d613..afca7a80ce0339 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -559,6 +559,14 @@ Environment::file_handle_read_wrap_freelist() { return file_handle_read_wrap_freelist_; } +inline std::shared_ptr Environment::options() { + return options_; +} + +inline std::shared_ptr IsolateData::options() { + return options_; +} + void Environment::CreateImmediate(native_immediate_callback cb, void* data, v8::Local obj, diff --git a/src/env.cc b/src/env.cc index 0e9a8772fb2872..58971434856c6b 100644 --- a/src/env.cc +++ b/src/env.cc @@ -44,6 +44,8 @@ IsolateData::IsolateData(Isolate* isolate, if (platform_ != nullptr) platform_->RegisterIsolate(this, event_loop); + options_.reset(new PerIsolateOptions(*per_process_opts->per_isolate)); + // Create string and private symbol properties as internalized one byte // strings after the platform is properly initialized. // @@ -116,9 +118,6 @@ Environment::Environment(IsolateData* isolate_data, emit_env_nonstring_warning_(true), makecallback_cntr_(0), should_abort_on_uncaught_toggle_(isolate_, 1), -#if HAVE_INSPECTOR - inspector_agent_(new inspector::Agent(this)), -#endif http_parser_buffer_(nullptr), fs_stats_field_array_(isolate_, kFsStatsFieldsLength * 2), fs_stats_field_bigint_array_(isolate_, kFsStatsFieldsLength * 2), @@ -128,6 +127,19 @@ Environment::Environment(IsolateData* isolate_data, v8::Context::Scope context_scope(context); set_as_external(v8::External::New(isolate(), this)); + // We create new copies of the per-Environment option sets, so that it is + // easier to modify them after Environment creation. The defaults are + // part of the per-Isolate option set, for which in turn the defaults are + // part of the per-process option set. + options_.reset(new EnvironmentOptions(*isolate_data->options()->per_env)); + options_->debug_options.reset(new DebugOptions(*options_->debug_options)); + +#if HAVE_INSPECTOR + // We can only create the inspector agent after having cloned the options. + inspector_agent_ = + std::unique_ptr(new inspector::Agent(this)); +#endif + AssignToContext(context, ContextInfo("")); destroy_async_id_list_.reserve(512); @@ -176,10 +188,8 @@ Environment::~Environment() { delete[] http_parser_buffer_; } -void Environment::Start(int argc, - const char* const* argv, - int exec_argc, - const char* const* exec_argv, +void Environment::Start(const std::vector& args, + const std::vector& exec_args, bool start_profiler_idle_notifier) { HandleScope handle_scope(isolate()); Context::Scope context_scope(context()); @@ -222,7 +232,7 @@ void Environment::Start(int argc, process_template->GetFunction()->NewInstance(context()).ToLocalChecked(); set_process_object(process_object); - SetupProcessObject(this, argc, argv, exec_argc, exec_argv); + SetupProcessObject(this, args, exec_args); static uv_once_t init_once = UV_ONCE_INIT; uv_once(&init_once, InitThreadLocalOnce); diff --git a/src/env.h b/src/env.h index c6b5a7eeafa5f5..497ae18a47f919 100644 --- a/src/env.h +++ b/src/env.h @@ -34,6 +34,7 @@ #include "uv.h" #include "v8.h" #include "node.h" +#include "node_options.h" #include "node_http2_state.h" #include @@ -364,6 +365,7 @@ class IsolateData { inline uv_loop_t* event_loop() const; inline uint32_t* zero_fill_field() const; inline MultiIsolatePlatform* platform() const; + inline std::shared_ptr options(); #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) @@ -397,6 +399,7 @@ class IsolateData { uv_loop_t* const event_loop_; uint32_t* const zero_fill_field_; MultiIsolatePlatform* platform_; + std::shared_ptr options_; DISALLOW_COPY_AND_ASSIGN(IsolateData); }; @@ -584,10 +587,8 @@ class Environment { tracing::AgentWriterHandle* tracing_agent_writer); ~Environment(); - void Start(int argc, - const char* const* argv, - int exec_argc, - const char* const* exec_argv, + void Start(const std::vector& args, + const std::vector& exec_args, bool start_profiler_idle_notifier); typedef void (*HandleCleanupCb)(Environment* env, @@ -858,6 +859,8 @@ class Environment { v8::EmbedderGraph* graph, void* data); + inline std::shared_ptr options(); + private: inline void CreateImmediate(native_immediate_callback cb, void* data, @@ -887,6 +890,8 @@ class Environment { size_t makecallback_cntr_; std::vector destroy_async_id_list_; + std::shared_ptr options_; + AliasedBuffer should_abort_on_uncaught_toggle_; int should_not_abort_scope_counter_ = 0; diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 0b99217d0a3fa8..adc32f1bcd4c19 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -608,11 +608,14 @@ class NodeInspectorClient : public V8InspectorClient { std::unique_ptr interface_; }; -Agent::Agent(Environment* env) : parent_env_(env) {} +Agent::Agent(Environment* env) + : parent_env_(env), + debug_options_(env->options()->debug_options) {} Agent::~Agent() = default; -bool Agent::Start(const std::string& path, const DebugOptions& options) { +bool Agent::Start(const std::string& path, + std::shared_ptr options) { path_ = path; debug_options_ = options; client_ = std::make_shared(parent_env_); @@ -626,8 +629,8 @@ bool Agent::Start(const std::string& path, const DebugOptions& options) { StartDebugSignalHandler(); } - bool wait_for_connect = options.wait_for_connect(); - if (!options.inspector_enabled() || !StartIoThread()) { + bool wait_for_connect = options->wait_for_connect(); + if (!options->inspector_enabled || !StartIoThread()) { return false; } if (wait_for_connect) { @@ -789,7 +792,7 @@ void Agent::ContextCreated(Local context, const ContextInfo& info) { } bool Agent::WillWaitForConnect() { - return debug_options_.wait_for_connect(); + return debug_options_->wait_for_connect(); } bool Agent::IsActive() { diff --git a/src/inspector_agent.h b/src/inspector_agent.h index dcd6e13aba275f..4e32d3ef1a96b4 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -9,7 +9,7 @@ #error("This header can only be used when inspector is enabled") #endif -#include "node_debug_options.h" +#include "node_options.h" #include "node_persistent.h" #include "v8.h" @@ -45,7 +45,7 @@ class Agent { ~Agent(); // Create client_, may create io_ if option enabled - bool Start(const std::string& path, const DebugOptions& options); + bool Start(const std::string& path, std::shared_ptr options); // Stop and destroy io_ void Stop(); @@ -96,7 +96,7 @@ class Agent { // Calls StartIoThread() from off the main thread. void RequestIoThreadStart(); - DebugOptions& options() { return debug_options_; } + std::shared_ptr options() { return debug_options_; } void ContextCreated(v8::Local context, const ContextInfo& info); private: @@ -109,7 +109,7 @@ class Agent { // Interface for transports, e.g. WebSocket server std::unique_ptr io_; std::string path_; - DebugOptions debug_options_; + std::shared_ptr debug_options_; bool pending_enable_async_hook_ = false; bool pending_disable_async_hook_ = false; diff --git a/src/inspector_io.cc b/src/inspector_io.cc index 41fea546a83265..da44d55d06e10a 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -242,7 +242,7 @@ class InspectorIoDelegate: public node::inspector::SocketServerDelegate { std::unique_ptr InspectorIo::Start( std::shared_ptr main_thread, const std::string& path, - const DebugOptions& options) { + std::shared_ptr options) { auto io = std::unique_ptr( new InspectorIo(main_thread, path, options)); if (io->request_queue_->Expired()) { // Thread is not running @@ -253,7 +253,7 @@ std::unique_ptr InspectorIo::Start( InspectorIo::InspectorIo(std::shared_ptr main_thread, const std::string& path, - const DebugOptions& options) + std::shared_ptr options) : main_thread_(main_thread), options_(options), thread_(), script_name_(path), id_(GenerateID()) { Mutex::ScopedLock scoped_lock(thread_start_lock_); @@ -288,7 +288,8 @@ void InspectorIo::ThreadMain() { new InspectorIoDelegate(queue, main_thread_, id_, script_path, script_name_)); InspectorSocketServer server(std::move(delegate), &loop, - options_.host_name(), options_.port()); + options_->host().c_str(), + options_->port()); request_queue_ = queue->handle(); // Its lifetime is now that of the server delegate queue.reset(); diff --git a/src/inspector_io.h b/src/inspector_io.h index 7c43d212f0422e..2b9f0acd48383d 100644 --- a/src/inspector_io.h +++ b/src/inspector_io.h @@ -2,7 +2,6 @@ #define SRC_INSPECTOR_IO_H_ #include "inspector_socket_server.h" -#include "node_debug_options.h" #include "node_mutex.h" #include "uv.h" @@ -46,19 +45,20 @@ class InspectorIo { // Returns empty pointer if thread was not started static std::unique_ptr Start( std::shared_ptr main_thread, const std::string& path, - const DebugOptions& options); + std::shared_ptr options); // Will block till the transport thread shuts down ~InspectorIo(); void StopAcceptingNewConnections(); - std::string host() const { return options_.host_name(); } + const std::string& host() const { return options_->host(); } int port() const { return port_; } std::vector GetTargetIds() const; private: InspectorIo(std::shared_ptr handle, - const std::string& path, const DebugOptions& options); + const std::string& path, + std::shared_ptr options); // Wrapper for agent->ThreadMain() static void ThreadMain(void* agent); @@ -72,7 +72,7 @@ class InspectorIo { // Used to post on a frontend interface thread, lives while the server is // running std::shared_ptr request_queue_; - const DebugOptions options_; + std::shared_ptr options_; // The IO thread runs its own uv_loop to implement the TCP server off // the main thread. diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 4e95598d3a0580..b8c78ba5ebd66d 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -242,12 +242,12 @@ void Open(const FunctionCallbackInfo& args) { if (args.Length() > 0 && args[0]->IsUint32()) { uint32_t port = args[0]->Uint32Value(); - agent->options().set_port(static_cast(port)); + agent->options()->host_port.port = port; } if (args.Length() > 1 && args[1]->IsString()) { Utf8Value host(env->isolate(), args[1].As()); - agent->options().set_host_name(*host); + agent->options()->host_port.host_name = *host; } if (args.Length() > 2 && args[2]->IsBoolean()) { diff --git a/src/node.cc b/src/node.cc index 847abaa6bbf5d6..97467d1649df16 100644 --- a/src/node.cc +++ b/src/node.cc @@ -27,7 +27,6 @@ #include "node_version.h" #include "node_internals.h" #include "node_revert.h" -#include "node_debug_options.h" #include "node_perf.h" #include "node_context_data.h" #include "tracing/traced_value.h" @@ -172,19 +171,6 @@ using v8::Value; static Mutex process_mutex; static Mutex environ_mutex; -static bool print_eval = false; -static bool force_repl = false; -static bool syntax_check_only = false; -static bool trace_deprecation = false; -static bool throw_deprecation = false; -static bool trace_sync_io = false; -static bool no_force_async_hooks_checks = false; -static bool track_heap_objects = false; -static const char* eval_string = nullptr; -static std::vector preload_modules; -static const int v8_default_thread_pool_size = 4; -static int v8_thread_pool_size = v8_default_thread_pool_size; -static bool prof_process = false; static bool v8_is_profiling = false; static bool node_is_initialized = false; static uv_once_t init_modpending_once = UV_ONCE_INIT; @@ -193,93 +179,13 @@ static node_module* modlist_builtin; static node_module* modlist_internal; static node_module* modlist_linked; static node_module* modlist_addon; -static std::string trace_enabled_categories; // NOLINT(runtime/string) -static std::string trace_file_pattern = // NOLINT(runtime/string) - "node_trace.${rotation}.log"; + +// TODO(addaleax): This should not be global. static bool abort_on_uncaught_exception = false; // Bit flag used to track security reverts (see node_revert.h) unsigned int reverted = 0; -#if defined(NODE_HAVE_I18N_SUPPORT) -// Path to ICU data (for i18n / Intl) -std::string icu_data_dir; // NOLINT(runtime/string) -#endif - -// used by C++ modules as well -bool no_deprecation = false; - -#if HAVE_OPENSSL -// use OpenSSL's cert store instead of bundled certs -bool ssl_openssl_cert_store = -#if defined(NODE_OPENSSL_CERT_STORE) - true; -#else - false; -#endif - -# if NODE_FIPS_MODE -// used by crypto module -bool enable_fips_crypto = false; -bool force_fips_crypto = false; -# endif // NODE_FIPS_MODE -std::string openssl_config; // NOLINT(runtime/string) -#endif // HAVE_OPENSSL - -// true if process warnings should be suppressed -bool no_process_warnings = false; -bool trace_warnings = false; - -// Set in node.cc by ParseArgs when --preserve-symlinks is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/module.js -bool config_preserve_symlinks = false; - -// Set in node.cc by ParseArgs when --preserve-symlinks-main is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/module.js -bool config_preserve_symlinks_main = false; - -// Set in node.cc by ParseArgs when --experimental-modules is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/module.js -bool config_experimental_modules = false; - -// Set in node.cc by ParseArgs when --experimental-vm-modules is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/vm.js -bool config_experimental_vm_modules = false; - -// Set in node.cc by ParseArgs when --experimental-worker is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/worker.js -bool config_experimental_worker = false; - -// Set in node.cc by ParseArgs when --experimental-repl-await is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/repl.js. -bool config_experimental_repl_await = false; - -// Set in node.cc by ParseArgs when --loader is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/internal/bootstrap/node.js -std::string config_userland_loader; // NOLINT(runtime/string) - -// Set by ParseArgs when --pending-deprecation or NODE_PENDING_DEPRECATION -// is used. -bool config_pending_deprecation = false; - -// Set in node.cc by ParseArgs when --redirect-warnings= is used. -std::string config_warning_file; // NOLINT(runtime/string) - -// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is -// used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/internal/bootstrap/node.js -bool config_expose_internals = false; - -std::string config_process_title; // NOLINT(runtime/string) - bool v8_initialized = false; bool linux_at_secure = false; @@ -287,11 +193,12 @@ bool linux_at_secure = false; // process-relative uptime base, initialized at start-up double prog_start_time; +std::shared_ptr per_process_opts { + new PerProcessOptions() }; + static Mutex node_isolate_mutex; static v8::Isolate* node_isolate; -DebugOptions debug_options; - // Ensures that __metadata trace events are only emitted // when tracing is enabled. class NodeTraceStateObserver : @@ -415,7 +322,7 @@ static struct { #if HAVE_INSPECTOR bool StartInspector(Environment* env, const char* script_path, - const DebugOptions& options) { + std::shared_ptr options) { // Inspector agent can't fail to start, but if it was configured to listen // right away on the websocket port and fails to bind/etc, this will return // false. @@ -429,13 +336,14 @@ static struct { #endif // HAVE_INSPECTOR void StartTracingAgent() { - if (trace_enabled_categories.empty()) { + if (per_process_opts->trace_event_categories.empty()) { tracing_file_writer_ = tracing_agent_->DefaultHandle(); } else { tracing_file_writer_ = tracing_agent_->AddClient( - ParseCommaSeparatedSet(trace_enabled_categories), + ParseCommaSeparatedSet(per_process_opts->trace_event_categories), std::unique_ptr( - new tracing::NodeTraceWriter(trace_file_pattern)), + new tracing::NodeTraceWriter( + per_process_opts->trace_event_file_pattern)), tracing::Agent::kUseDefaultCategories); } } @@ -1843,7 +1751,7 @@ static void EnvSetter(Local property, Local value, const PropertyCallbackInfo& info) { Environment* env = Environment::GetCurrent(info); - if (config_pending_deprecation && env->EmitProcessEnvWarning() && + if (env->options()->pending_deprecation && env->EmitProcessEnvWarning() && !value->IsString() && !value->IsNumber() && !value->IsBoolean()) { if (ProcessEmitDeprecationWarning( env, @@ -2033,11 +1941,11 @@ static Local GetFeatures(Environment* env) { static void DebugPortGetter(Local property, const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); Mutex::ScopedLock lock(process_mutex); - int port = debug_options.port(); + int port = env->options()->debug_options->port(); #if HAVE_INSPECTOR if (port == 0) { - Environment* env = Environment::GetCurrent(info); if (auto io = env->inspector_agent()->io()) port = io->port(); } @@ -2049,8 +1957,10 @@ static void DebugPortGetter(Local property, static void DebugPortSetter(Local property, Local value, const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); Mutex::ScopedLock lock(process_mutex); - debug_options.set_port(value->Int32Value()); + env->options()->debug_options->host_port.port = + value->Int32Value(env->context()).FromMaybe(0); } @@ -2080,10 +1990,8 @@ namespace { } // anonymous namespace void SetupProcessObject(Environment* env, - int argc, - const char* const* argv, - int exec_argc, - const char* const* exec_argv) { + const std::vector& args, + const std::vector& exec_args) { HandleScope scope(env->isolate()); Local process = env->process_object(); @@ -2221,18 +2129,22 @@ void SetupProcessObject(Environment* env, #endif // process.argv - Local arguments = Array::New(env->isolate(), argc); - for (int i = 0; i < argc; ++i) { - arguments->Set(i, String::NewFromUtf8(env->isolate(), argv[i], - v8::NewStringType::kNormal).ToLocalChecked()); + Local arguments = Array::New(env->isolate(), args.size()); + for (size_t i = 0; i < args.size(); ++i) { + arguments->Set(env->context(), i, + String::NewFromUtf8(env->isolate(), args[i].c_str(), + v8::NewStringType::kNormal).ToLocalChecked()) + .FromJust(); } process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "argv"), arguments); // process.execArgv - Local exec_arguments = Array::New(env->isolate(), exec_argc); - for (int i = 0; i < exec_argc; ++i) { - exec_arguments->Set(i, String::NewFromUtf8(env->isolate(), exec_argv[i], - v8::NewStringType::kNormal).ToLocalChecked()); + Local exec_arguments = Array::New(env->isolate(), exec_args.size()); + for (size_t i = 0; i < exec_args.size(); ++i) { + exec_arguments->Set(env->context(), i, + String::NewFromUtf8(env->isolate(), exec_args[i].c_str(), + v8::NewStringType::kNormal).ToLocalChecked()) + .FromJust(); } process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "execArgv"), exec_arguments); @@ -2261,29 +2173,33 @@ void SetupProcessObject(Environment* env, GetParentProcessId).FromJust()); // -e, --eval - if (eval_string) { + if (env->options()->has_eval_string) { READONLY_PROPERTY(process, "_eval", - String::NewFromUtf8(env->isolate(), eval_string, + String::NewFromUtf8( + env->isolate(), + env->options()->eval_string.c_str(), v8::NewStringType::kNormal).ToLocalChecked()); } // -p, --print - if (print_eval) { + if (env->options()->print_eval) { READONLY_PROPERTY(process, "_print_eval", True(env->isolate())); } // -c, --check - if (syntax_check_only) { + if (env->options()->syntax_check_only) { READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate())); } // -i, --interactive - if (force_repl) { + if (env->options()->force_repl) { READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); } // -r, --require + std::vector preload_modules = + std::move(env->options()->preload_modules); if (!preload_modules.empty()) { Local array = Array::New(env->isolate()); for (unsigned int i = 0; i < preload_modules.size(); ++i) { @@ -2301,22 +2217,23 @@ void SetupProcessObject(Environment* env, } // --no-deprecation - if (no_deprecation) { + // TODO(addaleax): Uncomment the commented part. + if (/*env->options()->*/no_deprecation) { READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); } // --no-warnings - if (no_process_warnings) { + if (env->options()->no_warnings) { READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate())); } // --trace-warnings - if (trace_warnings) { + if (env->options()->trace_warnings) { READONLY_PROPERTY(process, "traceProcessWarnings", True(env->isolate())); } // --throw-deprecation - if (throw_deprecation) { + if (env->options()->throw_deprecation) { READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); } @@ -2326,35 +2243,35 @@ void SetupProcessObject(Environment* env, #endif // NODE_NO_BROWSER_GLOBALS // --prof-process - if (prof_process) { + if (env->options()->prof_process) { READONLY_PROPERTY(process, "profProcess", True(env->isolate())); } // --trace-deprecation - if (trace_deprecation) { + if (env->options()->trace_deprecation) { READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); } - // TODO(refack): move the following 3 to `node_config` + // TODO(refack): move the following 4 to `node_config` // --inspect-brk - if (debug_options.wait_for_connect()) { + if (env->options()->debug_options->wait_for_connect()) { READONLY_DONT_ENUM_PROPERTY(process, "_breakFirstLine", True(env->isolate())); } - if (debug_options.break_node_first_line()) { + if (env->options()->debug_options->break_node_first_line) { READONLY_DONT_ENUM_PROPERTY(process, "_breakNodeFirstLine", True(env->isolate())); } // --inspect --debug-brk - if (debug_options.deprecated_invocation()) { + if (env->options()->debug_options->deprecated_invocation()) { READONLY_DONT_ENUM_PROPERTY(process, "_deprecatedDebugBrk", True(env->isolate())); } // --debug or, --debug-brk without --inspect - if (debug_options.invalid_invocation()) { + if (env->options()->debug_options->invalid_invocation()) { READONLY_DONT_ENUM_PROPERTY(process, "_invalidDebug", True(env->isolate())); } @@ -2378,7 +2295,7 @@ void SetupProcessObject(Environment* env, v8::NewStringType::kInternalized, exec_path_len).ToLocalChecked(); } else { - exec_path_value = String::NewFromUtf8(env->isolate(), argv[0], + exec_path_value = String::NewFromUtf8(env->isolate(), args[0].c_str(), v8::NewStringType::kInternalized).ToLocalChecked(); } process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "execPath"), @@ -2560,7 +2477,8 @@ void LoadEnvironment(Environment* env) { get_binding_fn, get_linked_binding_fn, get_internal_binding_fn, - Boolean::New(env->isolate(), debug_options.break_node_first_line()) + Boolean::New(env->isolate(), + env->options()->debug_options->break_node_first_line) }; // Bootstrap internal loaders @@ -2741,363 +2659,8 @@ static void PrintHelp() { } -static bool ArgIsAllowed(const char* arg, const char* allowed) { - for (; *arg && *allowed; arg++, allowed++) { - // Like normal strcmp(), except that a '_' in `allowed` matches either a '-' - // or '_' in `arg`. - if (*allowed == '_') { - if (!(*arg == '_' || *arg == '-')) - return false; - } else { - if (*arg != *allowed) - return false; - } - } - - // "--some-arg=val" is allowed for "--some-arg" - if (*arg == '=') - return true; - - // Both must be null, or one string is just a prefix of the other, not a - // match. - return !*arg && !*allowed; -} - - -static void CheckIfAllowedInEnv(const char* exe, bool is_env, - const char* arg) { - if (!is_env) - return; - - static const char* whitelist[] = { - // Node options, sorted in `node --help` order for ease of comparison. - // Please, update NODE_OPTIONS section in cli.md if changed. - "--enable-fips", - "--experimental-modules", - "--experimental-repl-await", - "--experimental-vm-modules", - "--experimental-worker", - "--expose-http2", // keep as a non-op through v9.x - "--force-fips", - "--icu-data-dir", - "--inspect", - "--inspect-brk", - "--inspect-port", - "--loader", - "--napi-modules", - "--no-deprecation", - "--no-force-async-hooks-checks", - "--no-warnings", - "--openssl-config", - "--pending-deprecation", - "--redirect-warnings", - "--require", - "--throw-deprecation", - "--title", - "--tls-cipher-list", - "--trace-deprecation", - "--trace-event-categories", - "--trace-event-file-pattern", - "--trace-events-enabled", - "--trace-sync-io", - "--trace-warnings", - "--track-heap-objects", - "--use-bundled-ca", - "--use-openssl-ca", - "--v8-pool-size", - "--zero-fill-buffers", - "-r", - - // V8 options (define with '_', which allows '-' or '_') - "--abort_on_uncaught_exception", - "--max_old_space_size", - "--perf_basic_prof", - "--perf_prof", - "--stack_trace_limit", - }; - - for (unsigned i = 0; i < arraysize(whitelist); i++) { - const char* allowed = whitelist[i]; - if (ArgIsAllowed(arg, allowed)) - return; - } - - fprintf(stderr, "%s: %s is not allowed in NODE_OPTIONS\n", exe, arg); - exit(9); -} - - -// Parse command line arguments. -// -// argv is modified in place. exec_argv and v8_argv are out arguments that -// ParseArgs() allocates memory for and stores a pointer to the output -// vector in. The caller should free them with delete[]. -// -// On exit: -// -// * argv contains the arguments with node and V8 options filtered out. -// * exec_argv contains both node and V8 options and nothing else. -// * v8_argv contains argv[0] plus any V8 options -static void ParseArgs(int* argc, - const char** argv, - int* exec_argc, - const char*** exec_argv, - int* v8_argc, - const char*** v8_argv, - bool is_env) { - const unsigned int nargs = static_cast(*argc); - const char** new_exec_argv = new const char*[nargs]; - const char** new_v8_argv = new const char*[nargs]; - const char** new_argv = new const char*[nargs]; -#if HAVE_OPENSSL - bool use_bundled_ca = false; - bool use_openssl_ca = false; -#endif // HAVE_OPENSSL - - for (unsigned int i = 0; i < nargs; ++i) { - new_exec_argv[i] = nullptr; - new_v8_argv[i] = nullptr; - new_argv[i] = nullptr; - } - - // exec_argv starts with the first option, the other two start with argv[0]. - unsigned int new_exec_argc = 0; - unsigned int new_v8_argc = 1; - unsigned int new_argc = 1; - new_v8_argv[0] = argv[0]; - new_argv[0] = argv[0]; - - unsigned int index = 1; - bool short_circuit = false; - while (index < nargs && argv[index][0] == '-' && !short_circuit) { - const char* const arg = argv[index]; - unsigned int args_consumed = 1; - - CheckIfAllowedInEnv(argv[0], is_env, arg); - - if (debug_options.ParseOption(argv[0], arg)) { - // Done, consumed by DebugOptions::ParseOption(). - } else if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) { - printf("%s\n", NODE_VERSION); - exit(0); - } else if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { - PrintHelp(); - exit(0); - } else if (strcmp(arg, "--eval") == 0 || - strcmp(arg, "-e") == 0 || - strcmp(arg, "--print") == 0 || - strcmp(arg, "-pe") == 0 || - strcmp(arg, "-p") == 0) { - bool is_eval = strchr(arg, 'e') != nullptr; - bool is_print = strchr(arg, 'p') != nullptr; - print_eval = print_eval || is_print; - // --eval, -e and -pe always require an argument. - if (is_eval == true) { - args_consumed += 1; - eval_string = argv[index + 1]; - if (eval_string == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); - exit(9); - } - } else if ((index + 1 < nargs) && - argv[index + 1] != nullptr && - argv[index + 1][0] != '-') { - args_consumed += 1; - eval_string = argv[index + 1]; - if (strncmp(eval_string, "\\-", 2) == 0) { - // Starts with "\\-": escaped expression, drop the backslash. - eval_string += 1; - } - } - } else if (strcmp(arg, "--require") == 0 || - strcmp(arg, "-r") == 0) { - const char* module = argv[index + 1]; - if (module == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); - exit(9); - } - args_consumed += 1; - preload_modules.push_back(module); - } else if (strcmp(arg, "--check") == 0 || strcmp(arg, "-c") == 0) { - syntax_check_only = true; - } else if (strcmp(arg, "--interactive") == 0 || strcmp(arg, "-i") == 0) { - force_repl = true; - } else if (strcmp(arg, "--no-deprecation") == 0) { - no_deprecation = true; - } else if (strcmp(arg, "--napi-modules") == 0) { - // no-op - } else if (strcmp(arg, "--no-warnings") == 0) { - no_process_warnings = true; - } else if (strcmp(arg, "--trace-warnings") == 0) { - trace_warnings = true; - } else if (strncmp(arg, "--redirect-warnings=", 20) == 0) { - config_warning_file = arg + 20; - } else if (strcmp(arg, "--trace-deprecation") == 0) { - trace_deprecation = true; - } else if (strcmp(arg, "--trace-sync-io") == 0) { - trace_sync_io = true; - } else if (strcmp(arg, "--no-force-async-hooks-checks") == 0) { - no_force_async_hooks_checks = true; - } else if (strcmp(arg, "--trace-events-enabled") == 0) { - if (trace_enabled_categories.empty()) - trace_enabled_categories = "v8,node,node.async_hooks"; - } else if (strcmp(arg, "--trace-event-categories") == 0) { - const char* categories = argv[index + 1]; - if (categories == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); - exit(9); - } - args_consumed += 1; - trace_enabled_categories = categories; - } else if (strcmp(arg, "--trace-event-file-pattern") == 0) { - const char* file_pattern = argv[index + 1]; - if (file_pattern == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); - exit(9); - } - args_consumed += 1; - trace_file_pattern = file_pattern; - } else if (strcmp(arg, "--track-heap-objects") == 0) { - track_heap_objects = true; - } else if (strcmp(arg, "--throw-deprecation") == 0) { - throw_deprecation = true; - } else if (strncmp(arg, "--security-revert=", 18) == 0) { - const char* cve = arg + 18; - Revert(cve); - } else if (strncmp(arg, "--title=", 8) == 0) { - config_process_title = arg + 8; - } else if (strcmp(arg, "--preserve-symlinks") == 0) { - config_preserve_symlinks = true; - } else if (strcmp(arg, "--preserve-symlinks-main") == 0) { - config_preserve_symlinks_main = true; - } else if (strcmp(arg, "--experimental-modules") == 0) { - config_experimental_modules = true; - } else if (strcmp(arg, "--experimental-vm-modules") == 0) { - config_experimental_vm_modules = true; - } else if (strcmp(arg, "--experimental-worker") == 0) { - config_experimental_worker = true; - } else if (strcmp(arg, "--experimental-repl-await") == 0) { - config_experimental_repl_await = true; - } else if (strcmp(arg, "--loader") == 0) { - const char* module = argv[index + 1]; - if (!config_experimental_modules) { - fprintf(stderr, "%s: %s requires --experimental-modules be enabled\n", - argv[0], arg); - exit(9); - } - if (module == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); - exit(9); - } - args_consumed += 1; - config_userland_loader = module; - } else if (strcmp(arg, "--prof-process") == 0) { - prof_process = true; - short_circuit = true; - } else if (strcmp(arg, "--zero-fill-buffers") == 0) { - zero_fill_all_buffers = true; - } else if (strcmp(arg, "--pending-deprecation") == 0) { - config_pending_deprecation = true; - } else if (strcmp(arg, "--v8-options") == 0) { - new_v8_argv[new_v8_argc] = "--help"; - new_v8_argc += 1; - } else if (strncmp(arg, "--v8-pool-size=", 15) == 0) { - v8_thread_pool_size = atoi(arg + 15); -#if HAVE_OPENSSL - } else if (strncmp(arg, "--tls-cipher-list=", 18) == 0) { - default_cipher_list = arg + 18; - } else if (strncmp(arg, "--use-openssl-ca", 16) == 0) { - ssl_openssl_cert_store = true; - use_openssl_ca = true; - } else if (strncmp(arg, "--use-bundled-ca", 16) == 0) { - use_bundled_ca = true; - ssl_openssl_cert_store = false; -#if NODE_FIPS_MODE - } else if (strcmp(arg, "--enable-fips") == 0) { - enable_fips_crypto = true; - } else if (strcmp(arg, "--force-fips") == 0) { - force_fips_crypto = true; -#endif /* NODE_FIPS_MODE */ - } else if (strncmp(arg, "--openssl-config=", 17) == 0) { - openssl_config.assign(arg + 17); -#endif /* HAVE_OPENSSL */ -#if defined(NODE_HAVE_I18N_SUPPORT) - } else if (strncmp(arg, "--icu-data-dir=", 15) == 0) { - icu_data_dir.assign(arg + 15); -#endif - } else if (strcmp(arg, "--expose-internals") == 0 || - strcmp(arg, "--expose_internals") == 0) { - config_expose_internals = true; - } else if (strcmp(arg, "--expose-http2") == 0 || - strcmp(arg, "--expose_http2") == 0) { - // Keep as a non-op through v9.x - } else if (strcmp(arg, "-") == 0) { - break; - } else if (strcmp(arg, "--") == 0) { - index += 1; - break; - } else if (strcmp(arg, "--abort-on-uncaught-exception") == 0 || - strcmp(arg, "--abort_on_uncaught_exception") == 0) { - abort_on_uncaught_exception = true; - // Also a V8 option. Pass through as-is. - new_v8_argv[new_v8_argc] = arg; - new_v8_argc += 1; - } else { - // V8 option. Pass through as-is. - new_v8_argv[new_v8_argc] = arg; - new_v8_argc += 1; - } - - memcpy(new_exec_argv + new_exec_argc, - argv + index, - args_consumed * sizeof(*argv)); - - new_exec_argc += args_consumed; - index += args_consumed; - } - -#if HAVE_OPENSSL - if (use_openssl_ca && use_bundled_ca) { - fprintf(stderr, - "%s: either --use-openssl-ca or --use-bundled-ca can be used, " - "not both\n", - argv[0]); - exit(9); - } -#endif - - if (eval_string != nullptr && syntax_check_only) { - fprintf(stderr, - "%s: either --check or --eval can be used, not both\n", argv[0]); - exit(9); - } - - // Copy remaining arguments. - const unsigned int args_left = nargs - index; - - if (is_env && args_left) { - fprintf(stderr, "%s: %s is not supported in NODE_OPTIONS\n", - argv[0], argv[index]); - exit(9); - } - - memcpy(new_argv + new_argc, argv + index, args_left * sizeof(*argv)); - new_argc += args_left; - - *exec_argc = new_exec_argc; - *exec_argv = new_exec_argv; - *v8_argc = new_v8_argc; - *v8_argv = new_v8_argv; - - // Copy new_argv over argv and update argc. - memcpy(argv, new_argv, new_argc * sizeof(*argv)); - delete[] new_argv; - *argc = static_cast(new_argc); -} - - static void StartInspector(Environment* env, const char* path, - DebugOptions debug_options) { + std::shared_ptr debug_options) { #if HAVE_INSPECTOR CHECK(!env->inspector_agent()->IsListening()); v8_platform.StartInspector(env, path, debug_options); @@ -3330,26 +2893,87 @@ inline void PlatformInit() { #endif // _WIN32 } +// TODO(addaleax): Remove, both from the public API and in implementation. +bool no_deprecation = false; +#if HAVE_OPENSSL +bool ssl_openssl_cert_store = false; +#if NODE_FIPS_MODE +bool enable_fips_crypto = false; +bool force_fips_crypto = false; +#endif +#endif -void ProcessArgv(int* argc, - const char** argv, - int* exec_argc, - const char*** exec_argv, - bool is_env = false) { +void ProcessArgv(std::vector* args, + std::vector* exec_args, + bool is_env) { // Parse a few arguments which are specific to Node. - int v8_argc; - const char** v8_argv; - ParseArgs(argc, argv, exec_argc, exec_argv, &v8_argc, &v8_argv, is_env); + std::vector v8_args; + std::string error; + 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); + } + + if (per_process_opts->print_version) { + printf("%s\n", NODE_VERSION); + exit(0); + } + + if (per_process_opts->print_help) { + PrintHelp(); + exit(0); + } + + if (per_process_opts->print_v8_help) { + V8::SetFlagsFromString("--help", 6); + exit(0); + } + + for (const std::string& cve : per_process_opts->security_reverts) + Revert(cve.c_str()); + + // TODO(addaleax): Move this validation to the option parsers. + auto env_opts = per_process_opts->per_isolate->per_env; + if (!env_opts->userland_loader.empty() && + !env_opts->experimental_modules) { + fprintf(stderr, "%s: --loader requires --experimental-modules be enabled\n", + args->at(0).c_str()); + exit(9); + } + + if (env_opts->syntax_check_only && env_opts->has_eval_string) { + fprintf(stderr, "%s: either --check or --eval can be used, not both\n", + args->at(0).c_str()); + exit(9); + } + + if (per_process_opts->use_openssl_ca && per_process_opts->use_bundled_ca) { + fprintf(stderr, "%s: either --use-openssl-ca or --use-bundled-ca can be " + "used, not both\n", + args->at(0).c_str()); + exit(9); + } + + if (std::find(v8_args.begin(), v8_args.end(), + "--abort-on-uncaught-exception") != v8_args.end() || + std::find(v8_args.begin(), v8_args.end(), + "--abort_on_uncaught_exception") != v8_args.end()) { + abort_on_uncaught_exception = true; + } // TODO(bnoordhuis) Intercept --prof arguments and start the CPU profiler // manually? That would give us a little more control over its runtime // behavior but it could also interfere with the user's intentions in ways // we fail to anticipate. Dillema. - for (int i = 1; i < v8_argc; ++i) { - if (strncmp(v8_argv[i], "--prof", sizeof("--prof") - 1) == 0) { - v8_is_profiling = true; - break; - } + if (std::find(v8_args.begin(), v8_args.end(), "--prof") != v8_args.end()) { + v8_is_profiling = true; } #ifdef __POSIX__ @@ -3361,28 +2985,40 @@ void ProcessArgv(int* argc, } #endif - // The const_cast doesn't violate conceptual const-ness. V8 doesn't modify - // the argv array or the elements it points to. - if (v8_argc > 1) - V8::SetFlagsFromCommandLine(&v8_argc, const_cast(v8_argv), true); + std::vector v8_args_as_char_ptr(v8_args.size()); + if (v8_args.size() > 0) { + for (size_t i = 0; i < v8_args.size(); ++i) + v8_args_as_char_ptr[i] = &v8_args[i][0]; + int argc = v8_args.size(); + V8::SetFlagsFromCommandLine(&argc, &v8_args_as_char_ptr[0], true); + v8_args_as_char_ptr.resize(argc); + } // Anything that's still in v8_argv is not a V8 or a node option. - for (int i = 1; i < v8_argc; i++) { - fprintf(stderr, "%s: bad option: %s\n", argv[0], v8_argv[i]); + for (size_t i = 1; i < v8_args_as_char_ptr.size(); i++) { + fprintf(stderr, "%s: bad option: %s\n", + args->at(0).c_str(), v8_args_as_char_ptr[i]); } - delete[] v8_argv; - v8_argv = nullptr; - if (v8_argc > 1) { + if (v8_args_as_char_ptr.size() > 1) { exit(9); } + + // TODO(addaleax): Remove. + zero_fill_all_buffers = per_process_opts->zero_fill_all_buffers; + no_deprecation = per_process_opts->per_isolate->per_env->no_deprecation; +#if HAVE_OPENSSL + ssl_openssl_cert_store = per_process_opts->ssl_openssl_cert_store; +#if NODE_FIPS_MODE + enable_fips_crypto = per_process_opts->enable_fips_crypto; + force_fips_crypto = per_process_opts->force_fips_crypto; +#endif +#endif } -void Init(int* argc, - const char** argv, - int* exec_argc, - const char*** exec_argv) { +void Init(std::vector* argv, + std::vector* exec_argv) { // Initialize prog_start_time to get relative uptime. prog_start_time = static_cast(uv_now(uv_default_loop())); @@ -3399,78 +3035,80 @@ void Init(int* argc, V8::SetFlagsFromString(NODE_V8_OPTIONS, sizeof(NODE_V8_OPTIONS) - 1); #endif + std::shared_ptr default_env_options = + per_process_opts->per_isolate->per_env; { std::string text; - config_pending_deprecation = + default_env_options->pending_deprecation = SafeGetenv("NODE_PENDING_DEPRECATION", &text) && text[0] == '1'; } // Allow for environment set preserving symlinks. { std::string text; - config_preserve_symlinks = + default_env_options->preserve_symlinks = SafeGetenv("NODE_PRESERVE_SYMLINKS", &text) && text[0] == '1'; } { std::string text; - config_preserve_symlinks_main = + default_env_options->preserve_symlinks_main = SafeGetenv("NODE_PRESERVE_SYMLINKS_MAIN", &text) && text[0] == '1'; } - if (config_warning_file.empty()) - SafeGetenv("NODE_REDIRECT_WARNINGS", &config_warning_file); + if (default_env_options->redirect_warnings.empty()) { + SafeGetenv("NODE_REDIRECT_WARNINGS", + &default_env_options->redirect_warnings); + } #if HAVE_OPENSSL - if (openssl_config.empty()) - SafeGetenv("OPENSSL_CONF", &openssl_config); + std::string* openssl_config = &per_process_opts->openssl_config; + if (openssl_config->empty()) { + SafeGetenv("OPENSSL_CONF", openssl_config); + } #endif #if !defined(NODE_WITHOUT_NODE_OPTIONS) std::string node_options; if (SafeGetenv("NODE_OPTIONS", &node_options)) { - // Smallest tokens are 2-chars (a not space and a space), plus 2 extra - // pointers, for the prepended executable name, and appended NULL pointer. - size_t max_len = 2 + (node_options.length() + 1) / 2; - const char** argv_from_env = new const char*[max_len]; - int argc_from_env = 0; + std::vector env_argv; // [0] is expected to be the program name, fill it in from the real argv. - argv_from_env[argc_from_env++] = argv[0]; - - char* cstr = strdup(node_options.c_str()); - char* initptr = cstr; - char* token; - while ((token = strtok(initptr, " "))) { // NOLINT(runtime/threadsafe_fn) - initptr = nullptr; - argv_from_env[argc_from_env++] = token; - } - argv_from_env[argc_from_env] = nullptr; - int exec_argc_; - const char** exec_argv_ = nullptr; - ProcessArgv(&argc_from_env, argv_from_env, &exec_argc_, &exec_argv_, true); - delete[] exec_argv_; - delete[] argv_from_env; - free(cstr); + env_argv.push_back(argv->at(0)); + + // Split NODE_OPTIONS at each ' ' character. + std::string::size_type index = std::string::npos; + do { + std::string::size_type prev_index = index; + index = node_options.find(' ', index + 1); + if (index - prev_index == 1) continue; + + const std::string option = node_options.substr(prev_index + 1, index); + if (!option.empty()) + env_argv.emplace_back(std::move(option)); + } while (index != std::string::npos); + + + ProcessArgv(&env_argv, nullptr, true); } #endif - ProcessArgv(argc, argv, exec_argc, exec_argv); + ProcessArgv(argv, exec_argv, false); // Set the process.title immediately after processing argv if --title is set. - if (!config_process_title.empty()) - uv_set_process_title(config_process_title.c_str()); + if (!per_process_opts->title.empty()) + uv_set_process_title(per_process_opts->title.c_str()); #if defined(NODE_HAVE_I18N_SUPPORT) // If the parameter isn't given, use the env variable. - if (icu_data_dir.empty()) - SafeGetenv("NODE_ICU_DATA", &icu_data_dir); + if (per_process_opts->icu_data_dir.empty()) + SafeGetenv("NODE_ICU_DATA", &per_process_opts->icu_data_dir); // Initialize ICU. // If icu_data_dir is empty here, it will load the 'minimal' data. - if (!i18n::InitializeICUDirectory(icu_data_dir)) { + if (!i18n::InitializeICUDirectory(per_process_opts->icu_data_dir)) { fprintf(stderr, "%s: could not initialize ICU " "(check NODE_ICU_DATA or --icu-data-dir parameters)\n", - argv[0]); + argv->at(0).c_str()); exit(9); } #endif @@ -3481,6 +3119,27 @@ void Init(int* argc, node_is_initialized = true; } +// TODO(addaleax): Deprecate and eventually remove this. +void Init(int* argc, + const char** argv, + int* exec_argc, + const char*** exec_argv) { + std::vector argv_(argv, argv + *argc); // NOLINT + std::vector exec_argv_; + + Init(&argv_, &exec_argv_); + + *argc = argv_.size(); + *exec_argc = exec_argv_.size(); + // These leak memory, because, in the original code of this function, no + // extra allocations were visible. This should be okay because this function + // is only supposed to be called once per process, though. + *exec_argv = Malloc(*exec_argc); + for (int i = 0; i < *exec_argc; ++i) + (*exec_argv)[i] = strdup(exec_argv_[i].c_str()); + for (int i = 0; i < *argc; ++i) + argv[i] = strdup(argv_[i].c_str()); +} void RunAtExit(Environment* env) { env->RunAtExitCallbacks(); @@ -3605,9 +3264,13 @@ Environment* CreateEnvironment(IsolateData* isolate_data, Isolate* isolate = context->GetIsolate(); HandleScope handle_scope(isolate); Context::Scope context_scope(context); - auto env = new Environment(isolate_data, context, - v8_platform.GetTracingAgentWriter()); - env->Start(argc, argv, exec_argc, exec_argv, v8_is_profiling); + // TODO(addaleax): This is a much better place for parsing per-Environment + // options than the global parse call. + std::vector args(argv, argv + argc); + std::vector exec_args(exec_argv, exec_argv + exec_argc); + Environment* env = new Environment(isolate_data, context, + v8_platform.GetTracingAgentWriter()); + env->Start(args, exec_args, v8_is_profiling); return env; } @@ -3660,23 +3323,27 @@ Local NewContext(Isolate* isolate, inline int Start(Isolate* isolate, IsolateData* isolate_data, - int argc, const char* const* argv, - int exec_argc, const char* const* exec_argv) { + const std::vector& args, + const std::vector& exec_args) { HandleScope handle_scope(isolate); Local context = NewContext(isolate); Context::Scope context_scope(context); Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter()); - env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling); + env.Start(args, exec_args, v8_is_profiling); - const char* path = argc > 1 ? argv[1] : nullptr; - StartInspector(&env, path, debug_options); + const char* path = args.size() > 1 ? args[1].c_str() : nullptr; + StartInspector(&env, path, env.options()->debug_options); - if (debug_options.inspector_enabled() && !v8_platform.InspectorStarted(&env)) + if (env.options()->debug_options->inspector_enabled && + !v8_platform.InspectorStarted(&env)) { return 12; // Signal internal error. + } env.set_abort_on_uncaught_exception(abort_on_uncaught_exception); - if (no_force_async_hooks_checks) { + // TODO(addaleax): Maybe access this option directly instead of setting + // a boolean member of Environment. Ditto below for trace_sync_io. + if (env.options()->no_force_async_hooks_checks) { env.async_hooks()->no_force_checks(); } @@ -3687,7 +3354,7 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, env.async_hooks()->pop_async_id(1); } - env.set_trace_sync_io(trace_sync_io); + env.set_trace_sync_io(env.options()->trace_sync_io); { SealHandleScope seal(isolate); @@ -3762,8 +3429,8 @@ Isolate* NewIsolate(ArrayBufferAllocator* allocator) { } inline int Start(uv_loop_t* event_loop, - int argc, const char* const* argv, - int exec_argc, const char* const* exec_argv) { + const std::vector& args, + const std::vector& exec_args) { std::unique_ptr allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator); Isolate* const isolate = NewIsolate(allocator.get()); @@ -3788,11 +3455,13 @@ inline int Start(uv_loop_t* event_loop, v8_platform.Platform(), allocator.get()), &FreeIsolateData); - if (track_heap_objects) { + // TODO(addaleax): This should load a real per-Isolate option, currently + // this is still effectively per-process. + if (isolate_data->options()->track_heap_objects) { isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); } exit_code = - Start(isolate, isolate_data.get(), argc, argv, exec_argc, exec_argv); + Start(isolate, isolate_data.get(), args, exec_args); } { @@ -3816,11 +3485,10 @@ int Start(int argc, char** argv) { // Hack around with the argv pointer. Used for process.title = "blah". argv = uv_setup_args(argc, argv); - // This needs to run *before* V8::Initialize(). The const_cast is not - // optional, in case you're wondering. - int exec_argc; - const char** exec_argv; - Init(&argc, const_cast(argv), &exec_argc, &exec_argv); + std::vector args(argv, argv + argc); + std::vector exec_args; + // This needs to run *before* V8::Initialize(). + Init(&args, &exec_args); #if HAVE_OPENSSL { @@ -3838,12 +3506,13 @@ int Start(int argc, char** argv) { V8::SetEntropySource(crypto::EntropySource); #endif // HAVE_OPENSSL - v8_platform.Initialize(v8_thread_pool_size); + v8_platform.Initialize( + per_process_opts->v8_thread_pool_size); V8::Initialize(); performance::performance_v8_start = PERFORMANCE_NOW(); v8_initialized = true; const int exit_code = - Start(uv_default_loop(), argc, argv, exec_argc, exec_argv); + Start(uv_default_loop(), args, exec_args); v8_platform.StopTracingAgent(); v8_initialized = false; V8::Dispose(); @@ -3856,9 +3525,6 @@ int Start(int argc, char** argv) { // will never be fully cleaned up. v8_platform.Dispose(); - delete[] exec_argv; - exec_argv = nullptr; - return exit_code; } diff --git a/src/node.h b/src/node.h index 636a3ef029732a..61919eb56fb4cb 100644 --- a/src/node.h +++ b/src/node.h @@ -199,6 +199,8 @@ typedef intptr_t ssize_t; namespace node { +// TODO(addaleax): Deprecate and remove all of these ASAP. They have been +// made effectively non-functional anyway. NODE_EXTERN extern bool no_deprecation; #if HAVE_OPENSSL NODE_EXTERN extern bool ssl_openssl_cert_store; @@ -208,7 +210,12 @@ NODE_EXTERN extern bool force_fips_crypto; # endif #endif +// TODO(addaleax): Officially deprecate this and replace it with something +// better suited for a public embedder API. NODE_EXTERN int Start(int argc, char* argv[]); + +// TODO(addaleax): Officially deprecate this and replace it with something +// better suited for a public embedder API. NODE_EXTERN void Init(int* argc, const char** argv, int* exec_argc, @@ -265,6 +272,8 @@ NODE_EXTERN IsolateData* CreateIsolateData( ArrayBufferAllocator* allocator); NODE_EXTERN void FreeIsolateData(IsolateData* isolate_data); +// TODO(addaleax): Add an official variant using STL containers, and move +// per-Environment options parsing here. NODE_EXTERN Environment* CreateEnvironment(IsolateData* isolate_data, v8::Local context, int argc, diff --git a/src/node_buffer.h b/src/node_buffer.h index b4aa12cbcfadc6..e8d306e7dd6bff 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -27,6 +27,7 @@ namespace node { +// TODO(addaleax): Deprecate and remove this ASAP. extern bool zero_fill_all_buffers; namespace Buffer { diff --git a/src/node_config.cc b/src/node_config.cc index 62fd4ef81e093c..d34269912e4713 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -2,7 +2,6 @@ #include "node_i18n.h" #include "env-inl.h" #include "util-inl.h" -#include "node_debug_options.h" namespace node { @@ -56,6 +55,7 @@ static void Initialize(Local target, #ifdef NODE_FIPS_MODE READONLY_BOOLEAN_PROPERTY("fipsMode"); + // TODO(addaleax): Use options parser variable instead. if (force_fips_crypto) READONLY_BOOLEAN_PROPERTY("fipsForced"); #endif @@ -72,35 +72,38 @@ static void Initialize(Local target, READONLY_BOOLEAN_PROPERTY("hasTracing"); #endif - READONLY_STRING_PROPERTY(target, "icuDataDir", icu_data_dir); + // TODO(addaleax): This seems to be an unused, private API. Remove it? + READONLY_STRING_PROPERTY(target, "icuDataDir", + per_process_opts->icu_data_dir); #endif // NODE_HAVE_I18N_SUPPORT - if (config_preserve_symlinks) + if (env->options()->preserve_symlinks) READONLY_BOOLEAN_PROPERTY("preserveSymlinks"); - if (config_preserve_symlinks_main) + if (env->options()->preserve_symlinks_main) READONLY_BOOLEAN_PROPERTY("preserveSymlinksMain"); - if (config_experimental_modules) { + if (env->options()->experimental_modules) { READONLY_BOOLEAN_PROPERTY("experimentalModules"); - if (!config_userland_loader.empty()) { - READONLY_STRING_PROPERTY(target, "userLoader", config_userland_loader); + const std::string& userland_loader = env->options()->userland_loader; + if (!userland_loader.empty()) { + READONLY_STRING_PROPERTY(target, "userLoader", userland_loader); } } - if (config_experimental_vm_modules) + if (env->options()->experimental_vm_modules) READONLY_BOOLEAN_PROPERTY("experimentalVMModules"); - if (config_experimental_worker) + if (env->options()->experimental_worker) READONLY_BOOLEAN_PROPERTY("experimentalWorker"); - if (config_experimental_repl_await) + if (env->options()->experimental_repl_await) READONLY_BOOLEAN_PROPERTY("experimentalREPLAwait"); - if (config_pending_deprecation) + if (env->options()->pending_deprecation) READONLY_BOOLEAN_PROPERTY("pendingDeprecation"); - if (config_expose_internals) + if (env->options()->expose_internals) READONLY_BOOLEAN_PROPERTY("exposeInternals"); if (env->abort_on_uncaught_exception()) @@ -110,22 +113,25 @@ static void Initialize(Local target, "bits", Number::New(env->isolate(), 8 * sizeof(intptr_t))); - if (!config_warning_file.empty()) { - READONLY_STRING_PROPERTY(target, "warningFile", config_warning_file); + const std::string& warning_file = env->options()->redirect_warnings; + if (!warning_file.empty()) { + READONLY_STRING_PROPERTY(target, "warningFile", warning_file); } - Local debugOptions = Object::New(isolate); - READONLY_PROPERTY(target, "debugOptions", debugOptions); + std::shared_ptr debug_options = env->options()->debug_options; + Local debug_options_obj = Object::New(isolate); + READONLY_PROPERTY(target, "debugOptions", debug_options_obj); - READONLY_STRING_PROPERTY(debugOptions, "host", debug_options.host_name()); + READONLY_STRING_PROPERTY(debug_options_obj, "host", + debug_options->host()); - READONLY_PROPERTY(debugOptions, + READONLY_PROPERTY(debug_options_obj, "port", - Integer::New(isolate, debug_options.port())); + Integer::New(isolate, debug_options->port())); - READONLY_PROPERTY(debugOptions, + READONLY_PROPERTY(debug_options_obj, "inspectorEnabled", - Boolean::New(isolate, debug_options.inspector_enabled())); + Boolean::New(isolate, debug_options->inspector_enabled)); } // InitConfig } // namespace node diff --git a/src/node_constants.cc b/src/node_constants.cc index f1468ff7ca03f2..b6c7bf37a3ad86 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -51,10 +51,6 @@ namespace node { using v8::Local; using v8::Object; -#if HAVE_OPENSSL -const char* default_cipher_list = DEFAULT_CIPHER_LIST_CORE; -#endif - namespace { void DefineErrnoConstants(Local target) { @@ -1240,7 +1236,7 @@ void DefineCryptoConstants(Local target) { DEFAULT_CIPHER_LIST_CORE); NODE_DEFINE_STRING_CONSTANT(target, "defaultCipherList", - default_cipher_list); + per_process_opts->tls_cipher_list.c_str()); #endif NODE_DEFINE_CONSTANT(target, INT_MAX); } diff --git a/src/node_constants.h b/src/node_constants.h index 1de420e2def571..6f73fb4d7d9bfc 100644 --- a/src/node_constants.h +++ b/src/node_constants.h @@ -66,10 +66,6 @@ namespace node { -#if HAVE_OPENSSL -extern const char* default_cipher_list; -#endif - void DefineConstants(v8::Isolate* isolate, v8::Local target); } // namespace node diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 8fac840244c6f7..ba88f1a2a5d76c 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -756,6 +756,8 @@ static X509_STORE* NewRootCertStore() { if (*system_cert_path != '\0') { X509_STORE_load_locations(store, system_cert_path, nullptr); } + // TODO(addaleax): Replace `ssl_openssl_cert_store` with + // `per_process_opts->ssl_openssl_cert_store`. if (ssl_openssl_cert_store) { X509_STORE_set_default_paths(store); } else { @@ -5079,14 +5081,14 @@ void InitCryptoOnce() { OPENSSL_no_config(); // --openssl-config=... - if (!openssl_config.empty()) { + if (!per_process_opts->openssl_config.empty()) { OPENSSL_load_builtin_modules(); #ifndef OPENSSL_NO_ENGINE ENGINE_load_builtin_engines(); #endif ERR_clear_error(); CONF_modules_load_file( - openssl_config.c_str(), + per_process_opts->openssl_config.c_str(), nullptr, CONF_MFLAGS_DEFAULT_SECTION); int err = ERR_get_error(); @@ -5104,6 +5106,9 @@ void InitCryptoOnce() { #ifdef NODE_FIPS_MODE /* Override FIPS settings in cnf file, if needed. */ unsigned long err = 0; // NOLINT(runtime/int) + // TODO(addaleax): Use commented part instead. + /*if (per_process_opts->enable_fips_crypto || + per_process_opts->force_fips_crypto) {*/ if (enable_fips_crypto || force_fips_crypto) { if (0 == FIPS_mode() && !FIPS_mode_set(1)) { err = ERR_get_error(); @@ -5166,6 +5171,7 @@ void GetFipsCrypto(const FunctionCallbackInfo& args) { } void SetFipsCrypto(const FunctionCallbackInfo& args) { + // TODO(addaleax): Use options parser variables instead. CHECK(!force_fips_crypto); Environment* env = Environment::GetCurrent(args); const bool enabled = FIPS_mode(); diff --git a/src/node_debug_options.cc b/src/node_debug_options.cc deleted file mode 100644 index 5fc29059ddc84f..00000000000000 --- a/src/node_debug_options.cc +++ /dev/null @@ -1,142 +0,0 @@ -#include "node_debug_options.h" - -#include -#include -#include -#include "util.h" - -namespace node { - -namespace { -const int default_inspector_port = 9229; - -inline std::string remove_brackets(const std::string& host) { - if (!host.empty() && host.front() == '[' && host.back() == ']') - return host.substr(1, host.size() - 2); - else - return host; -} - -int parse_and_validate_port(const std::string& port) { - char* endptr; - errno = 0; - const long result = strtol(port.c_str(), &endptr, 10); // NOLINT(runtime/int) - if (errno != 0 || *endptr != '\0'|| - (result != 0 && result < 1024) || result > 65535) { - fprintf(stderr, "Debug port must be 0 or in range 1024 to 65535.\n"); - exit(12); - } - return static_cast(result); -} - -std::pair split_host_port(const std::string& arg) { - // remove_brackets only works if no port is specified - // so if it has an effect only an IPv6 address was specified - std::string host = remove_brackets(arg); - if (host.length() < arg.length()) - return {host, -1}; - - 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 {arg, -1}; - } - } - return {"", parse_and_validate_port(arg)}; - } - // host and port found - return std::make_pair(remove_brackets(arg.substr(0, colon)), - parse_and_validate_port(arg.substr(colon + 1))); -} - -} // namespace - -DebugOptions::DebugOptions() : - inspector_enabled_(false), - deprecated_debug_(false), - break_first_line_(false), - break_node_first_line_(false), - host_name_("127.0.0.1"), port_(-1) { } - -bool DebugOptions::ParseOption(const char* argv0, const std::string& option) { - bool has_argument = false; - std::string option_name; - std::string argument; - - auto pos = option.find("="); - if (pos == std::string::npos) { - option_name = option; - } else { - option_name = option.substr(0, pos); - argument = option.substr(pos + 1); - - if (argument.length() > 0) - has_argument = true; - else - argument.clear(); - } - - // Note that --debug-port and --debug-brk in conjunction with --inspect - // work but are undocumented. - // --debug is no longer valid. - // Ref: https://github.com/nodejs/node/issues/12630 - // Ref: https://github.com/nodejs/node/pull/12949 - if (option_name == "--inspect") { - inspector_enabled_ = true; - } else if (option_name == "--debug") { - deprecated_debug_ = true; - } else if (option_name == "--inspect-brk") { - inspector_enabled_ = true; - break_first_line_ = true; - } else if (option_name == "--inspect-brk-node") { - inspector_enabled_ = true; - break_node_first_line_ = true; - } else if (option_name == "--debug-brk") { - break_first_line_ = true; - deprecated_debug_ = true; - } else if (option_name == "--debug-port" || - option_name == "--inspect-port") { - if (!has_argument) { - fprintf(stderr, "%s: %s requires an argument\n", - argv0, option.c_str()); - exit(9); - } - } else { - return false; - } - -#if !HAVE_INSPECTOR - if (inspector_enabled_) { - fprintf(stderr, - "Inspector support is not available with this Node.js build\n"); - } - inspector_enabled_ = false; - return false; -#endif - - // argument can be specified for *any* option to specify host:port - if (has_argument) { - std::pair host_port = split_host_port(argument); - if (!host_port.first.empty()) { - host_name_ = host_port.first; - } - if (host_port.second >= 0) { - port_ = host_port.second; - } - } - - return true; -} - -int DebugOptions::port() const { - int port = port_; - if (port < 0) { - port = default_inspector_port; - } - return port; -} - -} // namespace node diff --git a/src/node_debug_options.h b/src/node_debug_options.h deleted file mode 100644 index 98922ab099ac77..00000000000000 --- a/src/node_debug_options.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef SRC_NODE_DEBUG_OPTIONS_H_ -#define SRC_NODE_DEBUG_OPTIONS_H_ - -#include - -// Forward declaration to break recursive dependency chain with src/env.h. -namespace node { - -class DebugOptions { - public: - DebugOptions(); - bool ParseOption(const char* argv0, const std::string& option); - bool inspector_enabled() const { return inspector_enabled_; } - bool deprecated_invocation() const { - return deprecated_debug_ && - inspector_enabled_ && - break_first_line_; - } - bool invalid_invocation() const { - return deprecated_debug_ && !inspector_enabled_; - } - bool wait_for_connect() const { - return break_first_line_ || break_node_first_line_; - } - std::string host_name() const { return host_name_; } - void set_host_name(std::string host_name) { host_name_ = host_name; } - int port() const; - void set_port(int port) { port_ = port; } - bool break_node_first_line() const { return break_node_first_line_; } - - private: - bool inspector_enabled_; - bool deprecated_debug_; - bool break_first_line_; - bool break_node_first_line_; - std::string host_name_; - int port_; -}; - -} // namespace node - -#endif // SRC_NODE_DEBUG_OPTIONS_H_ diff --git a/src/node_i18n.h b/src/node_i18n.h index 70a0c79f76cf30..7faa5e57ef25fb 100644 --- a/src/node_i18n.h +++ b/src/node_i18n.h @@ -31,8 +31,6 @@ namespace node { -extern std::string icu_data_dir; // NOLINT(runtime/string) - namespace i18n { bool InitializeICUDirectory(const std::string& path); diff --git a/src/node_internals.h b/src/node_internals.h index d8521f39a1758f..e988fe189f3230 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -33,7 +33,6 @@ #include "v8.h" #include "tracing/trace_event.h" #include "node_perf_common.h" -#include "node_debug_options.h" #include "node_api.h" #include @@ -171,67 +170,10 @@ struct sockaddr; namespace node { -// Set in node.cc by ParseArgs with the value of --openssl-config. -// Used in node_crypto.cc when initializing OpenSSL. -extern std::string openssl_config; - -// Set in node.cc by ParseArgs when --preserve-symlinks is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/module.js -extern bool config_preserve_symlinks; - -// Set in node.cc by ParseArgs when --preserve-symlinks-main is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/module.js -extern bool config_preserve_symlinks_main; - -// Set in node.cc by ParseArgs when --experimental-modules is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/module.js -extern bool config_experimental_modules; - -// Set in node.cc by ParseArgs when --experimental-vm-modules is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/vm.js -extern bool config_experimental_vm_modules; - -// Set in node.cc by ParseArgs when --experimental-worker is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by the module loader. -extern bool config_experimental_worker; - -// Set in node.cc by ParseArgs when --experimental-repl-await is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/repl.js. -extern bool config_experimental_repl_await; - -// Set in node.cc by ParseArgs when --loader is used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/internal/bootstrap/node.js -extern std::string config_userland_loader; - -// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is -// used. -// Used in node_config.cc to set a constant on process.binding('config') -// that is used by lib/internal/bootstrap/node.js -extern bool config_expose_internals; - -// Set in node.cc by ParseArgs when --redirect-warnings= is used. -// Used to redirect warning output to a file rather than sending -// it to stderr. -extern std::string config_warning_file; // NOLINT(runtime/string) - -// Set in node.cc by ParseArgs when --pending-deprecation or -// NODE_PENDING_DEPRECATION is used -extern bool config_pending_deprecation; - // Tells whether it is safe to call v8::Isolate::GetCurrent(). extern bool v8_initialized; -// Contains initial debug options. -// Set in node.cc. -// Used in node_config.cc. -extern node::DebugOptions debug_options; +extern std::shared_ptr per_process_opts; // Forward declaration class Environment; @@ -415,10 +357,8 @@ inline v8::Local FillGlobalStatsArray(Environment* env, void SetupBootstrapObject(Environment* env, v8::Local bootstrapper); void SetupProcessObject(Environment* env, - int argc, - const char* const* argv, - int exec_argc, - const char* const* exec_argv); + const std::vector& args, + const std::vector& exec_args); // Call _register functions for all of // the built-in modules. Because built-in modules don't diff --git a/src/node_options-inl.h b/src/node_options-inl.h new file mode 100644 index 00000000000000..e610cd50d11436 --- /dev/null +++ b/src/node_options-inl.h @@ -0,0 +1,422 @@ +#ifndef SRC_NODE_OPTIONS_INL_H_ +#define SRC_NODE_OPTIONS_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_options.h" +#include "util.h" +#include + +namespace node { + +PerIsolateOptions* PerProcessOptions::get_per_isolate_options() { + return per_isolate.get(); +} + +DebugOptions* EnvironmentOptions::get_debug_options() { + return debug_options.get(); +} + +EnvironmentOptions* PerIsolateOptions::get_per_env_options() { + return per_env.get(); +} + +template +void OptionsParser::AddOption(const std::string& name, + bool Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kBoolean, + std::make_shared>(field), + env_setting + }); +} + +template +void OptionsParser::AddOption(const std::string& name, + int64_t Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kInteger, + std::make_shared>(field), + env_setting + }); +} + +template +void OptionsParser::AddOption(const std::string& name, + std::string Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kString, + std::make_shared>(field), + env_setting + }); +} + +template +void OptionsParser::AddOption( + const std::string& name, + std::vector Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kStringList, + std::make_shared>>(field), + env_setting + }); +} + +template +void OptionsParser::AddOption(const std::string& name, + HostPort Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kHostPort, + std::make_shared>(field), + env_setting + }); +} + +template +void OptionsParser::AddOption(const std::string& name, NoOp no_op_tag, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { kNoOp, nullptr, env_setting }); +} + +template +void OptionsParser::AddOption(const std::string& name, + V8Option v8_option_tag, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { kV8Option, nullptr, env_setting }); +} + +template +void OptionsParser::AddAlias(const std::string& from, + const std::string& to) { + aliases_[from] = { to }; +} + +template +void OptionsParser::AddAlias(const std::string& from, + const std::vector& to) { + aliases_[from] = to; +} + +template +void OptionsParser::AddAlias( + const std::string& from, + const std::initializer_list& to) { + AddAlias(from, std::vector(to)); +} + +template +void OptionsParser::Implies(const std::string& from, + const std::string& to) { + auto it = options_.find(to); + CHECK_NE(it, options_.end()); + CHECK_EQ(it->second.type, kBoolean); + implications_.emplace(from, Implication { + std::static_pointer_cast>(it->second.field), true + }); +} + +template +void OptionsParser::ImpliesNot(const std::string& from, + const std::string& to) { + auto it = options_.find(to); + CHECK_NE(it, options_.end()); + CHECK_EQ(it->second.type, kBoolean); + implications_.emplace(from, Implication { + std::static_pointer_cast>(it->second.field), false + }); +} + +template +template +auto OptionsParser::Convert( + std::shared_ptr original, + ChildOptions* (Options::* get_child)()) { + // If we have a field on ChildOptions, and we want to access it from an + // Options instance, we call get_child() on the original Options and then + // access it, i.e. this class implements a kind of function chaining. + struct AdaptedField : BaseOptionField { + void* LookupImpl(Options* options) const override { + return original->LookupImpl((options->*get_child)()); + } + + AdaptedField( + std::shared_ptr original, + ChildOptions* (Options::* get_child)()) + : original(original), get_child(get_child) {} + + std::shared_ptr original; + ChildOptions* (Options::* get_child)(); + }; + + return std::shared_ptr( + new AdaptedField(original, get_child)); +} +template +template +auto OptionsParser::Convert( + typename OptionsParser::OptionInfo original, + ChildOptions* (Options::* get_child)()) { + return OptionInfo { + original.type, + Convert(original.field, get_child), + original.env_setting + }; +} + +template +template +auto OptionsParser::Convert( + typename OptionsParser::Implication original, + ChildOptions* (Options::* get_child)()) { + return Implication { + std::static_pointer_cast>( + Convert(original.target_field, get_child)), + original.target_value + }; +} + +template +template +void OptionsParser::Insert( + OptionsParser* child_options_parser, + ChildOptions* (Options::* get_child)()) { + aliases_.insert(child_options_parser->aliases_.begin(), + child_options_parser->aliases_.end()); + + for (const auto& pair : child_options_parser->options_) + options_.emplace(pair.first, Convert(pair.second, get_child)); + + for (const auto& pair : child_options_parser->implications_) + implications_.emplace(pair.first, Convert(pair.second, get_child)); +} + +inline std::string NotAllowedInEnvErr(const std::string& arg) { + return arg + " is not allowed in NODE_OPTIONS"; +} + +inline std::string RequiresArgumentErr(const std::string& arg) { + return arg + " requires an argument"; +} + +// We store some of the basic information around a single Parse call inside +// this struct, to separate storage of command line arguments and their +// handling. In particular, this makes it easier to introduce 'synthetic' +// arguments that get inserted by expanding option aliases. +struct ArgsInfo { + // Generally, the idea here is that the first entry in `*underlying` stores + // the "0th" argument (the program name), then `synthetic_args` are inserted, + // followed by the remainder of `*underlying`. + std::vector* underlying; + std::vector synthetic_args; + + std::vector* exec_args; + + ArgsInfo(std::vector* args, + std::vector* exec_args) + : underlying(args), exec_args(exec_args) {} + + size_t remaining() const { + // -1 to account for the program name. + return underlying->size() - 1 + synthetic_args.size(); + } + + bool empty() const { return remaining() == 0; } + const std::string& program_name() const { return underlying->at(0); } + + std::string& first() { + return synthetic_args.empty() ? underlying->at(1) : synthetic_args.front(); + } + + std::string pop_first() { + std::string ret = std::move(first()); + if (synthetic_args.empty()) { + // Only push arguments to `exec_args` that were also originally passed + // on the command line (i.e. not generated through alias expansion). + // '--' is a special case here since its purpose is to end `exec_argv`, + // which is why we do not include it. + if (exec_args != nullptr && first() != "--") + exec_args->push_back(ret); + underlying->erase(underlying->begin() + 1); + } else { + synthetic_args.erase(synthetic_args.begin()); + } + return ret; + } +}; + +template +void OptionsParser::Parse( + std::vector* const orig_args, + std::vector* const exec_args, + std::vector* const v8_args, + Options* const options, + OptionEnvvarSettings required_env_settings, + std::string* const error) { + ArgsInfo args(orig_args, exec_args); + + // The first entry is the process name. Make sure it ends up in the V8 argv, + // since V8::SetFlagsFromCommandLine() expects that to hold true for that + // array as well. + if (v8_args->empty()) + v8_args->push_back(args.program_name()); + + while (!args.empty() && error->empty()) { + if (args.first().size() <= 1 || args.first()[0] != '-') break; + + // We know that we're either going to consume this + // argument or fail completely. + const std::string arg = args.pop_first(); + + if (arg == "--") { + if (required_env_settings == kAllowedInEnvironment) + *error = NotAllowedInEnvErr("--"); + break; + } + + // Only allow --foo=bar notation for options starting with double dashes. + // (E.g. -e=a is not allowed as shorthand for --eval=a, which would + // otherwise be the result of alias expansion.) + const std::string::size_type equals_index = + arg[0] == '-' && arg[1] == '-' ? arg.find('=') : std::string::npos; + std::string name = + equals_index == std::string::npos ? arg : arg.substr(0, equals_index); + + // Store the 'original name' of the argument. This name differs from + // 'name' in that it contains a possible '=' sign and is not affected + // by alias expansion. + std::string original_name = name; + if (equals_index != std::string::npos) + original_name += '='; + + { + auto it = aliases_.end(); + // Expand aliases: + // - If `name` can be found in `aliases_`. + // - If `name` + '=' can be found in `aliases_`. + // - If `name` + " " can be found in `aliases_`, and we have + // a subsequent argument that does not start with '-' itself. + while ((it = aliases_.find(name)) != aliases_.end() || + (equals_index != std::string::npos && + (it = aliases_.find(name + '=')) != aliases_.end()) || + (!args.empty() && + !args.first().empty() && + args.first()[0] != '-' && + (it = aliases_.find(name + " ")) != aliases_.end())) { + const std::string prev_name = std::move(name); + const std::vector& expansion = it->second; + + // Use the first entry in the expansion as the new 'name'. + name = expansion.front(); + + if (expansion.size() > 1) { + // The other arguments, if any, are going to be handled later. + args.synthetic_args.insert( + args.synthetic_args.begin(), + expansion.begin() + 1, + expansion.end()); + } + + if (name == prev_name) break; + } + } + + auto it = options_.find(name); + + if (it == options_.end()) { + // We would assume that this is a V8 option if neither we nor any child + // parser knows about it, so we convert - to _ for + // canonicalization (since V8 accepts both) and look up again in order + // to find a match. + // TODO(addaleax): Make the canonicalization unconditional, i.e. allow + // both - and _ in Node's own options as well. + std::string::size_type index = 2; // Start after initial '--'. + while ((index = name.find('-', index + 1)) != std::string::npos) + name[index] = '_'; + it = options_.find(name); + } + + if ((it == options_.end() || + it->second.env_setting == kDisallowedInEnvironment) && + required_env_settings == kAllowedInEnvironment) { + *error = NotAllowedInEnvErr(original_name); + break; + } + + if (it == options_.end()) { + v8_args->push_back(arg); + continue; + } + + { + auto implications = implications_.equal_range(name); + for (auto it = implications.first; it != implications.second; ++it) + *it->second.target_field->Lookup(options) = it->second.target_value; + } + + const OptionInfo& info = it->second; + std::string value; + if (info.type != kBoolean && info.type != kNoOp && info.type != kV8Option) { + if (equals_index != std::string::npos) { + value = arg.substr(equals_index + 1); + if (value.empty()) { + missing_argument: + *error = RequiresArgumentErr(original_name); + break; + } + } else { + if (args.empty()) + goto missing_argument; + + value = args.pop_first(); + + if (!value.empty() && value[0] == '-') { + goto missing_argument; + } else { + if (!value.empty() && value[0] == '\\' && value[1] == '-') + value = value.substr(1); // Treat \- as escaping an -. + } + } + } + + switch (info.type) { + case kBoolean: + *std::static_pointer_cast>(info.field) + ->Lookup(options) = true; + break; + case kInteger: + *std::static_pointer_cast>(info.field) + ->Lookup(options) = std::atoll(value.c_str()); + break; + case kString: + *std::static_pointer_cast>(info.field) + ->Lookup(options) = value; + break; + case kStringList: + std::static_pointer_cast>>( + info.field)->Lookup(options)->emplace_back(std::move(value)); + break; + case kHostPort: + std::static_pointer_cast>(info.field) + ->Lookup(options)->Update(SplitHostPort(value, error)); + break; + case kNoOp: + break; + case kV8Option: + v8_args->push_back(arg); + break; + default: + UNREACHABLE(); + } + } +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_OPTIONS_INL_H_ diff --git a/src/node_options.cc b/src/node_options.cc new file mode 100644 index 00000000000000..78998fbea4cbd9 --- /dev/null +++ b/src/node_options.cc @@ -0,0 +1,221 @@ +#include "node_options-inl.h" +#include + +namespace node { + +DebugOptionsParser::DebugOptionsParser() { + AddOption("--inspect-port", &DebugOptions::host_port, + kAllowedInEnvironment); + AddAlias("--debug-port", "--inspect-port"); + + AddOption("--inspect", &DebugOptions::inspector_enabled, + kAllowedInEnvironment); + AddAlias("--inspect=", { "--inspect-port", "--inspect" }); + + AddOption("--debug", &DebugOptions::deprecated_debug); + AddAlias("--debug=", { "--inspect-port", "--debug" }); + + AddOption("--inspect-brk", &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); + Implies("--inspect-brk-node", "--inspect"); + AddAlias("--inspect-brk-node=", { "--inspect-port", "--inspect-brk-node" }); + + AddOption("--debug-brk", &DebugOptions::break_first_line); + Implies("--debug-brk", "--debug"); + AddAlias("--debug-brk=", { "--inspect-port", "--debug-brk" }); +} + +DebugOptionsParser DebugOptionsParser::instance; + +EnvironmentOptionsParser::EnvironmentOptionsParser() { + AddOption("--experimental-modules", &EnvironmentOptions::experimental_modules, + kAllowedInEnvironment); + AddOption("--experimental-repl-await", + &EnvironmentOptions::experimental_repl_await, + kAllowedInEnvironment); + AddOption("--experimental-vm-modules", + &EnvironmentOptions::experimental_vm_modules, + kAllowedInEnvironment); + AddOption("--experimental-worker", &EnvironmentOptions::experimental_worker, + kAllowedInEnvironment); + 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, + kAllowedInEnvironment); + AddOption("--no-deprecation", &EnvironmentOptions::no_deprecation, + kAllowedInEnvironment); + AddOption("--no-force-async-hooks-checks", + &EnvironmentOptions::no_force_async_hooks_checks, + kAllowedInEnvironment); + AddOption("--no-warnings", &EnvironmentOptions::no_warnings, + kAllowedInEnvironment); + AddOption("--pending-deprecation", &EnvironmentOptions::pending_deprecation, + kAllowedInEnvironment); + AddOption("--preserve-symlinks", &EnvironmentOptions::preserve_symlinks); + AddOption("--preserve-symlinks-main", + &EnvironmentOptions::preserve_symlinks_main); + AddOption("--prof-process", &EnvironmentOptions::prof_process); + AddOption("--redirect-warnings", &EnvironmentOptions::redirect_warnings, + kAllowedInEnvironment); + AddOption("--throw-deprecation", &EnvironmentOptions::throw_deprecation, + kAllowedInEnvironment); + AddOption("--trace-deprecation", &EnvironmentOptions::trace_deprecation, + kAllowedInEnvironment); + AddOption("--trace-sync-io", &EnvironmentOptions::trace_sync_io, + kAllowedInEnvironment); + AddOption("--trace-warnings", &EnvironmentOptions::trace_warnings, + kAllowedInEnvironment); + + AddOption("--check", &EnvironmentOptions::syntax_check_only); + AddAlias("-c", "--check"); + // This option is only so that we can tell --eval with an empty string from + // no eval at all. Having it not start with a dash makes it inaccessible + // from the parser itself, but available for using Implies(). + // TODO(addaleax): When moving --help over to something generated from the + // programmatic descriptions, this will need some special care. + // (See also [ssl_openssl_cert_store] below.) + AddOption("[has_eval_string]", &EnvironmentOptions::has_eval_string); + AddOption("--eval", &EnvironmentOptions::eval_string); + Implies("--eval", "[has_eval_string]"); + AddOption("--print", &EnvironmentOptions::print_eval); + AddAlias("-e", "--eval"); + AddAlias("--print ", "-pe"); + AddAlias("-pe", { "--print", "--eval" }); + AddAlias("-p", "--print"); + AddOption("--require", &EnvironmentOptions::preload_modules, + kAllowedInEnvironment); + AddAlias("-r", "--require"); + AddOption("--interactive", &EnvironmentOptions::force_repl); + AddAlias("-i", "--interactive"); + + AddOption("--napi-modules", NoOp {}, kAllowedInEnvironment); + AddOption("--expose-http2", NoOp {}, kAllowedInEnvironment); + AddOption("--expose_http2", NoOp {}, kAllowedInEnvironment); + + Insert(&DebugOptionsParser::instance, + &EnvironmentOptions::get_debug_options); +} + +EnvironmentOptionsParser EnvironmentOptionsParser::instance; + +PerIsolateOptionsParser::PerIsolateOptionsParser() { + AddOption("--track-heap-objects", &PerIsolateOptions::track_heap_objects, + kAllowedInEnvironment); + + // Explicitly add some V8 flags to mark them as allowed in NODE_OPTIONS. + AddOption("--abort_on_uncaught_exception", 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); +} + +PerIsolateOptionsParser PerIsolateOptionsParser::instance; + +PerProcessOptionsParser::PerProcessOptionsParser() { + AddOption("--title", &PerProcessOptions::title, kAllowedInEnvironment); + AddOption("--trace-event-categories", + &PerProcessOptions::trace_event_categories, + kAllowedInEnvironment); + AddOption("--trace-event-file-pattern", + &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, + kAllowedInEnvironment); + AddOption("--zero-fill-buffers", &PerProcessOptions::zero_fill_all_buffers, + kAllowedInEnvironment); + + AddOption("--security-reverts", &PerProcessOptions::security_reverts); + AddOption("--help", &PerProcessOptions::print_help); + AddAlias("-h", "--help"); + AddOption("--version", &PerProcessOptions::print_version); + AddAlias("-v", "--version"); + AddOption("--v8-options", &PerProcessOptions::print_v8_help); + +#ifdef NODE_HAVE_I18N_SUPPORT + AddOption("--icu-data-dir", &PerProcessOptions::icu_data_dir, + kAllowedInEnvironment); +#endif + +#if HAVE_OPENSSL + AddOption("--openssl-config", &PerProcessOptions::openssl_config, + kAllowedInEnvironment); + AddOption("--tls-cipher-list", &PerProcessOptions::tls_cipher_list, + kAllowedInEnvironment); + AddOption("--use-openssl-ca", &PerProcessOptions::use_openssl_ca, + kAllowedInEnvironment); + AddOption("--use-bundled-ca", &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, + kAllowedInEnvironment); + AddOption("--force-fips", &PerProcessOptions::force_fips_crypto, + kAllowedInEnvironment); +#endif +#endif + + Insert(&PerIsolateOptionsParser::instance, + &PerProcessOptions::get_per_isolate_options); +} + +PerProcessOptionsParser PerProcessOptionsParser::instance; + +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 int ParseAndValidatePort(const std::string& port, std::string* error) { + char* endptr; + errno = 0; + const long result = strtol(port.c_str(), &endptr, 10); // NOLINT(runtime/int) + if (errno != 0 || *endptr != '\0'|| + (result != 0 && result < 1024) || result > 65535) { + *error = "Port must be 0 or in range 1024 to 65535."; + } + return static_cast(result); +} + +HostPort SplitHostPort(const std::string& arg, std::string* error) { + // 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, -1 }; + + 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, -1 }; + } + } + return HostPort { "", ParseAndValidatePort(arg, error) }; + } + // Host and port found: + return HostPort { RemoveBrackets(arg.substr(0, colon)), + ParseAndValidatePort(arg.substr(colon + 1), error) }; +} +} // namespace node diff --git a/src/node_options.h b/src/node_options.h new file mode 100644 index 00000000000000..957e2b729d9ff4 --- /dev/null +++ b/src/node_options.h @@ -0,0 +1,356 @@ +#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" + +namespace node { + +struct HostPort { + std::string host_name; + int port; + + void Update(const HostPort& other) { + if (!other.host_name.empty()) host_name = other.host_name; + if (other.port >= 0) port = other.port; + } +}; + +// 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: + bool inspector_enabled = false; + bool deprecated_debug = false; + bool break_first_line = false; + bool break_node_first_line = false; + HostPort host_port = {"127.0.0.1", -1}; + enum { kDefaultInspectorPort = 9229 }; + + bool deprecated_invocation() const { + return deprecated_debug && + inspector_enabled && + break_first_line; + } + + bool invalid_invocation() const { + return deprecated_debug && !inspector_enabled; + } + + bool wait_for_connect() const { + return break_first_line || break_node_first_line; + } + + const std::string& host() { + return host_port.host_name; + } + + int port() { + return host_port.port < 0 ? kDefaultInspectorPort : host_port.port; + } +}; + +class EnvironmentOptions { + public: + std::shared_ptr debug_options { new DebugOptions() }; + bool experimental_modules = false; + bool experimental_repl_await = false; + bool experimental_vm_modules = false; + bool experimental_worker = false; + bool expose_internals = false; + bool no_deprecation = false; + bool no_force_async_hooks_checks = false; + bool no_warnings = false; + bool pending_deprecation = false; + bool preserve_symlinks = false; + bool preserve_symlinks_main = false; + bool prof_process = false; + std::string redirect_warnings; + bool throw_deprecation = false; + bool trace_deprecation = false; + bool trace_sync_io = false; + bool trace_warnings = false; + std::string userland_loader; + + bool syntax_check_only = false; + bool has_eval_string = false; + std::string eval_string; + bool print_eval = false; + bool force_repl = false; + + std::vector preload_modules; + + std::vector user_argv; + + inline DebugOptions* get_debug_options(); +}; + +class PerIsolateOptions { + public: + std::shared_ptr per_env { new EnvironmentOptions() }; + bool track_heap_objects = false; + + inline EnvironmentOptions* get_per_env_options(); +}; + +class PerProcessOptions { + public: + std::shared_ptr per_isolate { new PerIsolateOptions() }; + + std::string title; + std::string trace_event_categories; + std::string trace_event_file_pattern = "node_trace.${rotation}.log"; + int64_t v8_thread_pool_size = 4; + bool zero_fill_all_buffers = false; + + std::vector security_reverts; + bool print_help = false; + bool print_v8_help = false; + bool print_version = false; + +#ifdef NODE_HAVE_I18N_SUPPORT + std::string icu_data_dir; +#endif + + // TODO(addaleax): Some of these could probably be per-Environment. +#if HAVE_OPENSSL + std::string openssl_config; + std::string tls_cipher_list = DEFAULT_CIPHER_LIST_CORE; +#ifdef NODE_OPENSSL_CERT_STORE + bool ssl_openssl_cert_store = true; +#else + bool ssl_openssl_cert_store = false; +#endif + bool use_openssl_ca = false; + bool use_bundled_ca = false; +#if NODE_FIPS_MODE + bool enable_fips_crypto = false; + bool force_fips_crypto = false; +#endif +#endif + + inline PerIsolateOptions* get_per_isolate_options(); +}; + +// The actual options parser, as opposed to the structs containing them: + +HostPort SplitHostPort(const std::string& arg, std::string* error); + +enum OptionEnvvarSettings { + kAllowedInEnvironment, + kDisallowedInEnvironment +}; + +enum OptionType { + kNoOp, + kV8Option, + kBoolean, + kInteger, + kString, + kHostPort, + kStringList, +}; + +template +class OptionsParser { + public: + virtual ~OptionsParser() {} + + typedef Options TargetType; + + struct NoOp {}; + struct V8Option {}; + + // TODO(addaleax): A lot of the `std::string` usage here could be reduced + // to simple `const char*`s if it's reasonable to expect the values to be + // known at compile-time. + + // 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). + void AddOption(const std::string& name, + bool Options::* field, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + void AddOption(const std::string& name, + int64_t Options::* field, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + void AddOption(const std::string& name, + std::string Options::* field, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + void AddOption(const std::string& name, + std::vector Options::* field, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + void AddOption(const std::string& name, + HostPort Options::* field, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + void AddOption(const std::string& name, + NoOp no_op_tag, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + void AddOption(const std::string& name, + V8Option v8_option_tag, + OptionEnvvarSettings env_setting = kDisallowedInEnvironment); + + // 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 std::string& from, const std::string& to); + void AddAlias(const std::string& from, const std::vector& to); + void AddAlias(const std::string& from, + const std::initializer_list& to); + + // Add implications from some arbitary option to a boolean one, either + // in a way that makes `from` set `to` to true or to false. + void Implies(const std::string& from, const std::string& to); + void ImpliesNot(const std::string& from, const std::string& 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(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. + virtual void Parse(std::vector* const args, + std::vector* const exec_args, + std::vector* const v8_args, + Options* const options, + OptionEnvvarSettings required_env_settings, + std::string* const error); + + 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() {} + virtual void* LookupImpl(Options* options) const = 0; + }; + + // Represents a field of type T within `Options`. + template + class OptionField : public BaseOptionField { + public: + typedef T Type; + + T* Lookup(Options* options) const { + return static_cast(this->LookupImpl(options)); + } + }; + + // Represents a field of type T withing `Options` that can be looked up + // as a C++ member field. + template + class SimpleOptionField : public OptionField { + public: + explicit SimpleOptionField(T Options::* field) : field_(field) {} + void* LookupImpl(Options* options) const override { + return static_cast(&(options->*field_)); + } + + private: + T Options::* field_; + }; + + // 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. + struct OptionInfo { + OptionType type; + std::shared_ptr field; + OptionEnvvarSettings env_setting; + }; + + // An implied option is composed of the information on where to store a + // specific boolean value (if another specific option is encountered). + struct Implication { + 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; +}; + +class DebugOptionsParser : public OptionsParser { + public: + DebugOptionsParser(); + + static DebugOptionsParser instance; +}; + +class EnvironmentOptionsParser : public OptionsParser { + public: + EnvironmentOptionsParser(); + + static EnvironmentOptionsParser instance; +}; + +class PerIsolateOptionsParser : public OptionsParser { + public: + PerIsolateOptionsParser(); + + static PerIsolateOptionsParser instance; +}; + +class PerProcessOptionsParser : public OptionsParser { + public: + PerProcessOptionsParser(); + + static PerProcessOptionsParser instance; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_OPTIONS_H_ diff --git a/src/node_worker.cc b/src/node_worker.cc index 4f210203003eed..d70cb42dfff44a 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -104,7 +104,9 @@ Worker::Worker(Environment* env, Local wrap) env_->set_worker_context(this); env_->set_thread_id(thread_id_); - env_->Start(0, nullptr, 0, nullptr, env->profiler_idle_notifier_started()); + env_->Start(std::vector{}, + std::vector{}, + env->profiler_idle_notifier_started()); } // The new isolate won't be bothered on this thread again.