diff options
author | Anna Henningsen <anna@addaleax.net> | 2018-08-10 03:45:28 +0300 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2018-08-23 00:37:19 +0300 |
commit | 29a71bae40ffa0bbc8ba6b2bdf051a09987da7f7 (patch) | |
tree | 1d66e5b012218ede32c5219d9918d2e2a897ee7b | |
parent | 92880f31da1eca98a42e0f61708b10d9d8d83955 (diff) |
src: refactor options parsing
This is a major refactor of our Node’s parser. See `node_options.cc`
for how it is used, and `node_options-inl.h` for the bulk
of its implementation.
Unfortunately, the implementation has come to have some
complexity, in order to meet the following goals:
- Make it easy to *use* for defining or changing options.
- Keep it (mostly) backwards-compatible.
- No tests were harmed as part of this commit.
- Be as consistent as possible.
- In particular, options can now generally accept arguments
through both `--foo=bar` notation and `--foo bar` notation.
We were previously very inconsistent on this point.
- Separate into different levels of scope, namely
per-process (global), per-Isolate and per-Environment
(+ debug options).
- Allow programmatic accessibility in the future.
- This includes a possible expansion for `--help` output.
This commit also leaves a number of `TODO` comments, mostly for
improving consistency even more (possibly with having to modify
tests), improving embedder support, as well as removing pieces of
exposed configuration variables that should never have become
part of the public API but unfortunately are at this point.
PR-URL: https://github.com/nodejs/node/pull/22392
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
-rw-r--r-- | node.gyp | 5 | ||||
-rw-r--r-- | src/env-inl.h | 8 | ||||
-rw-r--r-- | src/env.cc | 26 | ||||
-rw-r--r-- | src/env.h | 13 | ||||
-rw-r--r-- | src/inspector_agent.cc | 13 | ||||
-rw-r--r-- | src/inspector_agent.h | 8 | ||||
-rw-r--r-- | src/inspector_io.cc | 7 | ||||
-rw-r--r-- | src/inspector_io.h | 10 | ||||
-rw-r--r-- | src/inspector_js_api.cc | 4 | ||||
-rw-r--r-- | src/node.cc | 818 | ||||
-rw-r--r-- | src/node.h | 9 | ||||
-rw-r--r-- | src/node_buffer.h | 1 | ||||
-rw-r--r-- | src/node_config.cc | 48 | ||||
-rw-r--r-- | src/node_constants.cc | 6 | ||||
-rw-r--r-- | src/node_constants.h | 4 | ||||
-rw-r--r-- | src/node_crypto.cc | 10 | ||||
-rw-r--r-- | src/node_debug_options.cc | 142 | ||||
-rw-r--r-- | src/node_debug_options.h | 42 | ||||
-rw-r--r-- | src/node_i18n.h | 2 | ||||
-rw-r--r-- | src/node_internals.h | 66 | ||||
-rw-r--r-- | src/node_options-inl.h | 422 | ||||
-rw-r--r-- | src/node_options.cc | 219 | ||||
-rw-r--r-- | src/node_options.h | 356 | ||||
-rw-r--r-- | src/node_worker.cc | 4 |
24 files changed, 1354 insertions, 889 deletions
@@ -343,7 +343,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', @@ -351,6 +350,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', @@ -407,7 +407,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', @@ -415,6 +414,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 3bca20c81ce..28c30e03543 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -564,6 +564,14 @@ Environment::file_handle_read_wrap_freelist() { return file_handle_read_wrap_freelist_; } +inline std::shared_ptr<EnvironmentOptions> Environment::options() { + return options_; +} + +inline std::shared_ptr<PerIsolateOptions> IsolateData::options() { + return options_; +} + void Environment::CreateImmediate(native_immediate_callback cb, void* data, v8::Local<v8::Object> obj, diff --git a/src/env.cc b/src/env.cc index 97405fe4b10..9c854f6f3ff 100644 --- a/src/env.cc +++ b/src/env.cc @@ -48,6 +48,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. // @@ -136,9 +138,6 @@ Environment::Environment(IsolateData* isolate_data, makecallback_cntr_(0), should_abort_on_uncaught_toggle_(isolate_, 1), trace_category_state_(isolate_, kTraceCategoryCount), -#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), @@ -148,6 +147,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<inspector::Agent>(new inspector::Agent(this)); +#endif + AssignToContext(context, ContextInfo("")); if (tracing_agent_writer_ != nullptr) { @@ -211,10 +223,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<std::string>& args, + const std::vector<std::string>& exec_args, bool start_profiler_idle_notifier) { HandleScope handle_scope(isolate()); Context::Scope context_scope(context()); @@ -260,7 +270,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 b393670b21c..556a09754fc 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 <list> @@ -365,6 +366,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<PerIsolateOptions> options(); #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) @@ -398,6 +400,7 @@ class IsolateData { uv_loop_t* const event_loop_; uint32_t* const zero_fill_field_; MultiIsolatePlatform* platform_; + std::shared_ptr<PerIsolateOptions> options_; DISALLOW_COPY_AND_ASSIGN(IsolateData); }; @@ -582,10 +585,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<std::string>& args, + const std::vector<std::string>& exec_args, bool start_profiler_idle_notifier); typedef void (*HandleCleanupCb)(Environment* env, @@ -882,6 +883,8 @@ class Environment { v8::EmbedderGraph* graph, void* data); + inline std::shared_ptr<EnvironmentOptions> options(); + private: inline void CreateImmediate(native_immediate_callback cb, void* data, @@ -912,6 +915,8 @@ class Environment { size_t makecallback_cntr_; std::vector<double> destroy_async_id_list_; + std::shared_ptr<EnvironmentOptions> options_; + AliasedBuffer<uint32_t, v8::Uint32Array> 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 0b99217d0a3..adc32f1bcd4 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -608,11 +608,14 @@ class NodeInspectorClient : public V8InspectorClient { std::unique_ptr<MainThreadInterface> 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<DebugOptions> options) { path_ = path; debug_options_ = options; client_ = std::make_shared<NodeInspectorClient>(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> 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 dcd6e13aba2..4e32d3ef1a9 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<DebugOptions> 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<DebugOptions> options() { return debug_options_; } void ContextCreated(v8::Local<v8::Context> context, const ContextInfo& info); private: @@ -109,7 +109,7 @@ class Agent { // Interface for transports, e.g. WebSocket server std::unique_ptr<InspectorIo> io_; std::string path_; - DebugOptions debug_options_; + std::shared_ptr<DebugOptions> 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 41fea546a83..da44d55d06e 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> InspectorIo::Start( std::shared_ptr<MainThreadHandle> main_thread, const std::string& path, - const DebugOptions& options) { + std::shared_ptr<DebugOptions> options) { auto io = std::unique_ptr<InspectorIo>( new InspectorIo(main_thread, path, options)); if (io->request_queue_->Expired()) { // Thread is not running @@ -253,7 +253,7 @@ std::unique_ptr<InspectorIo> InspectorIo::Start( InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread, const std::string& path, - const DebugOptions& options) + std::shared_ptr<DebugOptions> 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 7c43d212f04..2b9f0acd483 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<InspectorIo> Start( std::shared_ptr<MainThreadHandle> main_thread, const std::string& path, - const DebugOptions& options); + std::shared_ptr<DebugOptions> 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<std::string> GetTargetIds() const; private: InspectorIo(std::shared_ptr<MainThreadHandle> handle, - const std::string& path, const DebugOptions& options); + const std::string& path, + std::shared_ptr<DebugOptions> 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<RequestQueue> request_queue_; - const DebugOptions options_; + std::shared_ptr<DebugOptions> 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 4e95598d3a0..b8c78ba5ebd 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -242,12 +242,12 @@ void Open(const FunctionCallbackInfo<Value>& args) { if (args.Length() > 0 && args[0]->IsUint32()) { uint32_t port = args[0]->Uint32Value(); - agent->options().set_port(static_cast<int>(port)); + agent->options()->host_port.port = port; } if (args.Length() > 1 && args[1]->IsString()) { Utf8Value host(env->isolate(), args[1].As<String>()); - 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 33a68b37a46..b6002417104 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<std::string> 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<PerProcessOptions> 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<DebugOptions> 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<tracing::AsyncTraceWriter>( - new tracing::NodeTraceWriter(trace_file_pattern)), + new tracing::NodeTraceWriter( + per_process_opts->trace_event_file_pattern)), tracing::Agent::kUseDefaultCategories); } } @@ -1844,7 +1752,7 @@ static void EnvSetter(Local<Name> property, Local<Value> value, const PropertyCallbackInfo<Value>& 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, @@ -2034,11 +1942,11 @@ static Local<Object> GetFeatures(Environment* env) { static void DebugPortGetter(Local<Name> property, const PropertyCallbackInfo<Value>& 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(); } @@ -2050,8 +1958,10 @@ static void DebugPortGetter(Local<Name> property, static void DebugPortSetter(Local<Name> property, Local<Value> value, const PropertyCallbackInfo<void>& 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); } @@ -2081,10 +1991,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<std::string>& args, + const std::vector<std::string>& exec_args) { HandleScope scope(env->isolate()); Local<Object> process = env->process_object(); @@ -2222,18 +2130,22 @@ void SetupProcessObject(Environment* env, #endif // process.argv - Local<Array> 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<Array> 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<Array> 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<Array> 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); @@ -2262,29 +2174,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<std::string> preload_modules = + std::move(env->options()->preload_modules); if (!preload_modules.empty()) { Local<Array> array = Array::New(env->isolate()); for (unsigned int i = 0; i < preload_modules.size(); ++i) { @@ -2302,22 +2218,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())); } @@ -2327,35 +2244,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())); } @@ -2379,7 +2296,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"), @@ -2561,7 +2478,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 @@ -2742,359 +2660,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", - "--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<unsigned int>(*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, "-") == 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<int>(new_argc); -} - - static void StartInspector(Environment* env, const char* path, - DebugOptions debug_options) { + std::shared_ptr<DebugOptions> debug_options) { #if HAVE_INSPECTOR CHECK(!env->inspector_agent()->IsListening()); v8_platform.StartInspector(env, path, debug_options); @@ -3327,26 +2894,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<std::string>* args, + std::vector<std::string>* 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<std::string> 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__ @@ -3358,28 +2986,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<char**>(v8_argv), true); + std::vector<char*> 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<std::string>* argv, + std::vector<std::string>* exec_argv) { // Initialize prog_start_time to get relative uptime. prog_start_time = static_cast<double>(uv_now(uv_default_loop())); @@ -3396,78 +3036,80 @@ void Init(int* argc, V8::SetFlagsFromString(NODE_V8_OPTIONS, sizeof(NODE_V8_OPTIONS) - 1); #endif + std::shared_ptr<EnvironmentOptions> 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<std::string> 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 @@ -3478,6 +3120,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<std::string> argv_(argv, argv + *argc); // NOLINT + std::vector<std::string> 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<const char*>(*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(); @@ -3602,9 +3265,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<std::string> args(argv, argv + argc); + std::vector<std::string> 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; } @@ -3657,23 +3324,27 @@ Local<Context> 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<std::string>& args, + const std::vector<std::string>& exec_args) { HandleScope handle_scope(isolate); Local<Context> 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(); } @@ -3684,7 +3355,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); @@ -3759,8 +3430,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<std::string>& args, + const std::vector<std::string>& exec_args) { std::unique_ptr<ArrayBufferAllocator, decltype(&FreeArrayBufferAllocator)> allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator); Isolate* const isolate = NewIsolate(allocator.get()); @@ -3785,11 +3456,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); } { @@ -3813,11 +3486,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<const char**>(argv), &exec_argc, &exec_argv); + std::vector<std::string> args(argv, argv + argc); + std::vector<std::string> exec_args; + // This needs to run *before* V8::Initialize(). + Init(&args, &exec_args); #if HAVE_OPENSSL { @@ -3835,12 +3507,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(); @@ -3853,9 +3526,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 a94612eb0b9..ab82be7ac4d 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<v8::Context> context, int argc, diff --git a/src/node_buffer.h b/src/node_buffer.h index b4aa12cbcfa..e8d306e7dd6 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 62fd4ef81e0..d34269912e4 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<Object> 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<Object> 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<Object> 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<Object> debugOptions = Object::New(isolate); - READONLY_PROPERTY(target, "debugOptions", debugOptions); + std::shared_ptr<DebugOptions> debug_options = env->options()->debug_options; + Local<Object> 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 f1468ff7ca0..b6c7bf37a3a 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<Object> target) { @@ -1240,7 +1236,7 @@ void DefineCryptoConstants(Local<Object> 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 1de420e2def..6f73fb4d7d9 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<v8::Object> target); } // namespace node diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 7f5544b8f22..11ed4a3f192 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 { @@ -5094,14 +5096,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(); @@ -5119,6 +5121,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(); @@ -5181,6 +5186,7 @@ void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) { } void SetFipsCrypto(const FunctionCallbackInfo<Value>& 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 5fc29059ddc..00000000000 --- a/src/node_debug_options.cc +++ /dev/null @@ -1,142 +0,0 @@ -#include "node_debug_options.h" - -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#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<int>(result); -} - -std::pair<std::string, int> 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<std::string, int> 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 98922ab099a..00000000000 --- 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 <string> - -// 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 70a0c79f76c..7faa5e57ef2 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 968d229f100..60630953c78 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 <stdint.h> @@ -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<PerProcessOptions> per_process_opts; // Forward declaration class Environment; @@ -415,10 +357,8 @@ inline v8::Local<v8::Value> FillGlobalStatsArray(Environment* env, void SetupBootstrapObject(Environment* env, v8::Local<v8::Object> bootstrapper); void SetupProcessObject(Environment* env, - int argc, - const char* const* argv, - int exec_argc, - const char* const* exec_argv); + const std::vector<std::string>& args, + const std::vector<std::string>& exec_args); // Call _register<module_name> 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 00000000000..e610cd50d11 --- /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 <cstdlib> + +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 <typename Options> +void OptionsParser<Options>::AddOption(const std::string& name, + bool Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kBoolean, + std::make_shared<SimpleOptionField<bool>>(field), + env_setting + }); +} + +template <typename Options> +void OptionsParser<Options>::AddOption(const std::string& name, + int64_t Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kInteger, + std::make_shared<SimpleOptionField<int64_t>>(field), + env_setting + }); +} + +template <typename Options> +void OptionsParser<Options>::AddOption(const std::string& name, + std::string Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kString, + std::make_shared<SimpleOptionField<std::string>>(field), + env_setting + }); +} + +template <typename Options> +void OptionsParser<Options>::AddOption( + const std::string& name, + std::vector<std::string> Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kStringList, + std::make_shared<SimpleOptionField<std::vector<std::string>>>(field), + env_setting + }); +} + +template <typename Options> +void OptionsParser<Options>::AddOption(const std::string& name, + HostPort Options::* field, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { + kHostPort, + std::make_shared<SimpleOptionField<HostPort>>(field), + env_setting + }); +} + +template <typename Options> +void OptionsParser<Options>::AddOption(const std::string& name, NoOp no_op_tag, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { kNoOp, nullptr, env_setting }); +} + +template <typename Options> +void OptionsParser<Options>::AddOption(const std::string& name, + V8Option v8_option_tag, + OptionEnvvarSettings env_setting) { + options_.emplace(name, OptionInfo { kV8Option, nullptr, env_setting }); +} + +template <typename Options> +void OptionsParser<Options>::AddAlias(const std::string& from, + const std::string& to) { + aliases_[from] = { to }; +} + +template <typename Options> +void OptionsParser<Options>::AddAlias(const std::string& from, + const std::vector<std::string>& to) { + aliases_[from] = to; +} + +template <typename Options> +void OptionsParser<Options>::AddAlias( + const std::string& from, + const std::initializer_list<std::string>& to) { + AddAlias(from, std::vector<std::string>(to)); +} + +template <typename Options> +void OptionsParser<Options>::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<OptionField<bool>>(it->second.field), true + }); +} + +template <typename Options> +void OptionsParser<Options>::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<OptionField<bool>>(it->second.field), false + }); +} + +template <typename Options> +template <typename OriginalField, typename ChildOptions> +auto OptionsParser<Options>::Convert( + std::shared_ptr<OriginalField> 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<OriginalField> original, + ChildOptions* (Options::* get_child)()) + : original(original), get_child(get_child) {} + + std::shared_ptr<OriginalField> original; + ChildOptions* (Options::* get_child)(); + }; + + return std::shared_ptr<BaseOptionField>( + new AdaptedField(original, get_child)); +} +template <typename Options> +template <typename ChildOptions> +auto OptionsParser<Options>::Convert( + typename OptionsParser<ChildOptions>::OptionInfo original, + ChildOptions* (Options::* get_child)()) { + return OptionInfo { + original.type, + Convert(original.field, get_child), + original.env_setting + }; +} + +template <typename Options> +template <typename ChildOptions> +auto OptionsParser<Options>::Convert( + typename OptionsParser<ChildOptions>::Implication original, + ChildOptions* (Options::* get_child)()) { + return Implication { + std::static_pointer_cast<OptionField<bool>>( + Convert(original.target_field, get_child)), + original.target_value + }; +} + +template <typename Options> +template <typename ChildOptions> +void OptionsParser<Options>::Insert( + OptionsParser<ChildOptions>* 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<std::string>* underlying; + std::vector<std::string> synthetic_args; + + std::vector<std::string>* exec_args; + + ArgsInfo(std::vector<std::string>* args, + std::vector<std::string>* 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 <typename Options> +void OptionsParser<Options>::Parse( + std::vector<std::string>* const orig_args, + std::vector<std::string>* const exec_args, + std::vector<std::string>* 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` + " <arg>" 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 + " <arg>")) != aliases_.end())) { + const std::string prev_name = std::move(name); + const std::vector<std::string>& 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<OptionField<bool>>(info.field) + ->Lookup(options) = true; + break; + case kInteger: + *std::static_pointer_cast<OptionField<int64_t>>(info.field) + ->Lookup(options) = std::atoll(value.c_str()); + break; + case kString: + *std::static_pointer_cast<OptionField<std::string>>(info.field) + ->Lookup(options) = value; + break; + case kStringList: + std::static_pointer_cast<OptionField<std::vector<std::string>>>( + info.field)->Lookup(options)->emplace_back(std::move(value)); + break; + case kHostPort: + std::static_pointer_cast<OptionField<HostPort>>(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 00000000000..b10de9ef28a --- /dev/null +++ b/src/node_options.cc @@ -0,0 +1,219 @@ +#include "node_options-inl.h" +#include <errno.h> + +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 <arg>", "-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); + + 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<int>(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 00000000000..957e2b729d9 --- /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 <string> +#include <vector> +#include <unordered_map> +#include <memory> +#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<DebugOptions> 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<std::string> preload_modules; + + std::vector<std::string> user_argv; + + inline DebugOptions* get_debug_options(); +}; + +class PerIsolateOptions { + public: + std::shared_ptr<EnvironmentOptions> per_env { new EnvironmentOptions() }; + bool track_heap_objects = false; + + inline EnvironmentOptions* get_per_env_options(); +}; + +class PerProcessOptions { + public: + std::shared_ptr<PerIsolateOptions> 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<std::string> 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 <typename Options> +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<std::string> 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 <arg>", 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<std::string>& to); + void AddAlias(const std::string& from, + const std::initializer_list<std::string>& 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 <typename ChildOptions> + void Insert(OptionsParser<ChildOptions>* 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<std::string>* const args, + std::vector<std::string>* const exec_args, + std::vector<std::string>* 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 <typename T> + class OptionField : public BaseOptionField { + public: + typedef T Type; + + T* Lookup(Options* options) const { + return static_cast<T*>(this->LookupImpl(options)); + } + }; + + // Represents a field of type T withing `Options` that can be looked up + // as a C++ member field. + template <typename T> + class SimpleOptionField : public OptionField<T> { + public: + explicit SimpleOptionField(T Options::* field) : field_(field) {} + void* LookupImpl(Options* options) const override { + return static_cast<void*>(&(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<BaseOptionField> 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<OptionField<bool>> 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 <typename OriginalField, typename ChildOptions> + static auto Convert( + std::shared_ptr<OriginalField> original, + ChildOptions* (Options::* get_child)()); + template <typename ChildOptions> + static auto Convert( + typename OptionsParser<ChildOptions>::OptionInfo original, + ChildOptions* (Options::* get_child)()); + template <typename ChildOptions> + static auto Convert( + typename OptionsParser<ChildOptions>::Implication original, + ChildOptions* (Options::* get_child)()); + + std::unordered_map<std::string, OptionInfo> options_; + std::unordered_map<std::string, std::vector<std::string>> aliases_; + std::unordered_multimap<std::string, Implication> implications_; + + template <typename OtherOptions> + friend class OptionsParser; +}; + +class DebugOptionsParser : public OptionsParser<DebugOptions> { + public: + DebugOptionsParser(); + + static DebugOptionsParser instance; +}; + +class EnvironmentOptionsParser : public OptionsParser<EnvironmentOptions> { + public: + EnvironmentOptionsParser(); + + static EnvironmentOptionsParser instance; +}; + +class PerIsolateOptionsParser : public OptionsParser<PerIsolateOptions> { + public: + PerIsolateOptionsParser(); + + static PerIsolateOptionsParser instance; +}; + +class PerProcessOptionsParser : public OptionsParser<PerProcessOptions> { + 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 06edb968041..80deddedde6 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -104,7 +104,9 @@ Worker::Worker(Environment* env, Local<Object> 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::string>{}, + std::vector<std::string>{}, + env->profiler_idle_notifier_started()); } // The new isolate won't be bothered on this thread again. |