//===-- 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 #include #include #include #include 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(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(Options[I].Var) = static_cast(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