blob: 60234124e8ede9306059cc4e4e008fa880e20f77 [file] [log] [blame]
//===-- options_parser.cpp --------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "gwp_asan/optional/options_parser.h"
#include "gwp_asan/optional/printf.h"
#include "gwp_asan/utilities.h"
#include <assert.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
namespace {
enum class OptionType : uint8_t {
OT_bool,
OT_int,
};
#define InvokeIfNonNull(Printf, ...) \
do { \
if (Printf) \
Printf(__VA_ARGS__); \
} while (0);
class OptionParser {
public:
explicit OptionParser(gwp_asan::Printf_t PrintfForWarnings)
: Printf(PrintfForWarnings) {}
void registerOption(const char *Name, const char *Desc, OptionType Type,
void *Var);
void parseString(const char *S);
void printOptionDescriptions();
private:
// Calculate at compile-time how many options are available.
#define GWP_ASAN_OPTION(...) +1
static constexpr size_t MaxOptions = 0
#include "gwp_asan/options.inc"
;
#undef GWP_ASAN_OPTION
struct Option {
const char *Name;
const char *Desc;
OptionType Type;
void *Var;
} Options[MaxOptions];
size_t NumberOfOptions = 0;
const char *Buffer = nullptr;
uintptr_t Pos = 0;
gwp_asan::Printf_t Printf = nullptr;
void skipWhitespace();
void parseOptions();
bool parseOption();
bool setOptionToValue(const char *Name, const char *Value);
};
void OptionParser::printOptionDescriptions() {
InvokeIfNonNull(Printf, "GWP-ASan: Available options:\n");
for (size_t I = 0; I < NumberOfOptions; ++I)
InvokeIfNonNull(Printf, "\t%s\n\t\t- %s\n", Options[I].Name,
Options[I].Desc);
}
bool isSeparator(char C) {
return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' ||
C == '\r';
}
bool isSeparatorOrNull(char C) { return !C || isSeparator(C); }
void OptionParser::skipWhitespace() {
while (isSeparator(Buffer[Pos]))
++Pos;
}
bool OptionParser::parseOption() {
const uintptr_t NameStart = Pos;
while (Buffer[Pos] != '=' && !isSeparatorOrNull(Buffer[Pos]))
++Pos;
const char *Name = Buffer + NameStart;
if (Buffer[Pos] != '=') {
InvokeIfNonNull(Printf, "GWP-ASan: Expected '=' when parsing option '%s'.",
Name);
return false;
}
const uintptr_t ValueStart = ++Pos;
const char *Value;
if (Buffer[Pos] == '\'' || Buffer[Pos] == '"') {
const char Quote = Buffer[Pos++];
while (Buffer[Pos] != 0 && Buffer[Pos] != Quote)
++Pos;
if (Buffer[Pos] == 0) {
InvokeIfNonNull(Printf, "GWP-ASan: Unterminated string in option '%s'.",
Name);
return false;
}
Value = Buffer + ValueStart + 1;
++Pos; // consume the closing quote
} else {
while (!isSeparatorOrNull(Buffer[Pos]))
++Pos;
Value = Buffer + ValueStart;
}
return setOptionToValue(Name, Value);
}
void OptionParser::parseOptions() {
while (true) {
skipWhitespace();
if (Buffer[Pos] == 0)
break;
if (!parseOption()) {
InvokeIfNonNull(Printf, "GWP-ASan: Options parsing failed.\n");
return;
}
}
}
void OptionParser::parseString(const char *S) {
if (!S)
return;
Buffer = S;
Pos = 0;
parseOptions();
}
bool parseBool(const char *Value, bool *b) {
if (strncmp(Value, "0", 1) == 0 || strncmp(Value, "no", 2) == 0 ||
strncmp(Value, "false", 5) == 0) {
*b = false;
return true;
}
if (strncmp(Value, "1", 1) == 0 || strncmp(Value, "yes", 3) == 0 ||
strncmp(Value, "true", 4) == 0) {
*b = true;
return true;
}
return false;
}
bool OptionParser::setOptionToValue(const char *Name, const char *Value) {
for (size_t I = 0; I < NumberOfOptions; ++I) {
const uintptr_t Len = strlen(Options[I].Name);
if (strncmp(Name, Options[I].Name, Len) != 0 || Name[Len] != '=')
continue;
bool Ok = false;
switch (Options[I].Type) {
case OptionType::OT_bool:
Ok = parseBool(Value, reinterpret_cast<bool *>(Options[I].Var));
if (!Ok)
InvokeIfNonNull(
Printf, "GWP-ASan: Invalid boolean value '%s' for option '%s'.\n",
Value, Options[I].Name);
break;
case OptionType::OT_int:
char *ValueEnd;
*reinterpret_cast<int *>(Options[I].Var) =
static_cast<int>(strtol(Value, &ValueEnd, 10));
Ok =
*ValueEnd == '"' || *ValueEnd == '\'' || isSeparatorOrNull(*ValueEnd);
if (!Ok)
InvokeIfNonNull(
Printf, "GWP-ASan: Invalid integer value '%s' for option '%s'.\n",
Value, Options[I].Name);
break;
}
return Ok;
}
InvokeIfNonNull(Printf, "GWP-ASan: Unknown option '%s'.", Name);
return true;
}
void OptionParser::registerOption(const char *Name, const char *Desc,
OptionType Type, void *Var) {
assert(NumberOfOptions < MaxOptions &&
"GWP-ASan Error: Ran out of space for options.\n");
Options[NumberOfOptions].Name = Name;
Options[NumberOfOptions].Desc = Desc;
Options[NumberOfOptions].Type = Type;
Options[NumberOfOptions].Var = Var;
++NumberOfOptions;
}
void registerGwpAsanOptions(OptionParser *parser,
gwp_asan::options::Options *o) {
#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \
parser->registerOption(#Name, Description, OptionType::OT_##Type, &o->Name);
#include "gwp_asan/options.inc"
#undef GWP_ASAN_OPTION
}
const char *getGwpAsanDefaultOptions() {
return (__gwp_asan_default_options) ? __gwp_asan_default_options() : "";
}
gwp_asan::options::Options *getOptionsInternal() {
static gwp_asan::options::Options GwpAsanOptions;
return &GwpAsanOptions;
}
} // anonymous namespace
namespace gwp_asan {
namespace options {
void initOptions(const char *OptionsStr, Printf_t PrintfForWarnings) {
Options *o = getOptionsInternal();
o->setDefaults();
OptionParser Parser(PrintfForWarnings);
registerGwpAsanOptions(&Parser, o);
// Override from the weak function definition in this executable.
Parser.parseString(getGwpAsanDefaultOptions());
// Override from the provided options string.
Parser.parseString(OptionsStr);
if (o->help)
Parser.printOptionDescriptions();
if (!o->Enabled)
return;
if (o->MaxSimultaneousAllocations <= 0) {
InvokeIfNonNull(
PrintfForWarnings,
"GWP-ASan ERROR: MaxSimultaneousAllocations must be > 0 when GWP-ASan "
"is enabled.\n");
o->Enabled = false;
}
if (o->SampleRate <= 0) {
InvokeIfNonNull(
PrintfForWarnings,
"GWP-ASan ERROR: SampleRate must be > 0 when GWP-ASan is enabled.\n");
o->Enabled = false;
}
}
void initOptions(Printf_t PrintfForWarnings) {
initOptions(getenv("GWP_ASAN_OPTIONS"), PrintfForWarnings);
}
Options &getOptions() { return *getOptionsInternal(); }
} // namespace options
} // namespace gwp_asan