tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

jsoptparse.cpp (18791B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "shell/jsoptparse.h"
      8 
      9 #include <algorithm>
     10 #include <stdarg.h>
     11 #include <string_view>
     12 
     13 #include "util/Unicode.h"
     14 
     15 using namespace js;
     16 using namespace js::cli;
     17 using namespace js::cli::detail;
     18 
     19 #define OPTION_CONVERT_IMPL(__cls)                                             \
     20  bool Option::is##__cls##Option() const { return kind == OptionKind##__cls; } \
     21  __cls##Option* Option::as##__cls##Option() {                                 \
     22    MOZ_ASSERT(is##__cls##Option());                                           \
     23    return static_cast<__cls##Option*>(this);                                  \
     24  }                                                                            \
     25  const __cls##Option* Option::as##__cls##Option() const {                     \
     26    return const_cast<Option*>(this)->as##__cls##Option();                     \
     27  }
     28 
     29 ValuedOption* Option::asValued() {
     30  MOZ_ASSERT(isValued());
     31  return static_cast<ValuedOption*>(this);
     32 }
     33 
     34 const ValuedOption* Option::asValued() const {
     35  return const_cast<Option*>(this)->asValued();
     36 }
     37 
     38 OPTION_CONVERT_IMPL(Bool)
     39 OPTION_CONVERT_IMPL(String)
     40 OPTION_CONVERT_IMPL(Int)
     41 OPTION_CONVERT_IMPL(MultiString)
     42 
     43 void OptionParser::setArgTerminatesOptions(const char* name, bool enabled) {
     44  findArgument(name)->setTerminatesOptions(enabled);
     45 }
     46 
     47 void OptionParser::setIgnoresUnknownOptions(const char* name, bool enabled) {
     48  auto* opt = findOption(name);
     49  MOZ_ASSERT(opt);
     50  opt->setIgnoresUnknownOptions(enabled);
     51 }
     52 
     53 void OptionParser::setArgCapturesRest(const char* name) {
     54  MOZ_ASSERT(restArgument == -1,
     55             "only one argument may be set to capture the rest");
     56  restArgument = findArgumentIndex(name);
     57  MOZ_ASSERT(restArgument != -1,
     58             "unknown argument name passed to setArgCapturesRest");
     59 }
     60 
     61 OptionParser::Result OptionParser::error(const char* fmt, ...) {
     62  va_list args;
     63  va_start(args, fmt);
     64  fprintf(stderr, "Error: ");
     65  vfprintf(stderr, fmt, args);
     66  va_end(args);
     67  fputs("\n\n", stderr);
     68  return ParseError;
     69 }
     70 
     71 /* Quick and dirty paragraph printer. */
     72 static void PrintParagraph(const char* text, unsigned startColno,
     73                           const unsigned limitColno, bool padFirstLine) {
     74  unsigned colno = startColno;
     75  unsigned indent = 0;
     76  const char* it = text;
     77 
     78  if (padFirstLine) {
     79    printf("%*s", int(startColno), "");
     80  }
     81 
     82  /* Skip any leading spaces. */
     83  while (*it != '\0' && unicode::IsSpace(*it)) {
     84    ++it;
     85  }
     86 
     87  while (*it != '\0') {
     88    MOZ_ASSERT(!unicode::IsSpace(*it) || *it == '\n');
     89 
     90    /* Delimit the current token. */
     91    const char* limit = it;
     92    while (!unicode::IsSpace(*limit) && *limit != '\0') {
     93      ++limit;
     94    }
     95 
     96    /*
     97     * If the current token is longer than the available number of columns,
     98     * then make a line break before printing the token.
     99     */
    100    size_t tokLen = limit - it;
    101    if (tokLen + colno >= limitColno) {
    102      printf("\n%*s%.*s", int(startColno + indent), "", int(tokLen), it);
    103      colno = startColno + tokLen;
    104    } else {
    105      printf("%.*s", int(tokLen), it);
    106      colno += tokLen;
    107    }
    108 
    109    switch (*limit) {
    110      case '\0':
    111        return;
    112      case ' ':
    113        putchar(' ');
    114        colno += 1;
    115        it = limit;
    116        while (*it == ' ') {
    117          ++it;
    118        }
    119        break;
    120      case '\n':
    121        /* |text| wants to force a newline here. */
    122        printf("\n%*s", int(startColno), "");
    123        colno = startColno;
    124        it = limit + 1;
    125        /* Could also have line-leading spaces. */
    126        indent = 0;
    127        while (*it == ' ') {
    128          putchar(' ');
    129          ++colno;
    130          ++indent;
    131          ++it;
    132        }
    133        break;
    134      default:
    135        MOZ_CRASH("unhandled token splitting character in text");
    136    }
    137  }
    138 }
    139 
    140 static const char* OptionFlagsToFormatInfo(char shortflag, bool isValued,
    141                                           size_t* length) {
    142  static const char* const fmt[4] = {"  -%c --%s ", "  --%s ", "  -%c --%s=%s ",
    143                                     "  --%s=%s "};
    144 
    145  /* How mny chars w/o longflag? */
    146  size_t lengths[4] = {strlen(fmt[0]) - 3, strlen(fmt[1]) - 3,
    147                       strlen(fmt[2]) - 5, strlen(fmt[3]) - 5};
    148  int index = isValued ? 2 : 0;
    149  if (!shortflag) {
    150    index++;
    151  }
    152 
    153  *length = lengths[index];
    154  return fmt[index];
    155 }
    156 
    157 OptionParser::Result OptionParser::printHelp(const char* progname) {
    158  constexpr std::string_view prognameMeta = "{progname}";
    159 
    160  const char* prefixEnd = strstr(usage, prognameMeta.data());
    161  if (prefixEnd) {
    162    printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
    163           prefixEnd + prognameMeta.length());
    164  } else {
    165    puts(usage);
    166  }
    167 
    168  if (descr) {
    169    putchar('\n');
    170    PrintParagraph(descr, 2, descrWidth, true);
    171    putchar('\n');
    172  }
    173 
    174  if (version) {
    175    printf("\nVersion: %s\n\n", version);
    176  }
    177 
    178  if (!arguments.empty()) {
    179    printf("Arguments:\n");
    180 
    181    static const char fmt[] = "  %s ";
    182    size_t fmtChars = sizeof(fmt) - 2;
    183    size_t lhsLen = 0;
    184    for (Option* arg : arguments) {
    185      lhsLen = std::max(lhsLen, strlen(arg->longflag) + fmtChars);
    186    }
    187 
    188    for (Option* arg : arguments) {
    189      size_t chars = printf(fmt, arg->longflag);
    190      for (; chars < lhsLen; ++chars) {
    191        putchar(' ');
    192      }
    193      PrintParagraph(arg->help, lhsLen, helpWidth, false);
    194      putchar('\n');
    195    }
    196    putchar('\n');
    197  }
    198 
    199  if (!options.empty()) {
    200    printf("Options:\n");
    201 
    202    /* Calculate sizes for column alignment. */
    203    size_t lhsLen = 0;
    204    for (Option* opt : options) {
    205      size_t longflagLen = strlen(opt->longflag);
    206 
    207      size_t fmtLen;
    208      OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
    209 
    210      size_t len = fmtLen + longflagLen;
    211      if (opt->isValued()) {
    212        len += strlen(opt->asValued()->metavar);
    213      }
    214      lhsLen = std::max(lhsLen, len);
    215    }
    216 
    217    /* Print option help text. */
    218    for (Option* opt : options) {
    219      size_t fmtLen;
    220      const char* fmt =
    221          OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
    222      size_t chars;
    223      if (opt->isValued()) {
    224        if (opt->shortflag) {
    225          chars = printf(fmt, opt->shortflag, opt->longflag,
    226                         opt->asValued()->metavar);
    227        } else {
    228          chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
    229        }
    230      } else {
    231        if (opt->shortflag) {
    232          chars = printf(fmt, opt->shortflag, opt->longflag);
    233        } else {
    234          chars = printf(fmt, opt->longflag);
    235        }
    236      }
    237      for (; chars < lhsLen; ++chars) {
    238        putchar(' ');
    239      }
    240      PrintParagraph(opt->help, lhsLen, helpWidth, false);
    241      putchar('\n');
    242    }
    243  }
    244 
    245  return EarlyExit;
    246 }
    247 
    248 OptionParser::Result OptionParser::printVersion() {
    249  MOZ_ASSERT(version);
    250  printf("%s\n", version);
    251  return EarlyExit;
    252 }
    253 
    254 OptionParser::Result OptionParser::extractValue(size_t argc, char** argv,
    255                                                size_t* i, char** value) {
    256  MOZ_ASSERT(*i < argc);
    257  char* eq = strchr(argv[*i], '=');
    258  if (eq) {
    259    *value = eq + 1;
    260    if (*value[0] == '\0') {
    261      return error("A value is required for option %.*s", (int)(eq - argv[*i]),
    262                   argv[*i]);
    263    }
    264    return Okay;
    265  }
    266 
    267  if (argc == *i + 1) {
    268    return error("Expected a value for option %s", argv[*i]);
    269  }
    270 
    271  *i += 1;
    272  *value = argv[*i];
    273  return Okay;
    274 }
    275 
    276 OptionParser::Result OptionParser::handleOption(Option* opt, size_t argc,
    277                                                char** argv, size_t* i,
    278                                                bool* optionsAllowed) {
    279  if (opt->getTerminatesOptions()) {
    280    *optionsAllowed = false;
    281  }
    282 
    283  switch (opt->kind) {
    284    case OptionKindBool: {
    285      if (opt == &helpOption) {
    286        return printHelp(argv[0]);
    287      }
    288      if (opt == &versionOption) {
    289        return printVersion();
    290      }
    291      opt->asBoolOption()->value = true;
    292      return Okay;
    293    }
    294    /*
    295     * Valued options are allowed to specify their values either via
    296     * successive arguments or a single --longflag=value argument.
    297     */
    298    case OptionKindString: {
    299      char* value = nullptr;
    300      if (Result r = extractValue(argc, argv, i, &value)) {
    301        return r;
    302      }
    303      opt->asStringOption()->value = value;
    304      return Okay;
    305    }
    306    case OptionKindInt: {
    307      char* value = nullptr;
    308      if (Result r = extractValue(argc, argv, i, &value)) {
    309        return r;
    310      }
    311      opt->asIntOption()->value = atoi(value);
    312      return Okay;
    313    }
    314    case OptionKindMultiString: {
    315      char* value = nullptr;
    316      if (Result r = extractValue(argc, argv, i, &value)) {
    317        return r;
    318      }
    319      StringArg arg(value, *i);
    320      return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
    321    }
    322    default:
    323      MOZ_CRASH("unhandled option kind");
    324  }
    325 }
    326 
    327 OptionParser::Result OptionParser::handleArg(size_t argc, char** argv,
    328                                             size_t* i, bool* optionsAllowed) {
    329  if (nextArgument >= arguments.length()) {
    330    return error("Too many arguments provided");
    331  }
    332 
    333  Option* arg = arguments[nextArgument];
    334 
    335  if (arg->getTerminatesOptions()) {
    336    *optionsAllowed = false;
    337  }
    338 
    339  switch (arg->kind) {
    340    case OptionKindString:
    341      arg->asStringOption()->value = argv[*i];
    342      nextArgument += 1;
    343      return Okay;
    344    case OptionKindMultiString: {
    345      // Don't advance the next argument -- there can only be one (final)
    346      // variadic argument.
    347      StringArg value(argv[*i], *i);
    348      return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
    349    }
    350    default:
    351      MOZ_CRASH("unhandled argument kind");
    352  }
    353 }
    354 
    355 OptionParser::Result OptionParser::parseArgs(int inputArgc, char** argv) {
    356  MOZ_ASSERT(inputArgc >= 0);
    357  size_t argc = inputArgc;
    358 
    359  // Permit a "no more options" capability, like |--| offers in many shell
    360  // interfaces.
    361  bool optionsAllowed = true;
    362 
    363  // Whether unknown options should report a warning instead of an error. This
    364  // is enabled by setIgnoresUnknownOptions and used for --fuzzing-safe.
    365  bool ignoreUnknownOptions = false;
    366 
    367  for (size_t i = 1; i < argc; ++i) {
    368    char* arg = argv[i];
    369    Result r;
    370    /* Note: solo dash option is actually a 'stdin' argument. */
    371    if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
    372      /* Option. */
    373      Option* opt;
    374      if (arg[1] == '-') {
    375        if (arg[2] == '\0') {
    376          /* End of options */
    377          optionsAllowed = false;
    378          nextArgument = restArgument;
    379          continue;
    380        } else {
    381          /* Long option. */
    382          opt = findOption(arg + 2);
    383          if (!opt) {
    384            if (ignoreUnknownOptions) {
    385              fprintf(stderr, "Warning: Ignoring unknown shell flag: %s\n",
    386                      arg);
    387              continue;
    388            }
    389            return error("Invalid long option: %s", arg);
    390          }
    391        }
    392      } else {
    393        /* Short option */
    394        if (arg[2] != '\0') {
    395          return error("Short option followed by junk: %s", arg);
    396        }
    397        opt = findOption(arg[1]);
    398        if (!opt) {
    399          return error("Invalid short option: %s", arg);
    400        }
    401      }
    402 
    403      if (opt->getIgnoresUnknownOptions()) {
    404        ignoreUnknownOptions = true;
    405      }
    406 
    407      r = handleOption(opt, argc, argv, &i, &optionsAllowed);
    408    } else {
    409      /* Argument. */
    410      r = handleArg(argc, argv, &i, &optionsAllowed);
    411    }
    412 
    413    if (r != Okay) {
    414      return r;
    415    }
    416  }
    417  return Okay;
    418 }
    419 
    420 void OptionParser::setHelpOption(char shortflag, const char* longflag,
    421                                 const char* help) {
    422  helpOption.setFlagInfo(shortflag, longflag, help);
    423 }
    424 
    425 bool OptionParser::getHelpOption() const { return helpOption.value; }
    426 
    427 bool OptionParser::getBoolOption(char shortflag) const {
    428  return tryFindOption(shortflag)->asBoolOption()->value;
    429 }
    430 
    431 int OptionParser::getIntOption(char shortflag) const {
    432  return tryFindOption(shortflag)->asIntOption()->value;
    433 }
    434 
    435 const char* OptionParser::getStringOption(char shortflag) const {
    436  return tryFindOption(shortflag)->asStringOption()->value;
    437 }
    438 
    439 MultiStringRange OptionParser::getMultiStringOption(char shortflag) const {
    440  const MultiStringOption* mso =
    441      tryFindOption(shortflag)->asMultiStringOption();
    442  return MultiStringRange(mso->strings.begin(), mso->strings.end());
    443 }
    444 
    445 bool OptionParser::getBoolOption(const char* longflag) const {
    446  return tryFindOption(longflag)->asBoolOption()->value;
    447 }
    448 
    449 int OptionParser::getIntOption(const char* longflag) const {
    450  return tryFindOption(longflag)->asIntOption()->value;
    451 }
    452 
    453 const char* OptionParser::getStringOption(const char* longflag) const {
    454  return tryFindOption(longflag)->asStringOption()->value;
    455 }
    456 
    457 MultiStringRange OptionParser::getMultiStringOption(
    458    const char* longflag) const {
    459  const MultiStringOption* mso = tryFindOption(longflag)->asMultiStringOption();
    460  return MultiStringRange(mso->strings.begin(), mso->strings.end());
    461 }
    462 
    463 OptionParser::~OptionParser() {
    464  for (Option* opt : options) {
    465    js_delete<Option>(opt);
    466  }
    467  for (Option* arg : arguments) {
    468    js_delete<Option>(arg);
    469  }
    470 }
    471 
    472 Option* OptionParser::findOption(char shortflag) {
    473  for (Option* opt : options) {
    474    if (opt->shortflag == shortflag) {
    475      return opt;
    476    }
    477  }
    478 
    479  if (versionOption.shortflag == shortflag) {
    480    return &versionOption;
    481  }
    482 
    483  return helpOption.shortflag == shortflag ? &helpOption : nullptr;
    484 }
    485 
    486 const Option* OptionParser::findOption(char shortflag) const {
    487  return const_cast<OptionParser*>(this)->findOption(shortflag);
    488 }
    489 
    490 const Option* OptionParser::tryFindOption(char shortflag) const {
    491  const Option* maybeOption = findOption(shortflag);
    492  if (!maybeOption) {
    493    fprintf(stderr, "Failed to find short option %c\n", shortflag);
    494    MOZ_CRASH();
    495  }
    496  return maybeOption;
    497 }
    498 
    499 Option* OptionParser::findOption(const char* longflag) {
    500  for (Option* opt : options) {
    501    const char* target = opt->longflag;
    502    if (opt->isValued()) {
    503      size_t targetLen = strlen(target);
    504      /* Permit a trailing equals sign on the longflag argument. */
    505      for (size_t i = 0; i < targetLen; ++i) {
    506        if (longflag[i] == '\0' || longflag[i] != target[i]) {
    507          goto no_match;
    508        }
    509      }
    510      if (longflag[targetLen] == '\0' || longflag[targetLen] == '=') {
    511        return opt;
    512      }
    513    } else {
    514      if (strcmp(target, longflag) == 0) {
    515        return opt;
    516      }
    517    }
    518  no_match:;
    519  }
    520 
    521  if (strcmp(versionOption.longflag, longflag) == 0) {
    522    return &versionOption;
    523  }
    524 
    525  return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
    526 }
    527 
    528 const Option* OptionParser::findOption(const char* longflag) const {
    529  return const_cast<OptionParser*>(this)->findOption(longflag);
    530 }
    531 
    532 const Option* OptionParser::tryFindOption(const char* longflag) const {
    533  const Option* maybeOption = findOption(longflag);
    534  if (!maybeOption) {
    535    fprintf(stderr, "Failed to find long option %s\n", longflag);
    536    MOZ_CRASH();
    537  }
    538  return maybeOption;
    539 }
    540 
    541 /* Argument accessors */
    542 
    543 int OptionParser::findArgumentIndex(const char* name) const {
    544  for (Option* const* it = arguments.begin(); it != arguments.end(); ++it) {
    545    const char* target = (*it)->longflag;
    546    if (strcmp(target, name) == 0) {
    547      return it - arguments.begin();
    548    }
    549  }
    550  return -1;
    551 }
    552 
    553 Option* OptionParser::findArgument(const char* name) {
    554  int index = findArgumentIndex(name);
    555  return (index == -1) ? nullptr : arguments[index];
    556 }
    557 
    558 const Option* OptionParser::findArgument(const char* name) const {
    559  int index = findArgumentIndex(name);
    560  return (index == -1) ? nullptr : arguments[index];
    561 }
    562 
    563 const char* OptionParser::getStringArg(const char* name) const {
    564  return findArgument(name)->asStringOption()->value;
    565 }
    566 
    567 MultiStringRange OptionParser::getMultiStringArg(const char* name) const {
    568  const MultiStringOption* mso = findArgument(name)->asMultiStringOption();
    569  return MultiStringRange(mso->strings.begin(), mso->strings.end());
    570 }
    571 
    572 /* Option builders */
    573 
    574 // Use vanilla malloc for allocations. See OptionAllocPolicy.
    575 JS_DECLARE_NEW_METHODS(opt_new, malloc, static MOZ_ALWAYS_INLINE)
    576 
    577 bool OptionParser::addIntOption(char shortflag, const char* longflag,
    578                                const char* metavar, const char* help,
    579                                int defaultValue) {
    580  if (!options.reserve(options.length() + 1)) {
    581    return false;
    582  }
    583  IntOption* io =
    584      opt_new<IntOption>(shortflag, longflag, help, metavar, defaultValue);
    585  if (!io) {
    586    return false;
    587  }
    588  options.infallibleAppend(io);
    589  return true;
    590 }
    591 
    592 bool OptionParser::addBoolOption(char shortflag, const char* longflag,
    593                                 const char* help) {
    594  if (!options.reserve(options.length() + 1)) {
    595    return false;
    596  }
    597  BoolOption* bo = opt_new<BoolOption>(shortflag, longflag, help);
    598  if (!bo) {
    599    return false;
    600  }
    601  options.infallibleAppend(bo);
    602  return true;
    603 }
    604 
    605 bool OptionParser::addStringOption(char shortflag, const char* longflag,
    606                                   const char* metavar, const char* help) {
    607  if (!options.reserve(options.length() + 1)) {
    608    return false;
    609  }
    610  StringOption* so = opt_new<StringOption>(shortflag, longflag, help, metavar);
    611  if (!so) {
    612    return false;
    613  }
    614  options.infallibleAppend(so);
    615  return true;
    616 }
    617 
    618 bool OptionParser::addMultiStringOption(char shortflag, const char* longflag,
    619                                        const char* metavar, const char* help) {
    620  if (!options.reserve(options.length() + 1)) {
    621    return false;
    622  }
    623  MultiStringOption* mso =
    624      opt_new<MultiStringOption>(shortflag, longflag, help, metavar);
    625  if (!mso) {
    626    return false;
    627  }
    628  options.infallibleAppend(mso);
    629  return true;
    630 }
    631 
    632 /* Argument builders */
    633 
    634 bool OptionParser::addOptionalStringArg(const char* name, const char* help) {
    635  if (!arguments.reserve(arguments.length() + 1)) {
    636    return false;
    637  }
    638  StringOption* so = opt_new<StringOption>(1, name, help, (const char*)nullptr);
    639  if (!so) {
    640    return false;
    641  }
    642  arguments.infallibleAppend(so);
    643  return true;
    644 }
    645 
    646 bool OptionParser::addOptionalMultiStringArg(const char* name,
    647                                             const char* help) {
    648  MOZ_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
    649  if (!arguments.reserve(arguments.length() + 1)) {
    650    return false;
    651  }
    652  MultiStringOption* mso =
    653      opt_new<MultiStringOption>(1, name, help, (const char*)nullptr);
    654  if (!mso) {
    655    return false;
    656  }
    657  arguments.infallibleAppend(mso);
    658  return true;
    659 }