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