/src/abseil-cpp/absl/flags/reflection.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // Copyright 2020 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/reflection.h" |
17 | | |
18 | | #include <assert.h> |
19 | | |
20 | | #include <atomic> |
21 | | #include <string> |
22 | | |
23 | | #include "absl/base/config.h" |
24 | | #include "absl/base/no_destructor.h" |
25 | | #include "absl/base/thread_annotations.h" |
26 | | #include "absl/container/flat_hash_map.h" |
27 | | #include "absl/flags/commandlineflag.h" |
28 | | #include "absl/flags/internal/private_handle_accessor.h" |
29 | | #include "absl/flags/internal/registry.h" |
30 | | #include "absl/flags/usage_config.h" |
31 | | #include "absl/strings/str_cat.h" |
32 | | #include "absl/strings/string_view.h" |
33 | | #include "absl/synchronization/mutex.h" |
34 | | |
35 | | namespace absl { |
36 | | ABSL_NAMESPACE_BEGIN |
37 | | namespace flags_internal { |
38 | | |
39 | | // -------------------------------------------------------------------- |
40 | | // FlagRegistry |
41 | | // A FlagRegistry singleton object holds all flag objects indexed by their |
42 | | // names so that if you know a flag's name, you can access or set it. If the |
43 | | // function is named FooLocked(), you must own the registry lock before |
44 | | // calling the function; otherwise, you should *not* hold the lock, and the |
45 | | // function will acquire it itself if needed. |
46 | | // -------------------------------------------------------------------- |
47 | | |
48 | | class FlagRegistry { |
49 | | public: |
50 | 2 | FlagRegistry() = default; |
51 | | ~FlagRegistry() = default; |
52 | | |
53 | | // Store a flag in this registry. Takes ownership of *flag. |
54 | | void RegisterFlag(CommandLineFlag& flag, const char* filename); |
55 | | |
56 | 16 | void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(lock_) { lock_.lock(); } |
57 | 0 | inline void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(lock_) { lock(); } |
58 | | |
59 | 16 | void unlock() ABSL_UNLOCK_FUNCTION(lock_) { lock_.unlock(); } |
60 | 0 | inline void Unlock() ABSL_UNLOCK_FUNCTION(lock_) { unlock(); } |
61 | | |
62 | | // Returns the flag object for the specified name, or nullptr if not found. |
63 | | // Will emit a warning if a 'retired' flag is specified. |
64 | | CommandLineFlag* FindFlag(absl::string_view name); |
65 | | |
66 | | static FlagRegistry& GlobalRegistry(); // returns a singleton registry |
67 | | |
68 | | private: |
69 | | friend class flags_internal::FlagSaverImpl; // reads all the flags in order |
70 | | // to copy them |
71 | | friend void ForEachFlag(std::function<void(CommandLineFlag&)> visitor); |
72 | | friend void FinalizeRegistry(); |
73 | | |
74 | | // The map from name to flag, for FindFlag(). |
75 | | using FlagMap = absl::flat_hash_map<absl::string_view, CommandLineFlag*>; |
76 | | using FlagIterator = FlagMap::iterator; |
77 | | using FlagConstIterator = FlagMap::const_iterator; |
78 | | FlagMap flags_; |
79 | | std::vector<CommandLineFlag*> flat_flags_; |
80 | | std::atomic<bool> finalized_flags_{false}; |
81 | | |
82 | | absl::Mutex lock_; |
83 | | |
84 | | // Disallow |
85 | | FlagRegistry(const FlagRegistry&); |
86 | | FlagRegistry& operator=(const FlagRegistry&); |
87 | | }; |
88 | | |
89 | | namespace { |
90 | | |
91 | | class FlagRegistryLock { |
92 | | public: |
93 | 16 | explicit FlagRegistryLock(FlagRegistry& fr) : fr_(fr) { fr_.lock(); } |
94 | 16 | ~FlagRegistryLock() { fr_.unlock(); } |
95 | | |
96 | | private: |
97 | | FlagRegistry& fr_; |
98 | | }; |
99 | | |
100 | | } // namespace |
101 | | |
102 | 0 | CommandLineFlag* FlagRegistry::FindFlag(absl::string_view name) { |
103 | 0 | if (finalized_flags_.load(std::memory_order_acquire)) { |
104 | | // We could save some gcus here if we make `Name()` be non-virtual. |
105 | | // We could move the `const char*` name to the base class. |
106 | 0 | auto it = std::partition_point( |
107 | 0 | flat_flags_.begin(), flat_flags_.end(), |
108 | 0 | [=](CommandLineFlag* f) { return f->Name() < name; }); |
109 | 0 | if (it != flat_flags_.end() && (*it)->Name() == name) return *it; |
110 | 0 | } |
111 | | |
112 | 0 | FlagRegistryLock frl(*this); |
113 | 0 | auto it = flags_.find(name); |
114 | 0 | return it != flags_.end() ? it->second : nullptr; |
115 | 0 | } |
116 | | |
117 | 16 | void FlagRegistry::RegisterFlag(CommandLineFlag& flag, const char* filename) { |
118 | 16 | if (filename != nullptr && |
119 | 16 | flag.Filename() != GetUsageConfig().normalize_filename(filename)) { |
120 | 0 | flags_internal::ReportUsageError( |
121 | 0 | absl::StrCat( |
122 | 0 | "Inconsistency between flag object and registration for flag '", |
123 | 0 | flag.Name(), |
124 | 0 | "', likely due to duplicate flags or an ODR violation. Relevant " |
125 | 0 | "files: ", |
126 | 0 | flag.Filename(), " and ", filename), |
127 | 0 | true); |
128 | 0 | std::exit(1); |
129 | 0 | } |
130 | | |
131 | 16 | FlagRegistryLock registry_lock(*this); |
132 | | |
133 | 16 | std::pair<FlagIterator, bool> ins = |
134 | 16 | flags_.insert(FlagMap::value_type(flag.Name(), &flag)); |
135 | 16 | if (ins.second == false) { // means the name was already in the map |
136 | 0 | CommandLineFlag& old_flag = *ins.first->second; |
137 | 0 | if (flag.IsRetired() != old_flag.IsRetired()) { |
138 | | // All registrations must agree on the 'retired' flag. |
139 | 0 | flags_internal::ReportUsageError( |
140 | 0 | absl::StrCat( |
141 | 0 | "Retired flag '", flag.Name(), "' was defined normally in file '", |
142 | 0 | (flag.IsRetired() ? old_flag.Filename() : flag.Filename()), "'."), |
143 | 0 | true); |
144 | 0 | } else if (flags_internal::PrivateHandleAccessor::TypeId(flag) != |
145 | 0 | flags_internal::PrivateHandleAccessor::TypeId(old_flag)) { |
146 | 0 | flags_internal::ReportUsageError( |
147 | 0 | absl::StrCat("Flag '", flag.Name(), |
148 | 0 | "' was defined more than once but with " |
149 | 0 | "differing types. Defined in files '", |
150 | 0 | old_flag.Filename(), "' and '", flag.Filename(), "'."), |
151 | 0 | true); |
152 | 0 | } else if (old_flag.IsRetired()) { |
153 | 0 | return; |
154 | 0 | } else if (old_flag.Filename() != flag.Filename()) { |
155 | 0 | flags_internal::ReportUsageError( |
156 | 0 | absl::StrCat("Flag '", flag.Name(), |
157 | 0 | "' was defined more than once (in files '", |
158 | 0 | old_flag.Filename(), "' and '", flag.Filename(), "')."), |
159 | 0 | true); |
160 | 0 | } else { |
161 | 0 | flags_internal::ReportUsageError( |
162 | 0 | absl::StrCat( |
163 | 0 | "Something is wrong with flag '", flag.Name(), "' in file '", |
164 | 0 | flag.Filename(), "'. One possibility: file '", flag.Filename(), |
165 | 0 | "' is being linked both statically and dynamically into this " |
166 | 0 | "executable. e.g. some files listed as srcs to a test and also " |
167 | 0 | "listed as srcs of some shared lib deps of the same test."), |
168 | 0 | true); |
169 | 0 | } |
170 | | // All cases above are fatal, except for the retired flags. |
171 | 0 | std::exit(1); |
172 | 0 | } |
173 | 16 | } |
174 | | |
175 | 16 | FlagRegistry& FlagRegistry::GlobalRegistry() { |
176 | 16 | static absl::NoDestructor<FlagRegistry> global_registry; |
177 | 16 | return *global_registry; |
178 | 16 | } |
179 | | |
180 | | // -------------------------------------------------------------------- |
181 | | |
182 | 0 | void ForEachFlag(std::function<void(CommandLineFlag&)> visitor) { |
183 | 0 | FlagRegistry& registry = FlagRegistry::GlobalRegistry(); |
184 | |
|
185 | 0 | if (registry.finalized_flags_.load(std::memory_order_acquire)) { |
186 | 0 | for (const auto& i : registry.flat_flags_) visitor(*i); |
187 | 0 | } |
188 | |
|
189 | 0 | FlagRegistryLock frl(registry); |
190 | 0 | for (const auto& i : registry.flags_) visitor(*i.second); |
191 | 0 | } |
192 | | |
193 | | // -------------------------------------------------------------------- |
194 | | |
195 | 16 | bool RegisterCommandLineFlag(CommandLineFlag& flag, const char* filename) { |
196 | 16 | FlagRegistry::GlobalRegistry().RegisterFlag(flag, filename); |
197 | 16 | return true; |
198 | 16 | } |
199 | | |
200 | 0 | void FinalizeRegistry() { |
201 | 0 | auto& registry = FlagRegistry::GlobalRegistry(); |
202 | 0 | FlagRegistryLock frl(registry); |
203 | 0 | if (registry.finalized_flags_.load(std::memory_order_relaxed)) { |
204 | | // Was already finalized. Ignore the second time. |
205 | 0 | return; |
206 | 0 | } |
207 | 0 | registry.flat_flags_.reserve(registry.flags_.size()); |
208 | 0 | for (const auto& f : registry.flags_) { |
209 | 0 | registry.flat_flags_.push_back(f.second); |
210 | 0 | } |
211 | 0 | std::sort(std::begin(registry.flat_flags_), std::end(registry.flat_flags_), |
212 | 0 | [](const CommandLineFlag* lhs, const CommandLineFlag* rhs) { |
213 | 0 | return lhs->Name() < rhs->Name(); |
214 | 0 | }); |
215 | 0 | registry.flags_.clear(); |
216 | 0 | registry.finalized_flags_.store(true, std::memory_order_release); |
217 | 0 | } |
218 | | |
219 | | // -------------------------------------------------------------------- |
220 | | |
221 | | namespace { |
222 | | |
223 | | // These are only used as constexpr global objects. |
224 | | // They do not use a virtual destructor to simplify their implementation. |
225 | | // They are not destroyed except at program exit, so leaks do not matter. |
226 | | #if defined(__GNUC__) && !defined(__clang__) |
227 | | #pragma GCC diagnostic push |
228 | | #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" |
229 | | #endif |
230 | | class RetiredFlagObj final : public CommandLineFlag { |
231 | | public: |
232 | | constexpr RetiredFlagObj(const char* name, FlagFastTypeId type_id) |
233 | 0 | : name_(name), type_id_(type_id) {} |
234 | | |
235 | | private: |
236 | 0 | absl::string_view Name() const override { return name_; } |
237 | 0 | std::string Filename() const override { |
238 | 0 | OnAccess(); |
239 | 0 | return "RETIRED"; |
240 | 0 | } |
241 | 0 | FlagFastTypeId TypeId() const override { return type_id_; } |
242 | 0 | std::string Help() const override { |
243 | 0 | OnAccess(); |
244 | 0 | return ""; |
245 | 0 | } |
246 | 0 | bool IsRetired() const override { return true; } |
247 | 0 | bool IsSpecifiedOnCommandLine() const override { |
248 | 0 | OnAccess(); |
249 | 0 | return false; |
250 | 0 | } |
251 | 0 | std::string DefaultValue() const override { |
252 | 0 | OnAccess(); |
253 | 0 | return ""; |
254 | 0 | } |
255 | 0 | std::string CurrentValue() const override { |
256 | 0 | OnAccess(); |
257 | 0 | return ""; |
258 | 0 | } |
259 | | |
260 | | // Any input is valid |
261 | 0 | bool ValidateInputValue(absl::string_view) const override { |
262 | 0 | OnAccess(); |
263 | 0 | return true; |
264 | 0 | } |
265 | | |
266 | 0 | std::unique_ptr<flags_internal::FlagStateInterface> SaveState() override { |
267 | 0 | return nullptr; |
268 | 0 | } |
269 | | |
270 | | bool ParseFrom(absl::string_view, flags_internal::FlagSettingMode, |
271 | 0 | flags_internal::ValueSource, std::string&) override { |
272 | 0 | OnAccess(); |
273 | 0 | return false; |
274 | 0 | } |
275 | | |
276 | 0 | void CheckDefaultValueParsingRoundtrip() const override { OnAccess(); } |
277 | | |
278 | 0 | void Read(void*) const override { OnAccess(); } |
279 | | |
280 | 0 | void OnAccess() const { |
281 | 0 | flags_internal::ReportUsageError( |
282 | 0 | absl::StrCat("Accessing retired flag '", name_, "'"), false); |
283 | 0 | } |
284 | | |
285 | | // Data members |
286 | | const char* const name_; |
287 | | const FlagFastTypeId type_id_; |
288 | | }; |
289 | | #if defined(__GNUC__) && !defined(__clang__) |
290 | | #pragma GCC diagnostic pop |
291 | | #endif |
292 | | |
293 | | } // namespace |
294 | | |
295 | 0 | void Retire(const char* name, FlagFastTypeId type_id, unsigned char* buf) { |
296 | 0 | static_assert(sizeof(RetiredFlagObj) == kRetiredFlagObjSize, ""); |
297 | 0 | static_assert(alignof(RetiredFlagObj) == kRetiredFlagObjAlignment, ""); |
298 | 0 | auto* flag = ::new (buf) flags_internal::RetiredFlagObj(name, type_id); |
299 | 0 | FlagRegistry::GlobalRegistry().RegisterFlag(*flag, nullptr); |
300 | 0 | } |
301 | | |
302 | | // -------------------------------------------------------------------- |
303 | | |
304 | | class FlagSaverImpl { |
305 | | public: |
306 | 0 | FlagSaverImpl() = default; |
307 | | FlagSaverImpl(const FlagSaverImpl&) = delete; |
308 | | void operator=(const FlagSaverImpl&) = delete; |
309 | | |
310 | | // Saves the flag states from the flag registry into this object. |
311 | | // It's an error to call this more than once. |
312 | 0 | void SaveFromRegistry() { |
313 | 0 | assert(backup_registry_.empty()); // call only once! |
314 | 0 | flags_internal::ForEachFlag([&](CommandLineFlag& flag) { |
315 | 0 | if (auto flag_state = |
316 | 0 | flags_internal::PrivateHandleAccessor::SaveState(flag)) { |
317 | 0 | backup_registry_.emplace_back(std::move(flag_state)); |
318 | 0 | } |
319 | 0 | }); |
320 | 0 | } |
321 | | |
322 | | // Restores the saved flag states into the flag registry. |
323 | 0 | void RestoreToRegistry() { |
324 | 0 | for (const auto& flag_state : backup_registry_) { |
325 | 0 | flag_state->Restore(); |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | | private: |
330 | | std::vector<std::unique_ptr<flags_internal::FlagStateInterface>> |
331 | | backup_registry_; |
332 | | }; |
333 | | |
334 | | } // namespace flags_internal |
335 | | |
336 | 0 | FlagSaver::FlagSaver() : impl_(new flags_internal::FlagSaverImpl) { |
337 | 0 | impl_->SaveFromRegistry(); |
338 | 0 | } |
339 | | |
340 | 0 | FlagSaver::~FlagSaver() { |
341 | 0 | if (!impl_) return; |
342 | | |
343 | 0 | impl_->RestoreToRegistry(); |
344 | 0 | delete impl_; |
345 | 0 | } |
346 | | |
347 | | // -------------------------------------------------------------------- |
348 | | |
349 | 0 | CommandLineFlag* FindCommandLineFlag(absl::string_view name) { |
350 | 0 | if (name.empty()) return nullptr; |
351 | 0 | flags_internal::FlagRegistry& registry = |
352 | 0 | flags_internal::FlagRegistry::GlobalRegistry(); |
353 | 0 | return registry.FindFlag(name); |
354 | 0 | } |
355 | | |
356 | | // -------------------------------------------------------------------- |
357 | | |
358 | 0 | absl::flat_hash_map<absl::string_view, absl::CommandLineFlag*> GetAllFlags() { |
359 | 0 | absl::flat_hash_map<absl::string_view, absl::CommandLineFlag*> res; |
360 | 0 | flags_internal::ForEachFlag([&](CommandLineFlag& flag) { |
361 | 0 | if (!flag.IsRetired()) res.insert({flag.Name(), &flag}); |
362 | 0 | }); |
363 | 0 | return res; |
364 | 0 | } |
365 | | |
366 | | ABSL_NAMESPACE_END |
367 | | } // namespace absl |