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 
19 namespace {
20 enum 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 
31 class OptionParser {
32 public:
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 
40 private:
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 
66 void 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 
73 bool isSeparator(char C) {
74   return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' ||
75          C == '\r';
76 }
77 
78 bool isSeparatorOrNull(char C) { return !C || isSeparator(C); }
79 
80 void OptionParser::skipWhitespace() {
81   while (isSeparator(Buffer[Pos]))
82     ++Pos;
83 }
84 
85 bool 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 
118 void 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 
130 void OptionParser::parseString(const char *S) {
131   if (!S)
132     return;
133   Buffer = S;
134   Pos = 0;
135   parseOptions();
136 }
137 
138 bool 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 
152 bool 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 
185 void 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 
196 void 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 
204 const char *getGwpAsanDefaultOptions() {
205   return (__gwp_asan_default_options) ? __gwp_asan_default_options() : "";
206 }
207 
208 gwp_asan::options::Options *getOptionsInternal() {
209   static gwp_asan::options::Options GwpAsanOptions;
210   return &GwpAsanOptions;
211 }
212 } // anonymous namespace
213 
214 namespace gwp_asan {
215 namespace options {
216 
217 void 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 
251 void initOptions(Printf_t PrintfForWarnings) {
252   initOptions(getenv("GWP_ASAN_OPTIONS"), PrintfForWarnings);
253 }
254 
255 Options &getOptions() { return *getOptionsInternal(); }
256 
257 } // namespace options
258 } // namespace gwp_asan
259