| 1 | //===-- sanitizer_flag_parser.cpp -----------------------------------------===// |
| 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 | // This file is a part of ThreadSanitizer/AddressSanitizer runtime. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "sanitizer_flag_parser.h" |
| 14 | |
| 15 | #include "sanitizer_common.h" |
| 16 | #include "sanitizer_flag_parser.h" |
| 17 | #include "sanitizer_flags.h" |
| 18 | #include "sanitizer_libc.h" |
| 19 | |
| 20 | namespace __sanitizer { |
| 21 | |
| 22 | class UnknownFlags { |
| 23 | static const int kMaxUnknownFlags = 20; |
| 24 | const char *unknown_flags_[kMaxUnknownFlags]; |
| 25 | int n_unknown_flags_; |
| 26 | |
| 27 | public: |
| 28 | void Add(const char *name) { |
| 29 | CHECK_LT(n_unknown_flags_, kMaxUnknownFlags); |
| 30 | unknown_flags_[n_unknown_flags_++] = name; |
| 31 | } |
| 32 | |
| 33 | void Report() { |
| 34 | if (!n_unknown_flags_) return; |
| 35 | Printf(format: "WARNING: found %d unrecognized flag(s):\n" , n_unknown_flags_); |
| 36 | for (int i = 0; i < n_unknown_flags_; ++i) |
| 37 | Printf(format: " %s\n" , unknown_flags_[i]); |
| 38 | n_unknown_flags_ = 0; |
| 39 | } |
| 40 | }; |
| 41 | |
| 42 | UnknownFlags unknown_flags; |
| 43 | |
| 44 | void ReportUnrecognizedFlags() { |
| 45 | unknown_flags.Report(); |
| 46 | } |
| 47 | |
| 48 | char *FlagParser::ll_strndup(const char *s, uptr n) { |
| 49 | uptr len = internal_strnlen(s, maxlen: n); |
| 50 | char *s2 = (char *)GetGlobalLowLevelAllocator().Allocate(size: len + 1); |
| 51 | internal_memcpy(dest: s2, src: s, n: len); |
| 52 | s2[len] = 0; |
| 53 | return s2; |
| 54 | } |
| 55 | |
| 56 | void FlagParser::PrintFlagDescriptions() { |
| 57 | char buffer[128]; |
| 58 | buffer[sizeof(buffer) - 1] = '\0'; |
| 59 | Printf(format: "Available flags for %s:\n" , SanitizerToolName); |
| 60 | for (int i = 0; i < n_flags_; ++i) { |
| 61 | bool truncated = !(flags_[i].handler->Format(buffer, size: sizeof(buffer))); |
| 62 | CHECK_EQ(buffer[sizeof(buffer) - 1], '\0'); |
| 63 | const char *truncation_str = truncated ? " Truncated" : "" ; |
| 64 | Printf(format: "\t%s\n\t\t- %s (Current Value%s: %s)\n" , flags_[i].name, |
| 65 | flags_[i].desc, truncation_str, buffer); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | void FlagParser::fatal_error(const char *err) { |
| 70 | Printf(format: "%s: ERROR: %s\n" , SanitizerToolName, err); |
| 71 | Die(); |
| 72 | } |
| 73 | |
| 74 | bool FlagParser::is_space(char c) { |
| 75 | return c == ' ' || c == ',' || c == ':' || c == '\n' || c == '\t' || |
| 76 | c == '\r'; |
| 77 | } |
| 78 | |
| 79 | void FlagParser::skip_whitespace() { |
| 80 | while (is_space(c: buf_[pos_])) ++pos_; |
| 81 | } |
| 82 | |
| 83 | void FlagParser::parse_flag(const char *env_option_name) { |
| 84 | uptr name_start = pos_; |
| 85 | while (buf_[pos_] != 0 && buf_[pos_] != '=' && !is_space(c: buf_[pos_])) ++pos_; |
| 86 | if (buf_[pos_] != '=') { |
| 87 | if (env_option_name) { |
| 88 | Printf(format: "%s: ERROR: expected '=' in %s\n" , SanitizerToolName, |
| 89 | env_option_name); |
| 90 | Die(); |
| 91 | } else { |
| 92 | fatal_error(err: "expected '='" ); |
| 93 | } |
| 94 | } |
| 95 | char *name = ll_strndup(s: buf_ + name_start, n: pos_ - name_start); |
| 96 | |
| 97 | uptr value_start = ++pos_; |
| 98 | char *value; |
| 99 | if (buf_[pos_] == '\'' || buf_[pos_] == '"') { |
| 100 | char quote = buf_[pos_++]; |
| 101 | while (buf_[pos_] != 0 && buf_[pos_] != quote) ++pos_; |
| 102 | if (buf_[pos_] == 0) fatal_error(err: "unterminated string" ); |
| 103 | value = ll_strndup(s: buf_ + value_start + 1, n: pos_ - value_start - 1); |
| 104 | ++pos_; // consume the closing quote |
| 105 | } else { |
| 106 | while (buf_[pos_] != 0 && !is_space(c: buf_[pos_])) ++pos_; |
| 107 | if (buf_[pos_] != 0 && !is_space(c: buf_[pos_])) |
| 108 | fatal_error(err: "expected separator or eol" ); |
| 109 | value = ll_strndup(s: buf_ + value_start, n: pos_ - value_start); |
| 110 | } |
| 111 | |
| 112 | bool res = run_handler(name, value); |
| 113 | if (!res) fatal_error(err: "Flag parsing failed." ); |
| 114 | } |
| 115 | |
| 116 | void FlagParser::parse_flags(const char *env_option_name) { |
| 117 | while (true) { |
| 118 | skip_whitespace(); |
| 119 | if (buf_[pos_] == 0) break; |
| 120 | parse_flag(env_option_name); |
| 121 | } |
| 122 | |
| 123 | // Do a sanity check for certain flags. |
| 124 | if (common_flags_dont_use.malloc_context_size < 1) |
| 125 | common_flags_dont_use.malloc_context_size = 1; |
| 126 | } |
| 127 | |
| 128 | void FlagParser::ParseStringFromEnv(const char *env_name) { |
| 129 | const char *env = GetEnv(name: env_name); |
| 130 | VPrintf(1, "%s: %s\n" , env_name, env ? env : "<empty>" ); |
| 131 | ParseString(s: env, env_name); |
| 132 | } |
| 133 | |
| 134 | void FlagParser::ParseString(const char *s, const char *env_option_name) { |
| 135 | if (!s) return; |
| 136 | // Backup current parser state to allow nested ParseString() calls. |
| 137 | const char *old_buf_ = buf_; |
| 138 | uptr old_pos_ = pos_; |
| 139 | buf_ = s; |
| 140 | pos_ = 0; |
| 141 | |
| 142 | parse_flags(env_option_name); |
| 143 | |
| 144 | buf_ = old_buf_; |
| 145 | pos_ = old_pos_; |
| 146 | } |
| 147 | |
| 148 | bool FlagParser::ParseFile(const char *path, bool ignore_missing) { |
| 149 | static const uptr kMaxIncludeSize = 1 << 15; |
| 150 | char *data; |
| 151 | uptr data_mapped_size; |
| 152 | error_t err; |
| 153 | uptr len; |
| 154 | if (!ReadFileToBuffer(file_name: path, buff: &data, buff_size: &data_mapped_size, read_len: &len, |
| 155 | max_len: Max(a: kMaxIncludeSize, b: GetPageSizeCached()), errno_p: &err)) { |
| 156 | if (ignore_missing) |
| 157 | return true; |
| 158 | Printf(format: "Failed to read options from '%s': error %d\n" , path, err); |
| 159 | return false; |
| 160 | } |
| 161 | ParseString(s: data, env_option_name: path); |
| 162 | UnmapOrDie(addr: data, size: data_mapped_size); |
| 163 | return true; |
| 164 | } |
| 165 | |
| 166 | bool FlagParser::run_handler(const char *name, const char *value) { |
| 167 | for (int i = 0; i < n_flags_; ++i) { |
| 168 | if (internal_strcmp(s1: name, s2: flags_[i].name) == 0) |
| 169 | return flags_[i].handler->Parse(value); |
| 170 | } |
| 171 | // Unrecognized flag. This is not a fatal error, we may print a warning later. |
| 172 | unknown_flags.Add(name); |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | void FlagParser::RegisterHandler(const char *name, FlagHandlerBase *handler, |
| 177 | const char *desc) { |
| 178 | CHECK_LT(n_flags_, kMaxFlags); |
| 179 | flags_[n_flags_].name = name; |
| 180 | flags_[n_flags_].desc = desc; |
| 181 | flags_[n_flags_].handler = handler; |
| 182 | ++n_flags_; |
| 183 | } |
| 184 | |
| 185 | FlagParser::FlagParser() : n_flags_(0), buf_(nullptr), pos_(0) { |
| 186 | flags_ = |
| 187 | (Flag *)GetGlobalLowLevelAllocator().Allocate(size: sizeof(Flag) * kMaxFlags); |
| 188 | } |
| 189 | |
| 190 | } // namespace __sanitizer |
| 191 | |