| 1 | // |
| 2 | // Copyright 2019 The Abseil Authors. |
| 3 | // |
| 4 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | // you may not use this file except in compliance with the License. |
| 6 | // You may obtain a copy of the License at |
| 7 | // |
| 8 | // https://www.apache.org/licenses/LICENSE-2.0 |
| 9 | // |
| 10 | // Unless required by applicable law or agreed to in writing, software |
| 11 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | // See the License for the specific language governing permissions and |
| 14 | // limitations under the License. |
| 15 | |
| 16 | #include "absl/flags/internal/registry.h" |
| 17 | |
| 18 | #include "absl/base/dynamic_annotations.h" |
| 19 | #include "absl/base/internal/raw_logging.h" |
| 20 | #include "absl/flags/config.h" |
| 21 | #include "absl/flags/usage_config.h" |
| 22 | #include "absl/strings/str_cat.h" |
| 23 | #include "absl/strings/string_view.h" |
| 24 | #include "absl/synchronization/mutex.h" |
| 25 | |
| 26 | // -------------------------------------------------------------------- |
| 27 | // FlagRegistry implementation |
| 28 | // A FlagRegistry holds all flag objects indexed |
| 29 | // by their names so that if you know a flag's name you can access or |
| 30 | // set it. |
| 31 | |
| 32 | namespace absl { |
| 33 | namespace flags_internal { |
| 34 | namespace { |
| 35 | |
| 36 | void DestroyFlag(CommandLineFlag* flag) NO_THREAD_SAFETY_ANALYSIS { |
| 37 | // Values are heap allocated for retired and Abseil Flags. |
| 38 | if (flag->IsRetired() || flag->IsAbseilFlag()) { |
| 39 | if (flag->cur) Delete(flag->op, flag->cur); |
| 40 | if (flag->def) Delete(flag->op, flag->def); |
| 41 | } |
| 42 | |
| 43 | delete flag->locks; |
| 44 | |
| 45 | // CommandLineFlag handle object is heap allocated for non Abseil Flags. |
| 46 | if (!flag->IsAbseilFlag()) { |
| 47 | delete flag; |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // -------------------------------------------------------------------- |
| 52 | // FlagRegistry |
| 53 | // A FlagRegistry singleton object holds all flag objects indexed |
| 54 | // by their names so that if you know a flag's name (as a C |
| 55 | // string), you can access or set it. If the function is named |
| 56 | // FooLocked(), you must own the registry lock before calling |
| 57 | // the function; otherwise, you should *not* hold the lock, and |
| 58 | // the function will acquire it itself if needed. |
| 59 | // -------------------------------------------------------------------- |
| 60 | |
| 61 | // A map from flag pointer to CommandLineFlag*. Used when registering |
| 62 | // validators. |
| 63 | class FlagPtrMap { |
| 64 | public: |
| 65 | void Register(CommandLineFlag* flag) { |
| 66 | auto& vec = buckets_[BucketForFlag(flag->cur)]; |
| 67 | if (vec.size() == vec.capacity()) { |
| 68 | // Bypass default 2x growth factor with 1.25 so we have fuller vectors. |
| 69 | // This saves 4% memory compared to default growth. |
| 70 | vec.reserve(vec.size() * 1.25 + 0.5); |
| 71 | } |
| 72 | vec.push_back(flag); |
| 73 | } |
| 74 | |
| 75 | CommandLineFlag* FindByPtr(const void* flag_ptr) { |
| 76 | const auto& flag_vector = buckets_[BucketForFlag(flag_ptr)]; |
| 77 | for (CommandLineFlag* entry : flag_vector) { |
| 78 | if (entry->cur == flag_ptr) { |
| 79 | return entry; |
| 80 | } |
| 81 | } |
| 82 | return nullptr; |
| 83 | } |
| 84 | |
| 85 | private: |
| 86 | // Instead of std::map, we use a custom hash table where each bucket stores |
| 87 | // flags in a vector. This reduces memory usage 40% of the memory that would |
| 88 | // have been used by std::map. |
| 89 | // |
| 90 | // kNumBuckets was picked as a large enough prime. As of writing this code, a |
| 91 | // typical large binary has ~8k (old-style) flags, and this would gives |
| 92 | // buckets with roughly 50 elements each. |
| 93 | // |
| 94 | // Note that reads to this hash table are rare: exactly as many as we have |
| 95 | // flags with validators. As of writing, a typical binary only registers 52 |
| 96 | // validated flags. |
| 97 | static constexpr size_t kNumBuckets = 163; |
| 98 | std::vector<CommandLineFlag*> buckets_[kNumBuckets]; |
| 99 | |
| 100 | static int BucketForFlag(const void* ptr) { |
| 101 | // Modulo a prime is good enough here. On a real program, bucket size stddev |
| 102 | // after registering 8k flags is ~5 (mean size at 51). |
| 103 | return reinterpret_cast<uintptr_t>(ptr) % kNumBuckets; |
| 104 | } |
| 105 | }; |
| 106 | constexpr size_t FlagPtrMap::kNumBuckets; |
| 107 | |
| 108 | } // namespace |
| 109 | |
| 110 | class FlagRegistry { |
| 111 | public: |
| 112 | FlagRegistry() = default; |
| 113 | ~FlagRegistry() { |
| 114 | for (auto& p : flags_) { |
| 115 | DestroyFlag(p.second); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | // Store a flag in this registry. Takes ownership of *flag. |
| 120 | // If ptr is non-null, the flag can later be found by calling |
| 121 | // FindFlagViaPtrLocked(ptr). |
| 122 | void RegisterFlag(CommandLineFlag* flag, const void* ptr); |
| 123 | |
| 124 | void Lock() EXCLUSIVE_LOCK_FUNCTION(lock_) { lock_.Lock(); } |
| 125 | void Unlock() UNLOCK_FUNCTION(lock_) { lock_.Unlock(); } |
| 126 | |
| 127 | // Returns the flag object for the specified name, or nullptr if not found. |
| 128 | // Will emit a warning if a 'retired' flag is specified. |
| 129 | CommandLineFlag* FindFlagLocked(absl::string_view name); |
| 130 | |
| 131 | // Returns the retired flag object for the specified name, or nullptr if not |
| 132 | // found or not retired. Does not emit a warning. |
| 133 | CommandLineFlag* FindRetiredFlagLocked(absl::string_view name); |
| 134 | |
| 135 | // Returns the flag object whose current-value is stored at flag_ptr. |
| 136 | CommandLineFlag* FindFlagViaPtrLocked(const void* flag_ptr); |
| 137 | |
| 138 | static FlagRegistry* GlobalRegistry(); // returns a singleton registry |
| 139 | |
| 140 | private: |
| 141 | friend class FlagSaverImpl; // reads all the flags in order to copy them |
| 142 | friend void ForEachFlagUnlocked( |
| 143 | std::function<void(CommandLineFlag*)> visitor); |
| 144 | |
| 145 | // The map from name to flag, for FindFlagLocked(). |
| 146 | using FlagMap = std::map<absl::string_view, CommandLineFlag*>; |
| 147 | using FlagIterator = FlagMap::iterator; |
| 148 | using FlagConstIterator = FlagMap::const_iterator; |
| 149 | FlagMap flags_; |
| 150 | |
| 151 | FlagPtrMap flag_ptr_map_; |
| 152 | |
| 153 | absl::Mutex lock_; |
| 154 | |
| 155 | // Disallow |
| 156 | FlagRegistry(const FlagRegistry&); |
| 157 | FlagRegistry& operator=(const FlagRegistry&); |
| 158 | }; |
| 159 | |
| 160 | FlagRegistry* FlagRegistry::GlobalRegistry() { |
| 161 | static FlagRegistry* global_registry = new FlagRegistry; |
| 162 | return global_registry; |
| 163 | } |
| 164 | |
| 165 | namespace { |
| 166 | |
| 167 | class FlagRegistryLock { |
| 168 | public: |
| 169 | explicit FlagRegistryLock(FlagRegistry* fr) : fr_(fr) { fr_->Lock(); } |
| 170 | ~FlagRegistryLock() { fr_->Unlock(); } |
| 171 | |
| 172 | private: |
| 173 | FlagRegistry* const fr_; |
| 174 | }; |
| 175 | |
| 176 | } // namespace |
| 177 | |
| 178 | void FlagRegistry::RegisterFlag(CommandLineFlag* flag, const void* ptr) { |
| 179 | FlagRegistryLock registry_lock(this); |
| 180 | std::pair<FlagIterator, bool> ins = |
| 181 | flags_.insert(FlagMap::value_type(flag->Name(), flag)); |
| 182 | if (ins.second == false) { // means the name was already in the map |
| 183 | CommandLineFlag* old_flag = ins.first->second; |
| 184 | if (flag->IsRetired() != old_flag->IsRetired()) { |
| 185 | // All registrations must agree on the 'retired' flag. |
| 186 | flags_internal::ReportUsageError( |
| 187 | absl::StrCat( |
| 188 | "Retired flag '" , flag->Name(), |
| 189 | "' was defined normally in file '" , |
| 190 | (flag->IsRetired() ? old_flag->Filename() : flag->Filename()), |
| 191 | "'." ), |
| 192 | true); |
| 193 | } else if (flag->op != old_flag->op) { |
| 194 | flags_internal::ReportUsageError( |
| 195 | absl::StrCat("Flag '" , flag->Name(), |
| 196 | "' was defined more than once but with " |
| 197 | "differing types. Defined in files '" , |
| 198 | old_flag->Filename(), "' and '" , flag->Filename(), |
| 199 | "' with types '" , old_flag->Typename(), "' and '" , |
| 200 | flag->Typename(), "', respectively." ), |
| 201 | true); |
| 202 | } else if (old_flag->IsRetired()) { |
| 203 | // Retired definitions are idempotent. Just keep the old one. |
| 204 | DestroyFlag(flag); |
| 205 | return; |
| 206 | } else if (old_flag->Filename() != flag->Filename()) { |
| 207 | flags_internal::ReportUsageError( |
| 208 | absl::StrCat("Flag '" , flag->Name(), |
| 209 | "' was defined more than once (in files '" , |
| 210 | old_flag->Filename(), "' and '" , flag->Filename(), |
| 211 | "')." ), |
| 212 | true); |
| 213 | } else { |
| 214 | flags_internal::ReportUsageError( |
| 215 | absl::StrCat( |
| 216 | "Something wrong with flag '" , flag->Name(), "' in file '" , |
| 217 | flag->Filename(), "'. One possibility: file '" , flag->Filename(), |
| 218 | "' is being linked both statically and dynamically into this " |
| 219 | "executable. e.g. some files listed as srcs to a test and also " |
| 220 | "listed as srcs of some shared lib deps of the same test." ), |
| 221 | true); |
| 222 | } |
| 223 | // All cases above are fatal, except for the retired flags. |
| 224 | std::exit(1); |
| 225 | } |
| 226 | |
| 227 | if (ptr != nullptr) { |
| 228 | // This must be the first time we're seeing this flag. |
| 229 | flag_ptr_map_.Register(flag); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | CommandLineFlag* FlagRegistry::FindFlagLocked(absl::string_view name) { |
| 234 | FlagConstIterator i = flags_.find(name); |
| 235 | if (i == flags_.end()) { |
| 236 | return nullptr; |
| 237 | } |
| 238 | |
| 239 | if (i->second->IsRetired()) { |
| 240 | flags_internal::ReportUsageError( |
| 241 | absl::StrCat("Accessing retired flag '" , name, "'" ), false); |
| 242 | } |
| 243 | |
| 244 | return i->second; |
| 245 | } |
| 246 | |
| 247 | CommandLineFlag* FlagRegistry::FindRetiredFlagLocked(absl::string_view name) { |
| 248 | FlagConstIterator i = flags_.find(name); |
| 249 | if (i == flags_.end() || !i->second->IsRetired()) { |
| 250 | return nullptr; |
| 251 | } |
| 252 | |
| 253 | return i->second; |
| 254 | } |
| 255 | |
| 256 | CommandLineFlag* FlagRegistry::FindFlagViaPtrLocked(const void* flag_ptr) { |
| 257 | return flag_ptr_map_.FindByPtr(flag_ptr); |
| 258 | } |
| 259 | |
| 260 | // -------------------------------------------------------------------- |
| 261 | // FlagSaver |
| 262 | // FlagSaverImpl |
| 263 | // This class stores the states of all flags at construct time, |
| 264 | // and restores all flags to that state at destruct time. |
| 265 | // Its major implementation challenge is that it never modifies |
| 266 | // pointers in the 'main' registry, so global FLAG_* vars always |
| 267 | // point to the right place. |
| 268 | // -------------------------------------------------------------------- |
| 269 | |
| 270 | class FlagSaverImpl { |
| 271 | public: |
| 272 | // Constructs an empty FlagSaverImpl object. |
| 273 | FlagSaverImpl() {} |
| 274 | ~FlagSaverImpl() { |
| 275 | // reclaim memory from each of our CommandLineFlags |
| 276 | for (const SavedFlag& src : backup_registry_) { |
| 277 | Delete(src.op, src.current); |
| 278 | Delete(src.op, src.default_value); |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | // Saves the flag states from the flag registry into this object. |
| 283 | // It's an error to call this more than once. |
| 284 | // Must be called when the registry mutex is not held. |
| 285 | void SaveFromRegistry() { |
| 286 | assert(backup_registry_.empty()); // call only once! |
| 287 | SavedFlag saved; |
| 288 | flags_internal::ForEachFlag([&](flags_internal::CommandLineFlag* flag) { |
| 289 | if (flag->IsRetired()) return; |
| 290 | |
| 291 | saved.name = flag->Name(); |
| 292 | saved.op = flag->op; |
| 293 | saved.marshalling_op = flag->marshalling_op; |
| 294 | { |
| 295 | absl::MutexLock l(InitFlagIfNecessary(flag)); |
| 296 | saved.validator = flag->validator; |
| 297 | saved.modified = flag->modified; |
| 298 | saved.on_command_line = flag->IsSpecifiedOnCommandLine(); |
| 299 | saved.current = Clone(saved.op, flag->cur); |
| 300 | saved.default_value = Clone(saved.op, flag->def); |
| 301 | saved.counter = flag->counter; |
| 302 | } |
| 303 | backup_registry_.push_back(saved); |
| 304 | }); |
| 305 | } |
| 306 | |
| 307 | // Restores the saved flag states into the flag registry. We |
| 308 | // assume no flags were added or deleted from the registry since |
| 309 | // the SaveFromRegistry; if they were, that's trouble! Must be |
| 310 | // called when the registry mutex is not held. |
| 311 | void RestoreToRegistry() { |
| 312 | FlagRegistry* const global_registry = FlagRegistry::GlobalRegistry(); |
| 313 | FlagRegistryLock frl(global_registry); |
| 314 | for (const SavedFlag& src : backup_registry_) { |
| 315 | CommandLineFlag* flag = global_registry->FindFlagLocked(src.name); |
| 316 | // If null, flag got deleted from registry. |
| 317 | if (!flag) continue; |
| 318 | |
| 319 | bool restored = false; |
| 320 | { |
| 321 | absl::Mutex* mu = InitFlagIfNecessary(flag); |
| 322 | absl::MutexLock l(mu); |
| 323 | flag->validator = src.validator; |
| 324 | flag->modified = src.modified; |
| 325 | flag->on_command_line = src.on_command_line; |
| 326 | if (flag->counter != src.counter || |
| 327 | ChangedDirectly(flag, src.default_value, flag->def)) { |
| 328 | flag->counter++; |
| 329 | Copy(src.op, src.default_value, flag->def); |
| 330 | } |
| 331 | if (flag->counter != src.counter || |
| 332 | ChangedDirectly(flag, src.current, flag->cur)) { |
| 333 | restored = true; |
| 334 | flag->counter++; |
| 335 | Copy(src.op, src.current, flag->cur); |
| 336 | UpdateCopy(flag, mu); |
| 337 | |
| 338 | // Revalidate the flag because the validator might store state based |
| 339 | // on the flag's value, which just changed due to the restore. |
| 340 | // Failing validation is ignored because it's assumed that the flag |
| 341 | // was valid previously and there's little that can be done about it |
| 342 | // here, anyway. |
| 343 | Validate(flag, flag->cur); |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | // Log statements must be done when no flag lock is held. |
| 348 | if (restored) { |
| 349 | ABSL_INTERNAL_LOG( |
| 350 | INFO, absl::StrCat("Restore saved value of " , flag->Name(), ": " , |
| 351 | Unparse(src.marshalling_op, src.current))); |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | private: |
| 357 | struct SavedFlag { |
| 358 | absl::string_view name; |
| 359 | FlagOpFn op; |
| 360 | FlagMarshallingOpFn marshalling_op; |
| 361 | int64_t counter; |
| 362 | bool modified; |
| 363 | bool on_command_line; |
| 364 | bool (*validator)(); |
| 365 | const void* current; // nullptr after restore |
| 366 | const void* default_value; // nullptr after restore |
| 367 | }; |
| 368 | |
| 369 | std::vector<SavedFlag> backup_registry_; |
| 370 | |
| 371 | FlagSaverImpl(const FlagSaverImpl&); // no copying! |
| 372 | void operator=(const FlagSaverImpl&); |
| 373 | }; |
| 374 | |
| 375 | FlagSaver::FlagSaver() : impl_(new FlagSaverImpl()) { |
| 376 | impl_->SaveFromRegistry(); |
| 377 | } |
| 378 | |
| 379 | void FlagSaver::Ignore() { |
| 380 | delete impl_; |
| 381 | impl_ = nullptr; |
| 382 | } |
| 383 | |
| 384 | FlagSaver::~FlagSaver() { |
| 385 | if (!impl_) return; |
| 386 | |
| 387 | impl_->RestoreToRegistry(); |
| 388 | delete impl_; |
| 389 | } |
| 390 | |
| 391 | // -------------------------------------------------------------------- |
| 392 | // GetAllFlags() |
| 393 | // The main way the FlagRegistry class exposes its data. This |
| 394 | // returns, as strings, all the info about all the flags in |
| 395 | // the main registry, sorted first by filename they are defined |
| 396 | // in, and then by flagname. |
| 397 | // -------------------------------------------------------------------- |
| 398 | |
| 399 | struct FilenameFlagnameLess { |
| 400 | bool operator()(const CommandLineFlagInfo& a, |
| 401 | const CommandLineFlagInfo& b) const { |
| 402 | int cmp = absl::string_view(a.filename).compare(b.filename); |
| 403 | if (cmp != 0) return cmp < 0; |
| 404 | return a.name < b.name; |
| 405 | } |
| 406 | }; |
| 407 | |
| 408 | void FillCommandLineFlagInfo(CommandLineFlag* flag, |
| 409 | CommandLineFlagInfo* result) { |
| 410 | result->name = std::string(flag->Name()); |
| 411 | result->type = std::string(flag->Typename()); |
| 412 | result->description = flag->Help(); |
| 413 | result->filename = flag->Filename(); |
| 414 | |
| 415 | UpdateModifiedBit(flag); |
| 416 | |
| 417 | absl::MutexLock l(InitFlagIfNecessary(flag)); |
| 418 | result->current_value = flag->CurrentValue(); |
| 419 | result->default_value = flag->DefaultValue(); |
| 420 | result->is_default = !flag->modified; |
| 421 | result->has_validator_fn = (flag->validator != nullptr); |
| 422 | result->flag_ptr = flag->IsAbseilFlag() ? nullptr : flag->cur; |
| 423 | } |
| 424 | |
| 425 | // -------------------------------------------------------------------- |
| 426 | |
| 427 | CommandLineFlag* FindCommandLineFlag(absl::string_view name) { |
| 428 | if (name.empty()) return nullptr; |
| 429 | FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); |
| 430 | FlagRegistryLock frl(registry); |
| 431 | |
| 432 | return registry->FindFlagLocked(name); |
| 433 | } |
| 434 | |
| 435 | CommandLineFlag* FindCommandLineV1Flag(const void* flag_ptr) { |
| 436 | FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); |
| 437 | FlagRegistryLock frl(registry); |
| 438 | |
| 439 | return registry->FindFlagViaPtrLocked(flag_ptr); |
| 440 | } |
| 441 | |
| 442 | CommandLineFlag* FindRetiredFlag(absl::string_view name) { |
| 443 | FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); |
| 444 | FlagRegistryLock frl(registry); |
| 445 | |
| 446 | return registry->FindRetiredFlagLocked(name); |
| 447 | } |
| 448 | |
| 449 | // -------------------------------------------------------------------- |
| 450 | |
| 451 | void ForEachFlagUnlocked(std::function<void(CommandLineFlag*)> visitor) { |
| 452 | FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); |
| 453 | for (FlagRegistry::FlagConstIterator i = registry->flags_.begin(); |
| 454 | i != registry->flags_.end(); ++i) { |
| 455 | visitor(i->second); |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | void ForEachFlag(std::function<void(CommandLineFlag*)> visitor) { |
| 460 | FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); |
| 461 | FlagRegistryLock frl(registry); |
| 462 | ForEachFlagUnlocked(visitor); |
| 463 | } |
| 464 | |
| 465 | // -------------------------------------------------------------------- |
| 466 | |
| 467 | void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT) { |
| 468 | flags_internal::ForEachFlag([&](CommandLineFlag* flag) { |
| 469 | if (flag->IsRetired()) return; |
| 470 | |
| 471 | CommandLineFlagInfo fi; |
| 472 | FillCommandLineFlagInfo(flag, &fi); |
| 473 | OUTPUT->push_back(fi); |
| 474 | }); |
| 475 | |
| 476 | // Now sort the flags, first by filename they occur in, then alphabetically |
| 477 | std::sort(OUTPUT->begin(), OUTPUT->end(), FilenameFlagnameLess()); |
| 478 | } |
| 479 | |
| 480 | // -------------------------------------------------------------------- |
| 481 | |
| 482 | bool RegisterCommandLineFlag(CommandLineFlag* flag, const void* ptr) { |
| 483 | FlagRegistry::GlobalRegistry()->RegisterFlag(flag, ptr); |
| 484 | return true; |
| 485 | } |
| 486 | |
| 487 | // -------------------------------------------------------------------- |
| 488 | |
| 489 | bool Retire(FlagOpFn ops, FlagMarshallingOpFn marshalling_ops, |
| 490 | const char* name) { |
| 491 | auto* flag = new CommandLineFlag( |
| 492 | name, |
| 493 | /*help_text=*/absl::flags_internal::HelpText::FromStaticCString(nullptr), |
| 494 | /*filename_arg=*/"RETIRED" , ops, marshalling_ops, |
| 495 | /*initial_value_gen=*/nullptr, |
| 496 | /*retired_arg=*/true, nullptr, nullptr); |
| 497 | FlagRegistry::GlobalRegistry()->RegisterFlag(flag, nullptr); |
| 498 | return true; |
| 499 | } |
| 500 | |
| 501 | // -------------------------------------------------------------------- |
| 502 | |
| 503 | bool IsRetiredFlag(absl::string_view name, bool* type_is_bool) { |
| 504 | assert(!name.empty()); |
| 505 | CommandLineFlag* flag = flags_internal::FindRetiredFlag(name); |
| 506 | if (flag == nullptr) { |
| 507 | return false; |
| 508 | } |
| 509 | assert(type_is_bool); |
| 510 | *type_is_bool = flag->IsOfType<bool>(); |
| 511 | return true; |
| 512 | } |
| 513 | |
| 514 | } // namespace flags_internal |
| 515 | } // namespace absl |
| 516 | |