| //===-- 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_libc.h" |
| #include "sanitizer_flags.h" |
| #include "sanitizer_flag_parser.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 : "<empty>"); |
| 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 |