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