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