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