• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef SRC_NODE_OPTIONS_INL_H_
2 #define SRC_NODE_OPTIONS_INL_H_
3 
4 #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5 
6 #include <cstdlib>
7 #include "node_options.h"
8 #include "util.h"
9 
10 namespace node {
11 
get_per_isolate_options()12 PerIsolateOptions* PerProcessOptions::get_per_isolate_options() {
13   return per_isolate.get();
14 }
15 
get_per_env_options()16 EnvironmentOptions* PerIsolateOptions::get_per_env_options() {
17   return per_env.get();
18 }
19 
20 namespace options_parser {
21 
22 template <typename Options>
AddOption(const char * name,const char * help_text,bool Options::* field,OptionEnvvarSettings env_setting,bool default_is_true)23 void OptionsParser<Options>::AddOption(const char* name,
24                                        const char* help_text,
25                                        bool Options::* field,
26                                        OptionEnvvarSettings env_setting,
27                                        bool default_is_true) {
28   options_.emplace(name,
29                    OptionInfo{kBoolean,
30                               std::make_shared<SimpleOptionField<bool>>(field),
31                               env_setting,
32                               help_text,
33                               default_is_true});
34 }
35 
36 template <typename Options>
AddOption(const char * name,const char * help_text,uint64_t Options::* field,OptionEnvvarSettings env_setting)37 void OptionsParser<Options>::AddOption(const char* name,
38                                        const char* help_text,
39                                        uint64_t Options::* field,
40                                        OptionEnvvarSettings env_setting) {
41   options_.emplace(
42       name,
43       OptionInfo{kUInteger,
44                  std::make_shared<SimpleOptionField<uint64_t>>(field),
45                  env_setting,
46                  help_text});
47 }
48 
49 template <typename Options>
AddOption(const char * name,const char * help_text,int64_t Options::* field,OptionEnvvarSettings env_setting)50 void OptionsParser<Options>::AddOption(const char* name,
51                                        const char* help_text,
52                                        int64_t Options::* field,
53                                        OptionEnvvarSettings env_setting) {
54   options_.emplace(
55       name,
56       OptionInfo{kInteger,
57                  std::make_shared<SimpleOptionField<int64_t>>(field),
58                  env_setting,
59                  help_text});
60 }
61 
62 template <typename Options>
AddOption(const char * name,const char * help_text,std::string Options::* field,OptionEnvvarSettings env_setting)63 void OptionsParser<Options>::AddOption(const char* name,
64                                        const char* help_text,
65                                        std::string Options::* field,
66                                        OptionEnvvarSettings env_setting) {
67   options_.emplace(
68       name,
69       OptionInfo{kString,
70                  std::make_shared<SimpleOptionField<std::string>>(field),
71                  env_setting,
72                  help_text});
73 }
74 
75 template <typename Options>
AddOption(const char * name,const char * help_text,std::vector<std::string> Options::* field,OptionEnvvarSettings env_setting)76 void OptionsParser<Options>::AddOption(
77     const char* name,
78     const char* help_text,
79     std::vector<std::string> Options::* field,
80     OptionEnvvarSettings env_setting) {
81   options_.emplace(name, OptionInfo {
82     kStringList,
83     std::make_shared<SimpleOptionField<std::vector<std::string>>>(field),
84     env_setting,
85     help_text
86   });
87 }
88 
89 template <typename Options>
AddOption(const char * name,const char * help_text,HostPort Options::* field,OptionEnvvarSettings env_setting)90 void OptionsParser<Options>::AddOption(const char* name,
91                                        const char* help_text,
92                                        HostPort Options::* field,
93                                        OptionEnvvarSettings env_setting) {
94   options_.emplace(
95       name,
96       OptionInfo{kHostPort,
97                  std::make_shared<SimpleOptionField<HostPort>>(field),
98                  env_setting,
99                  help_text});
100 }
101 
102 template <typename Options>
AddOption(const char * name,const char * help_text,NoOp no_op_tag,OptionEnvvarSettings env_setting)103 void OptionsParser<Options>::AddOption(const char* name,
104                                        const char* help_text,
105                                        NoOp no_op_tag,
106                                        OptionEnvvarSettings env_setting) {
107   options_.emplace(name, OptionInfo{kNoOp, nullptr, env_setting, help_text});
108 }
109 
110 template <typename Options>
AddOption(const char * name,const char * help_text,V8Option v8_option_tag,OptionEnvvarSettings env_setting)111 void OptionsParser<Options>::AddOption(const char* name,
112                                        const char* help_text,
113                                        V8Option v8_option_tag,
114                                        OptionEnvvarSettings env_setting) {
115   options_.emplace(name,
116                    OptionInfo{kV8Option, nullptr, env_setting, help_text});
117 }
118 
119 template <typename Options>
AddAlias(const char * from,const char * to)120 void OptionsParser<Options>::AddAlias(const char* from,
121                                       const char* to) {
122   aliases_[from] = { to };
123 }
124 
125 template <typename Options>
AddAlias(const char * from,const std::vector<std::string> & to)126 void OptionsParser<Options>::AddAlias(const char* from,
127                                       const std::vector<std::string>& to) {
128   aliases_[from] = to;
129 }
130 
131 template <typename Options>
AddAlias(const char * from,const std::initializer_list<std::string> & to)132 void OptionsParser<Options>::AddAlias(
133     const char* from,
134     const std::initializer_list<std::string>& to) {
135   AddAlias(from, std::vector<std::string>(to));
136 }
137 
138 template <typename Options>
Implies(const char * from,const char * to)139 void OptionsParser<Options>::Implies(const char* from,
140                                      const char* to) {
141   auto it = options_.find(to);
142   CHECK_NE(it, options_.end());
143   CHECK(it->second.type == kBoolean || it->second.type == kV8Option);
144   implications_.emplace(
145       from, Implication{it->second.type, to, it->second.field, true});
146 }
147 
148 template <typename Options>
ImpliesNot(const char * from,const char * to)149 void OptionsParser<Options>::ImpliesNot(const char* from,
150                                         const char* to) {
151   auto it = options_.find(to);
152   CHECK_NE(it, options_.end());
153   CHECK_EQ(it->second.type, kBoolean);
154   implications_.emplace(
155       from, Implication{it->second.type, to, it->second.field, false});
156 }
157 
158 template <typename Options>
159 template <typename OriginalField, typename ChildOptions>
Convert(std::shared_ptr<OriginalField> original,ChildOptions * (Options::* get_child)())160 auto OptionsParser<Options>::Convert(
161     std::shared_ptr<OriginalField> original,
162     ChildOptions* (Options::* get_child)()) {
163   // If we have a field on ChildOptions, and we want to access it from an
164   // Options instance, we call get_child() on the original Options and then
165   // access it, i.e. this class implements a kind of function chaining.
166   struct AdaptedField : BaseOptionField {
167     void* LookupImpl(Options* options) const override {
168       return original->LookupImpl((options->*get_child)());
169     }
170 
171     AdaptedField(
172         std::shared_ptr<OriginalField> original,
173         ChildOptions* (Options::* get_child)())
174           : original(original), get_child(get_child) {}
175 
176     std::shared_ptr<OriginalField> original;
177     ChildOptions* (Options::* get_child)();
178   };
179 
180   return std::shared_ptr<BaseOptionField>(
181       new AdaptedField(original, get_child));
182 }
183 template <typename Options>
184 template <typename ChildOptions>
Convert(typename OptionsParser<ChildOptions>::OptionInfo original,ChildOptions * (Options::* get_child)())185 auto OptionsParser<Options>::Convert(
186     typename OptionsParser<ChildOptions>::OptionInfo original,
187     ChildOptions* (Options::* get_child)()) {
188   return OptionInfo{original.type,
189                     Convert(original.field, get_child),
190                     original.env_setting,
191                     original.help_text,
192                     original.default_is_true};
193 }
194 
195 template <typename Options>
196 template <typename ChildOptions>
Convert(typename OptionsParser<ChildOptions>::Implication original,ChildOptions * (Options::* get_child)())197 auto OptionsParser<Options>::Convert(
198     typename OptionsParser<ChildOptions>::Implication original,
199     ChildOptions* (Options::* get_child)()) {
200   return Implication{
201       original.type,
202       original.name,
203       Convert(original.target_field, get_child),
204       original.target_value,
205   };
206 }
207 
208 template <typename Options>
209 template <typename ChildOptions>
Insert(const OptionsParser<ChildOptions> & child_options_parser,ChildOptions * (Options::* get_child)())210 void OptionsParser<Options>::Insert(
211     const OptionsParser<ChildOptions>& child_options_parser,
212     ChildOptions* (Options::* get_child)()) {
213   aliases_.insert(std::begin(child_options_parser.aliases_),
214                   std::end(child_options_parser.aliases_));
215 
216   for (const auto& pair : child_options_parser.options_)
217     options_.emplace(pair.first, Convert(pair.second, get_child));
218 
219   for (const auto& pair : child_options_parser.implications_)
220     implications_.emplace(pair.first, Convert(pair.second, get_child));
221 }
222 
NotAllowedInEnvErr(const std::string & arg)223 inline std::string NotAllowedInEnvErr(const std::string& arg) {
224   return arg + " is not allowed in NODE_OPTIONS";
225 }
226 
RequiresArgumentErr(const std::string & arg)227 inline std::string RequiresArgumentErr(const std::string& arg) {
228   return arg + " requires an argument";
229 }
230 
NegationImpliesBooleanError(const std::string & arg)231 inline std::string NegationImpliesBooleanError(const std::string& arg) {
232   return arg + " is an invalid negation because it is not a boolean option";
233 }
234 
235 // We store some of the basic information around a single Parse call inside
236 // this struct, to separate storage of command line arguments and their
237 // handling. In particular, this makes it easier to introduce 'synthetic'
238 // arguments that get inserted by expanding option aliases.
239 struct ArgsInfo {
240   // Generally, the idea here is that the first entry in `*underlying` stores
241   // the "0th" argument (the program name), then `synthetic_args` are inserted,
242   // followed by the remainder of `*underlying`.
243   std::vector<std::string>* underlying;
244   std::vector<std::string> synthetic_args;
245 
246   std::vector<std::string>* exec_args;
247 
ArgsInfoArgsInfo248   ArgsInfo(std::vector<std::string>* args,
249            std::vector<std::string>* exec_args)
250     : underlying(args), exec_args(exec_args) {}
251 
remainingArgsInfo252   size_t remaining() const {
253     // -1 to account for the program name.
254     return underlying->size() - 1 + synthetic_args.size();
255   }
256 
emptyArgsInfo257   bool empty() const { return remaining() == 0; }
program_nameArgsInfo258   const std::string& program_name() const { return underlying->at(0); }
259 
firstArgsInfo260   std::string& first() {
261     return synthetic_args.empty() ? underlying->at(1) : synthetic_args.front();
262   }
263 
pop_firstArgsInfo264   std::string pop_first() {
265     std::string ret = std::move(first());
266     if (synthetic_args.empty()) {
267       // Only push arguments to `exec_args` that were also originally passed
268       // on the command line (i.e. not generated through alias expansion).
269       // '--' is a special case here since its purpose is to end `exec_argv`,
270       // which is why we do not include it.
271       if (exec_args != nullptr && ret != "--")
272         exec_args->push_back(ret);
273       underlying->erase(underlying->begin() + 1);
274     } else {
275       synthetic_args.erase(synthetic_args.begin());
276     }
277     return ret;
278   }
279 };
280 
281 template <typename 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::vector<std::string> * const errors)282 void OptionsParser<Options>::Parse(
283     std::vector<std::string>* const orig_args,
284     std::vector<std::string>* const exec_args,
285     std::vector<std::string>* const v8_args,
286     Options* const options,
287     OptionEnvvarSettings required_env_settings,
288     std::vector<std::string>* const errors) const {
289   ArgsInfo args(orig_args, exec_args);
290 
291   // The first entry is the process name. Make sure it ends up in the V8 argv,
292   // since V8::SetFlagsFromCommandLine() expects that to hold true for that
293   // array as well.
294   if (v8_args->empty())
295     v8_args->push_back(args.program_name());
296 
297   while (!args.empty() && errors->empty()) {
298     if (args.first().size() <= 1 || args.first()[0] != '-') break;
299 
300     // We know that we're either going to consume this
301     // argument or fail completely.
302     const std::string arg = args.pop_first();
303 
304     if (arg == "--") {
305       if (required_env_settings == kAllowedInEnvironment)
306         errors->push_back(NotAllowedInEnvErr("--"));
307       break;
308     }
309 
310     // Only allow --foo=bar notation for options starting with double dashes.
311     // (E.g. -e=a is not allowed as shorthand for --eval=a, which would
312     // otherwise be the result of alias expansion.)
313     const std::string::size_type equals_index =
314         arg[0] == '-' && arg[1] == '-' ? arg.find('=') : std::string::npos;
315     std::string name =
316       equals_index == std::string::npos ? arg : arg.substr(0, equals_index);
317 
318     // Store the 'original name' of the argument. This name differs from
319     // 'name' in that it contains a possible '=' sign and is not affected
320     // by alias expansion.
321     std::string original_name = name;
322     if (equals_index != std::string::npos)
323       original_name += '=';
324 
325     auto missing_argument = [&]() {
326       errors->push_back(RequiresArgumentErr(original_name));
327     };
328 
329     // Normalize by replacing `_` with `-` in options.
330     for (std::string::size_type i = 2; i < name.size(); ++i) {
331       if (name[i] == '_')
332         name[i] = '-';
333     }
334 
335     // Convert --no-foo to --foo and keep in mind that we're negating.
336     bool is_negation = false;
337     if (name.find("--no-") == 0) {
338       name.erase(2, 3);  // remove no-
339       is_negation = true;
340     }
341 
342     {
343       auto it = aliases_.end();
344       // Expand aliases:
345       // - If `name` can be found in `aliases_`.
346       // - If `name` + '=' can be found in `aliases_`.
347       // - If `name` + " <arg>" can be found in `aliases_`, and we have
348       //   a subsequent argument that does not start with '-' itself.
349       while ((it = aliases_.find(name)) != aliases_.end() ||
350              (equals_index != std::string::npos &&
351               (it = aliases_.find(name + '=')) != aliases_.end()) ||
352              (!args.empty() &&
353                  !args.first().empty() &&
354                  args.first()[0] != '-' &&
355               (it = aliases_.find(name + " <arg>")) != aliases_.end())) {
356         const std::string prev_name = std::move(name);
357         const std::vector<std::string>& expansion = it->second;
358 
359         // Use the first entry in the expansion as the new 'name'.
360         name = expansion.front();
361 
362         if (expansion.size() > 1) {
363           // The other arguments, if any, are going to be handled later.
364           args.synthetic_args.insert(
365               args.synthetic_args.begin(),
366               expansion.begin() + 1,
367               expansion.end());
368         }
369 
370         if (name == prev_name) break;
371       }
372     }
373 
374     auto it = options_.find(name);
375 
376     if ((it == options_.end() ||
377          it->second.env_setting == kDisallowedInEnvironment) &&
378         required_env_settings == kAllowedInEnvironment) {
379       errors->push_back(NotAllowedInEnvErr(original_name));
380       break;
381     }
382 
383     {
384       std::string implied_name = name;
385       if (is_negation) {
386         // Implications for negated options are defined with "--no-".
387         implied_name.insert(2, "no-");
388       }
389       auto implications = implications_.equal_range(implied_name);
390       for (auto it = implications.first; it != implications.second; ++it) {
391         if (it->second.type == kV8Option) {
392           v8_args->push_back(it->second.name);
393         } else {
394           *it->second.target_field->template Lookup<bool>(options) =
395               it->second.target_value;
396         }
397       }
398     }
399 
400     if (it == options_.end()) {
401       v8_args->push_back(arg);
402       continue;
403     }
404 
405     const OptionInfo& info = it->second;
406 
407     // Some V8 options can be negated and they are validated by V8 later.
408     if (is_negation && info.type != kBoolean && info.type != kV8Option) {
409       errors->push_back(NegationImpliesBooleanError(arg));
410       break;
411     }
412 
413     std::string value;
414     if (info.type != kBoolean && info.type != kNoOp && info.type != kV8Option) {
415       if (equals_index != std::string::npos) {
416         value = arg.substr(equals_index + 1);
417         if (value.empty()) {
418           missing_argument();
419           break;
420         }
421       } else {
422         if (args.empty()) {
423           missing_argument();
424           break;
425         }
426 
427         value = args.pop_first();
428 
429         if (!value.empty() && value[0] == '-') {
430           missing_argument();
431           break;
432         } else {
433           if (!value.empty() && value[0] == '\\' && value[1] == '-')
434             value = value.substr(1);  // Treat \- as escaping an -.
435         }
436       }
437     }
438 
439     switch (info.type) {
440       case kBoolean:
441         *Lookup<bool>(info.field, options) = !is_negation;
442         break;
443       case kInteger:
444         *Lookup<int64_t>(info.field, options) = std::atoll(value.c_str());
445         break;
446       case kUInteger:
447         *Lookup<uint64_t>(info.field, options) = std::stoull(value);
448         break;
449       case kString:
450         *Lookup<std::string>(info.field, options) = value;
451         break;
452       case kStringList:
453         Lookup<std::vector<std::string>>(info.field, options)
454             ->emplace_back(std::move(value));
455         break;
456       case kHostPort:
457         Lookup<HostPort>(info.field, options)
458             ->Update(SplitHostPort(value, errors));
459         break;
460       case kNoOp:
461         break;
462       case kV8Option:
463         v8_args->push_back(arg);
464         break;
465       default:
466         UNREACHABLE();
467     }
468   }
469   options->CheckOptions(errors);
470 }
471 
472 }  // namespace options_parser
473 }  // namespace node
474 
475 #endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
476 
477 #endif  // SRC_NODE_OPTIONS_INL_H_
478