//===-- sanitizer_flag_parser.cpp -----------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// // // This file is a part of ThreadSanitizer/AddressSanitizer runtime. // //===----------------------------------------------------------------------===// #include "sanitizer_flag_parser.h" #include "sanitizer_common.h" #include "sanitizer_flag_parser.h" #include "sanitizer_flags.h" #include "sanitizer_libc.h" namespace __sanitizer { LowLevelAllocator FlagParser::Alloc; class UnknownFlags { static const int kMaxUnknownFlags = 20; const char *unknown_flags_[kMaxUnknownFlags]; int n_unknown_flags_; public: void Add(const char *name) { CHECK_LT(n_unknown_flags_, kMaxUnknownFlags); unknown_flags_[n_unknown_flags_++] = name; } void Report() { if (!n_unknown_flags_) return; Printf("WARNING: found %d unrecognized flag(s):\n", n_unknown_flags_); for (int i = 0; i < n_unknown_flags_; ++i) Printf(" %s\n", unknown_flags_[i]); n_unknown_flags_ = 0; } }; UnknownFlags unknown_flags; void ReportUnrecognizedFlags() { unknown_flags.Report(); } char *FlagParser::ll_strndup(const char *s, uptr n) { uptr len = internal_strnlen(s, n); char *s2 = (char*)Alloc.Allocate(len + 1); internal_memcpy(s2, s, len); s2[len] = 0; return s2; } void FlagParser::PrintFlagDescriptions() { char buffer[128]; buffer[sizeof(buffer) - 1] = '\0'; Printf("Available flags for %s:\n", SanitizerToolName); for (int i = 0; i < n_flags_; ++i) { bool truncated = !(flags_[i].handler->Format(buffer, sizeof(buffer))); CHECK_EQ(buffer[sizeof(buffer) - 1], '\0'); const char *truncation_str = truncated ? " Truncated" : ""; Printf("\t%s\n\t\t- %s (Current Value%s: %s)\n", flags_[i].name, flags_[i].desc, truncation_str, buffer); } } void FlagParser::fatal_error(const char *err) { Printf("%s: ERROR: %s\n", SanitizerToolName, err); Die(); } bool FlagParser::is_space(char c) { return c == ' ' || c == ',' || c == ':' || c == '\n' || c == '\t' || c == '\r'; } void FlagParser::skip_whitespace() { while (is_space(buf_[pos_])) ++pos_; } void FlagParser::parse_flag(const char *env_option_name) { uptr name_start = pos_; while (buf_[pos_] != 0 && buf_[pos_] != '=' && !is_space(buf_[pos_])) ++pos_; if (buf_[pos_] != '=') { if (env_option_name) { Printf("%s: ERROR: expected '=' in %s\n", SanitizerToolName, env_option_name); Die(); } else { fatal_error("expected '='"); } } char *name = ll_strndup(buf_ + name_start, pos_ - name_start); uptr value_start = ++pos_; char *value; if (buf_[pos_] == '\'' || buf_[pos_] == '"') { char quote = buf_[pos_++]; while (buf_[pos_] != 0 && buf_[pos_] != quote) ++pos_; if (buf_[pos_] == 0) fatal_error("unterminated string"); value = ll_strndup(buf_ + value_start + 1, pos_ - value_start - 1); ++pos_; // consume the closing quote } else { while (buf_[pos_] != 0 && !is_space(buf_[pos_])) ++pos_; if (buf_[pos_] != 0 && !is_space(buf_[pos_])) fatal_error("expected separator or eol"); value = ll_strndup(buf_ + value_start, pos_ - value_start); } bool res = run_handler(name, value); if (!res) fatal_error("Flag parsing failed."); } void FlagParser::parse_flags(const char *env_option_name) { while (true) { skip_whitespace(); if (buf_[pos_] == 0) break; parse_flag(env_option_name); } // Do a sanity check for certain flags. if (common_flags_dont_use.malloc_context_size < 1) common_flags_dont_use.malloc_context_size = 1; } void FlagParser::ParseStringFromEnv(const char *env_name) { const char *env = GetEnv(env_name); VPrintf(1, "%s: %s\n", env_name, env ? env : ""); ParseString(env, env_name); } void FlagParser::ParseString(const char *s, const char *env_option_name) { if (!s) return; // Backup current parser state to allow nested ParseString() calls. const char *old_buf_ = buf_; uptr old_pos_ = pos_; buf_ = s; pos_ = 0; parse_flags(env_option_name); buf_ = old_buf_; pos_ = old_pos_; } bool FlagParser::ParseFile(const char *path, bool ignore_missing) { static const uptr kMaxIncludeSize = 1 << 15; char *data; uptr data_mapped_size; error_t err; uptr len; if (!ReadFileToBuffer(path, &data, &data_mapped_size, &len, Max(kMaxIncludeSize, GetPageSizeCached()), &err)) { if (ignore_missing) return true; Printf("Failed to read options from '%s': error %d\n", path, err); return false; } ParseString(data, path); UnmapOrDie(data, data_mapped_size); return true; } bool FlagParser::run_handler(const char *name, const char *value) { for (int i = 0; i < n_flags_; ++i) { if (internal_strcmp(name, flags_[i].name) == 0) return flags_[i].handler->Parse(value); } // Unrecognized flag. This is not a fatal error, we may print a warning later. unknown_flags.Add(name); return true; } void FlagParser::RegisterHandler(const char *name, FlagHandlerBase *handler, const char *desc) { CHECK_LT(n_flags_, kMaxFlags); flags_[n_flags_].name = name; flags_[n_flags_].desc = desc; flags_[n_flags_].handler = handler; ++n_flags_; } FlagParser::FlagParser() : n_flags_(0), buf_(nullptr), pos_(0) { flags_ = (Flag *)Alloc.Allocate(sizeof(Flag) * kMaxFlags); } } // namespace __sanitizer