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 == kAllowedInEnvvar)
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 == kDisallowedInEnvvar) &&
378 required_env_settings == kAllowedInEnvvar) {
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 imp = implications.first; imp != implications.second; ++imp) {
391 if (imp->second.type == kV8Option) {
392 v8_args->push_back(imp->second.name);
393 } else {
394 *imp->second.target_field->template Lookup<bool>(options) =
395 imp->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) =
448 std::strtoull(value.c_str(), nullptr, 10);
449 break;
450 case kString:
451 *Lookup<std::string>(info.field, options) = value;
452 break;
453 case kStringList:
454 Lookup<std::vector<std::string>>(info.field, options)
455 ->emplace_back(std::move(value));
456 break;
457 case kHostPort:
458 Lookup<HostPort>(info.field, options)
459 ->Update(SplitHostPort(value, errors));
460 break;
461 case kNoOp:
462 break;
463 case kV8Option:
464 v8_args->push_back(arg);
465 break;
466 default:
467 UNREACHABLE();
468 }
469 }
470 options->CheckOptions(errors, orig_args);
471 }
472
473 } // namespace options_parser
474 } // namespace node
475
476 #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
477
478 #endif // SRC_NODE_OPTIONS_INL_H_
479