#ifndef SRC_NODE_OPTIONS_INL_H_ #define SRC_NODE_OPTIONS_INL_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "node_options.h" #include "util.h" #include namespace node { PerIsolateOptions* PerProcessOptions::get_per_isolate_options() { return per_isolate.get(); } DebugOptions* EnvironmentOptions::get_debug_options() { return &debug_options_; } const DebugOptions& EnvironmentOptions::debug_options() const { return debug_options_; } EnvironmentOptions* PerIsolateOptions::get_per_env_options() { return per_env.get(); } namespace options_parser { template void OptionsParser::AddOption(const char* name, const char* help_text, bool Options::* field, OptionEnvvarSettings env_setting) { options_.emplace(name, OptionInfo{kBoolean, std::make_shared>(field), env_setting, help_text}); } template void OptionsParser::AddOption(const char* name, const char* help_text, uint64_t Options::* field, OptionEnvvarSettings env_setting) { options_.emplace( name, OptionInfo{kUInteger, std::make_shared>(field), env_setting, help_text}); } template void OptionsParser::AddOption(const char* name, const char* help_text, int64_t Options::* field, OptionEnvvarSettings env_setting) { options_.emplace( name, OptionInfo{kInteger, std::make_shared>(field), env_setting, help_text}); } template void OptionsParser::AddOption(const char* name, const char* help_text, std::string Options::* field, OptionEnvvarSettings env_setting) { options_.emplace( name, OptionInfo{kString, std::make_shared>(field), env_setting, help_text}); } template void OptionsParser::AddOption( const char* name, const char* help_text, std::vector Options::* field, OptionEnvvarSettings env_setting) { options_.emplace(name, OptionInfo { kStringList, std::make_shared>>(field), env_setting, help_text }); } template void OptionsParser::AddOption(const char* name, const char* help_text, HostPort Options::* field, OptionEnvvarSettings env_setting) { options_.emplace( name, OptionInfo{kHostPort, std::make_shared>(field), env_setting, help_text}); } template void OptionsParser::AddOption(const char* name, const char* help_text, NoOp no_op_tag, OptionEnvvarSettings env_setting) { options_.emplace(name, OptionInfo{kNoOp, nullptr, env_setting, help_text}); } template void OptionsParser::AddOption(const char* name, const char* help_text, V8Option v8_option_tag, OptionEnvvarSettings env_setting) { options_.emplace(name, OptionInfo{kV8Option, nullptr, env_setting, help_text}); } template void OptionsParser::AddAlias(const char* from, const char* to) { aliases_[from] = { to }; } template void OptionsParser::AddAlias(const char* from, const std::vector& to) { aliases_[from] = to; } template void OptionsParser::AddAlias( const char* from, const std::initializer_list& to) { AddAlias(from, std::vector(to)); } template void OptionsParser::Implies(const char* from, const char* to) { auto it = options_.find(to); CHECK_NE(it, options_.end()); CHECK_EQ(it->second.type, kBoolean); implications_.emplace(from, Implication { it->second.field, true }); } template void OptionsParser::ImpliesNot(const char* from, const char* to) { auto it = options_.find(to); CHECK_NE(it, options_.end()); CHECK_EQ(it->second.type, kBoolean); implications_.emplace(from, Implication { it->second.field, false }); } template template auto OptionsParser::Convert( std::shared_ptr original, ChildOptions* (Options::* get_child)()) { // If we have a field on ChildOptions, and we want to access it from an // Options instance, we call get_child() on the original Options and then // access it, i.e. this class implements a kind of function chaining. struct AdaptedField : BaseOptionField { void* LookupImpl(Options* options) const override { return original->LookupImpl((options->*get_child)()); } AdaptedField( std::shared_ptr original, ChildOptions* (Options::* get_child)()) : original(original), get_child(get_child) {} std::shared_ptr original; ChildOptions* (Options::* get_child)(); }; return std::shared_ptr( new AdaptedField(original, get_child)); } template template auto OptionsParser::Convert( typename OptionsParser::OptionInfo original, ChildOptions* (Options::* get_child)()) { return OptionInfo{original.type, Convert(original.field, get_child), original.env_setting, original.help_text}; } template template auto OptionsParser::Convert( typename OptionsParser::Implication original, ChildOptions* (Options::* get_child)()) { return Implication { Convert(original.target_field, get_child), original.target_value }; } template template void OptionsParser::Insert( const OptionsParser& child_options_parser, ChildOptions* (Options::* get_child)()) { aliases_.insert(std::begin(child_options_parser.aliases_), std::end(child_options_parser.aliases_)); for (const auto& pair : child_options_parser.options_) options_.emplace(pair.first, Convert(pair.second, get_child)); for (const auto& pair : child_options_parser.implications_) implications_.emplace(pair.first, Convert(pair.second, get_child)); } inline std::string NotAllowedInEnvErr(const std::string& arg) { return arg + " is not allowed in NODE_OPTIONS"; } inline std::string RequiresArgumentErr(const std::string& arg) { return arg + " requires an argument"; } // We store some of the basic information around a single Parse call inside // this struct, to separate storage of command line arguments and their // handling. In particular, this makes it easier to introduce 'synthetic' // arguments that get inserted by expanding option aliases. struct ArgsInfo { // Generally, the idea here is that the first entry in `*underlying` stores // the "0th" argument (the program name), then `synthetic_args` are inserted, // followed by the remainder of `*underlying`. std::vector* underlying; std::vector synthetic_args; std::vector* exec_args; ArgsInfo(std::vector* args, std::vector* exec_args) : underlying(args), exec_args(exec_args) {} size_t remaining() const { // -1 to account for the program name. return underlying->size() - 1 + synthetic_args.size(); } bool empty() const { return remaining() == 0; } const std::string& program_name() const { return underlying->at(0); } std::string& first() { return synthetic_args.empty() ? underlying->at(1) : synthetic_args.front(); } std::string pop_first() { std::string ret = std::move(first()); if (synthetic_args.empty()) { // Only push arguments to `exec_args` that were also originally passed // on the command line (i.e. not generated through alias expansion). // '--' is a special case here since its purpose is to end `exec_argv`, // which is why we do not include it. if (exec_args != nullptr && ret != "--") exec_args->push_back(ret); underlying->erase(underlying->begin() + 1); } else { synthetic_args.erase(synthetic_args.begin()); } return ret; } }; template void OptionsParser::Parse( std::vector* const orig_args, std::vector* const exec_args, std::vector* const v8_args, Options* const options, OptionEnvvarSettings required_env_settings, std::vector* const errors) const { 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() && errors->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) errors->push_back(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 += '='; // Normalize by replacing `_` with `-` in options. for (std::string::size_type i = 2; i < name.size(); ++i) { if (name[i] == '_') name[i] = '-'; } { auto it = aliases_.end(); // Expand aliases: // - If `name` can be found in `aliases_`. // - If `name` + '=' can be found in `aliases_`. // - If `name` + " " can be found in `aliases_`, and we have // a subsequent argument that does not start with '-' itself. while ((it = aliases_.find(name)) != aliases_.end() || (equals_index != std::string::npos && (it = aliases_.find(name + '=')) != aliases_.end()) || (!args.empty() && !args.first().empty() && args.first()[0] != '-' && (it = aliases_.find(name + " ")) != aliases_.end())) { const std::string prev_name = std::move(name); const std::vector& expansion = it->second; // Use the first entry in the expansion as the new 'name'. name = expansion.front(); if (expansion.size() > 1) { // The other arguments, if any, are going to be handled later. args.synthetic_args.insert( args.synthetic_args.begin(), expansion.begin() + 1, expansion.end()); } if (name == prev_name) break; } } auto it = options_.find(name); if ((it == options_.end() || it->second.env_setting == kDisallowedInEnvironment) && required_env_settings == kAllowedInEnvironment) { errors->push_back(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->template 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: errors->push_back(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: *Lookup(info.field, options) = true; break; case kInteger: *Lookup(info.field, options) = std::atoll(value.c_str()); break; case kUInteger: *Lookup(info.field, options) = std::stoull(value); break; case kString: *Lookup(info.field, options) = value; break; case kStringList: Lookup>(info.field, options) ->emplace_back(std::move(value)); break; case kHostPort: Lookup(info.field, options) ->Update(SplitHostPort(value, errors)); break; case kNoOp: break; case kV8Option: v8_args->push_back(arg); break; default: UNREACHABLE(); } } options->CheckOptions(errors); } } // namespace options_parser } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_NODE_OPTIONS_INL_H_