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
32namespace absl {
33namespace flags_internal {
34namespace {
35
36void 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.
63class 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};
106constexpr size_t FlagPtrMap::kNumBuckets;
107
108} // namespace
109
110class 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
160FlagRegistry* FlagRegistry::GlobalRegistry() {
161 static FlagRegistry* global_registry = new FlagRegistry;
162 return global_registry;
163}
164
165namespace {
166
167class 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
178void 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
233CommandLineFlag* 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
247CommandLineFlag* 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
256CommandLineFlag* 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
270class 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
375FlagSaver::FlagSaver() : impl_(new FlagSaverImpl()) {
376 impl_->SaveFromRegistry();
377}
378
379void FlagSaver::Ignore() {
380 delete impl_;
381 impl_ = nullptr;
382}
383
384FlagSaver::~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
399struct 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
408void 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
427CommandLineFlag* 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
435CommandLineFlag* FindCommandLineV1Flag(const void* flag_ptr) {
436 FlagRegistry* const registry = FlagRegistry::GlobalRegistry();
437 FlagRegistryLock frl(registry);
438
439 return registry->FindFlagViaPtrLocked(flag_ptr);
440}
441
442CommandLineFlag* 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
451void 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
459void ForEachFlag(std::function<void(CommandLineFlag*)> visitor) {
460 FlagRegistry* const registry = FlagRegistry::GlobalRegistry();
461 FlagRegistryLock frl(registry);
462 ForEachFlagUnlocked(visitor);
463}
464
465// --------------------------------------------------------------------
466
467void 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
482bool RegisterCommandLineFlag(CommandLineFlag* flag, const void* ptr) {
483 FlagRegistry::GlobalRegistry()->RegisterFlag(flag, ptr);
484 return true;
485}
486
487// --------------------------------------------------------------------
488
489bool 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
503bool 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