Coverage Report

Created: 2025-10-28 06:50

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