1//===-- options_parser.cpp --------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "gwp_asan/optional/options_parser.h"
10#include "gwp_asan/optional/printf.h"
11#include "gwp_asan/utilities.h"
12
13#include <assert.h>
14#include <stdarg.h>
15#include <stdint.h>
16#include <stdlib.h>
17#include <string.h>
18
19namespace {
20enum class OptionType : uint8_t {
21  OT_bool,
22  OT_int,
23};
24
25#define InvokeIfNonNull(Printf, ...)                                           \
26  do {                                                                         \
27    if (Printf)                                                                \
28      Printf(__VA_ARGS__);                                                     \
29  } while (0);
30
31class OptionParser {
32public:
33  explicit OptionParser(gwp_asan::Printf_t PrintfForWarnings)
34      : Printf(PrintfForWarnings) {}
35  void registerOption(const char *Name, const char *Desc, OptionType Type,
36                      void *Var);
37  void parseString(const char *S);
38  void printOptionDescriptions();
39
40private:
41  // Calculate at compile-time how many options are available.
42#define GWP_ASAN_OPTION(...) +1
43  static constexpr size_t MaxOptions = 0
44#include "gwp_asan/options.inc"
45      ;
46#undef GWP_ASAN_OPTION
47
48  struct Option {
49    const char *Name;
50    const char *Desc;
51    OptionType Type;
52    void *Var;
53  } Options[MaxOptions];
54
55  size_t NumberOfOptions = 0;
56  const char *Buffer = nullptr;
57  uintptr_t Pos = 0;
58  gwp_asan::Printf_t Printf = nullptr;
59
60  void skipWhitespace();
61  void parseOptions();
62  bool parseOption();
63  bool setOptionToValue(const char *Name, const char *Value);
64};
65
66void OptionParser::printOptionDescriptions() {
67  InvokeIfNonNull(Printf, "GWP-ASan: Available options:\n");
68  for (size_t I = 0; I < NumberOfOptions; ++I)
69    InvokeIfNonNull(Printf, "\t%s\n\t\t- %s\n", Options[I].Name,
70                    Options[I].Desc);
71}
72
73bool isSeparator(char C) {
74  return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' ||
75         C == '\r';
76}
77
78bool isSeparatorOrNull(char C) { return !C || isSeparator(C); }
79
80void OptionParser::skipWhitespace() {
81  while (isSeparator(Buffer[Pos]))
82    ++Pos;
83}
84
85bool OptionParser::parseOption() {
86  const uintptr_t NameStart = Pos;
87  while (Buffer[Pos] != '=' && !isSeparatorOrNull(Buffer[Pos]))
88    ++Pos;
89
90  const char *Name = Buffer + NameStart;
91  if (Buffer[Pos] != '=') {
92    InvokeIfNonNull(Printf, "GWP-ASan: Expected '=' when parsing option '%s'.",
93                    Name);
94    return false;
95  }
96  const uintptr_t ValueStart = ++Pos;
97  const char *Value;
98  if (Buffer[Pos] == '\'' || Buffer[Pos] == '"') {
99    const char Quote = Buffer[Pos++];
100    while (Buffer[Pos] != 0 && Buffer[Pos] != Quote)
101      ++Pos;
102    if (Buffer[Pos] == 0) {
103      InvokeIfNonNull(Printf, "GWP-ASan: Unterminated string in option '%s'.",
104                      Name);
105      return false;
106    }
107    Value = Buffer + ValueStart + 1;
108    ++Pos; // consume the closing quote
109  } else {
110    while (!isSeparatorOrNull(Buffer[Pos]))
111      ++Pos;
112    Value = Buffer + ValueStart;
113  }
114
115  return setOptionToValue(Name, Value);
116}
117
118void OptionParser::parseOptions() {
119  while (true) {
120    skipWhitespace();
121    if (Buffer[Pos] == 0)
122      break;
123    if (!parseOption()) {
124      InvokeIfNonNull(Printf, "GWP-ASan: Options parsing failed.\n");
125      return;
126    }
127  }
128}
129
130void OptionParser::parseString(const char *S) {
131  if (!S)
132    return;
133  Buffer = S;
134  Pos = 0;
135  parseOptions();
136}
137
138bool parseBool(const char *Value, bool *b) {
139  if (strncmp(Value, "0", 1) == 0 || strncmp(Value, "no", 2) == 0 ||
140      strncmp(Value, "false", 5) == 0) {
141    *b = false;
142    return true;
143  }
144  if (strncmp(Value, "1", 1) == 0 || strncmp(Value, "yes", 3) == 0 ||
145      strncmp(Value, "true", 4) == 0) {
146    *b = true;
147    return true;
148  }
149  return false;
150}
151
152bool OptionParser::setOptionToValue(const char *Name, const char *Value) {
153  for (size_t I = 0; I < NumberOfOptions; ++I) {
154    const uintptr_t Len = strlen(Options[I].Name);
155    if (strncmp(Name, Options[I].Name, Len) != 0 || Name[Len] != '=')
156      continue;
157    bool Ok = false;
158    switch (Options[I].Type) {
159    case OptionType::OT_bool:
160      Ok = parseBool(Value, reinterpret_cast<bool *>(Options[I].Var));
161      if (!Ok)
162        InvokeIfNonNull(
163            Printf, "GWP-ASan: Invalid boolean value '%s' for option '%s'.\n",
164            Value, Options[I].Name);
165      break;
166    case OptionType::OT_int:
167      char *ValueEnd;
168      *reinterpret_cast<int *>(Options[I].Var) =
169          static_cast<int>(strtol(Value, &ValueEnd, 10));
170      Ok =
171          *ValueEnd == '"' || *ValueEnd == '\'' || isSeparatorOrNull(*ValueEnd);
172      if (!Ok)
173        InvokeIfNonNull(
174            Printf, "GWP-ASan: Invalid integer value '%s' for option '%s'.\n",
175            Value, Options[I].Name);
176      break;
177    }
178    return Ok;
179  }
180
181  InvokeIfNonNull(Printf, "GWP-ASan: Unknown option '%s'.", Name);
182  return true;
183}
184
185void OptionParser::registerOption(const char *Name, const char *Desc,
186                                  OptionType Type, void *Var) {
187  assert(NumberOfOptions < MaxOptions &&
188         "GWP-ASan Error: Ran out of space for options.\n");
189  Options[NumberOfOptions].Name = Name;
190  Options[NumberOfOptions].Desc = Desc;
191  Options[NumberOfOptions].Type = Type;
192  Options[NumberOfOptions].Var = Var;
193  ++NumberOfOptions;
194}
195
196void registerGwpAsanOptions(OptionParser *parser,
197                            gwp_asan::options::Options *o) {
198#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description)                 \
199  parser->registerOption(#Name, Description, OptionType::OT_##Type, &o->Name);
200#include "gwp_asan/options.inc"
201#undef GWP_ASAN_OPTION
202}
203
204const char *getGwpAsanDefaultOptions() {
205  return (__gwp_asan_default_options) ? __gwp_asan_default_options() : "";
206}
207
208gwp_asan::options::Options *getOptionsInternal() {
209  static gwp_asan::options::Options GwpAsanOptions;
210  return &GwpAsanOptions;
211}
212} // anonymous namespace
213
214namespace gwp_asan {
215namespace options {
216
217void initOptions(const char *OptionsStr, Printf_t PrintfForWarnings) {
218  Options *o = getOptionsInternal();
219  o->setDefaults();
220
221  OptionParser Parser(PrintfForWarnings);
222  registerGwpAsanOptions(&Parser, o);
223
224  // Override from the weak function definition in this executable.
225  Parser.parseString(getGwpAsanDefaultOptions());
226
227  // Override from the provided options string.
228  Parser.parseString(OptionsStr);
229
230  if (o->help)
231    Parser.printOptionDescriptions();
232
233  if (!o->Enabled)
234    return;
235
236  if (o->MaxSimultaneousAllocations <= 0) {
237    InvokeIfNonNull(
238        PrintfForWarnings,
239        "GWP-ASan ERROR: MaxSimultaneousAllocations must be > 0 when GWP-ASan "
240        "is enabled.\n");
241    o->Enabled = false;
242  }
243  if (o->SampleRate <= 0) {
244    InvokeIfNonNull(
245        PrintfForWarnings,
246        "GWP-ASan ERROR: SampleRate must be > 0 when GWP-ASan is enabled.\n");
247    o->Enabled = false;
248  }
249}
250
251void initOptions(Printf_t PrintfForWarnings) {
252  initOptions(getenv("GWP_ASAN_OPTIONS"), PrintfForWarnings);
253}
254
255Options &getOptions() { return *getOptionsInternal(); }
256
257} // namespace options
258} // namespace gwp_asan
259