• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/flag.h"
17 
18 #include <assert.h>
19 #include <stddef.h>
20 #include <stdint.h>
21 #include <string.h>
22 
23 #include <array>
24 #include <atomic>
25 #include <memory>
26 #include <new>
27 #include <string>
28 #include <typeinfo>
29 
30 #include "absl/base/call_once.h"
31 #include "absl/base/casts.h"
32 #include "absl/base/config.h"
33 #include "absl/base/optimization.h"
34 #include "absl/flags/config.h"
35 #include "absl/flags/internal/commandlineflag.h"
36 #include "absl/flags/usage_config.h"
37 #include "absl/memory/memory.h"
38 #include "absl/strings/str_cat.h"
39 #include "absl/strings/string_view.h"
40 #include "absl/synchronization/mutex.h"
41 
42 namespace absl {
43 ABSL_NAMESPACE_BEGIN
44 namespace flags_internal {
45 
46 // The help message indicating that the commandline flag has been
47 // 'stripped'. It will not show up when doing "-help" and its
48 // variants. The flag is stripped if ABSL_FLAGS_STRIP_HELP is set to 1
49 // before including absl/flags/flag.h
50 const char kStrippedFlagHelp[] = "\001\002\003\004 (unknown) \004\003\002\001";
51 
52 namespace {
53 
54 // Currently we only validate flag values for user-defined flag types.
ShouldValidateFlagValue(FlagFastTypeId flag_type_id)55 bool ShouldValidateFlagValue(FlagFastTypeId flag_type_id) {
56 #define DONT_VALIDATE(T, _) \
57   if (flag_type_id == base_internal::FastTypeId<T>()) return false;
58   ABSL_FLAGS_INTERNAL_SUPPORTED_TYPES(DONT_VALIDATE)
59 #undef DONT_VALIDATE
60 
61   return true;
62 }
63 
64 // RAII helper used to temporarily unlock and relock `absl::Mutex`.
65 // This is used when we need to ensure that locks are released while
66 // invoking user supplied callbacks and then reacquired, since callbacks may
67 // need to acquire these locks themselves.
68 class MutexRelock {
69  public:
MutexRelock(absl::Mutex & mu)70   explicit MutexRelock(absl::Mutex& mu) : mu_(mu) { mu_.Unlock(); }
~MutexRelock()71   ~MutexRelock() { mu_.Lock(); }
72 
73   MutexRelock(const MutexRelock&) = delete;
74   MutexRelock& operator=(const MutexRelock&) = delete;
75 
76  private:
77   absl::Mutex& mu_;
78 };
79 
80 }  // namespace
81 
82 ///////////////////////////////////////////////////////////////////////////////
83 // Persistent state of the flag data.
84 
85 class FlagImpl;
86 
87 class FlagState : public flags_internal::FlagStateInterface {
88  public:
89   template <typename V>
FlagState(FlagImpl & flag_impl,const V & v,bool modified,bool on_command_line,int64_t counter)90   FlagState(FlagImpl& flag_impl, const V& v, bool modified,
91             bool on_command_line, int64_t counter)
92       : flag_impl_(flag_impl),
93         value_(v),
94         modified_(modified),
95         on_command_line_(on_command_line),
96         counter_(counter) {}
97 
~FlagState()98   ~FlagState() override {
99     if (flag_impl_.ValueStorageKind() != FlagValueStorageKind::kAlignedBuffer &&
100         flag_impl_.ValueStorageKind() != FlagValueStorageKind::kSequenceLocked)
101       return;
102     flags_internal::Delete(flag_impl_.op_, value_.heap_allocated);
103   }
104 
105  private:
106   friend class FlagImpl;
107 
108   // Restores the flag to the saved state.
Restore() const109   void Restore() const override {
110     if (!flag_impl_.RestoreState(*this)) return;
111 
112     ABSL_INTERNAL_LOG(INFO,
113                       absl::StrCat("Restore saved value of ", flag_impl_.Name(),
114                                    " to: ", flag_impl_.CurrentValue()));
115   }
116 
117   // Flag and saved flag data.
118   FlagImpl& flag_impl_;
119   union SavedValue {
SavedValue(void * v)120     explicit SavedValue(void* v) : heap_allocated(v) {}
SavedValue(int64_t v)121     explicit SavedValue(int64_t v) : one_word(v) {}
122 
123     void* heap_allocated;
124     int64_t one_word;
125   } value_;
126   bool modified_;
127   bool on_command_line_;
128   int64_t counter_;
129 };
130 
131 ///////////////////////////////////////////////////////////////////////////////
132 // Flag implementation, which does not depend on flag value type.
133 
DynValueDeleter(FlagOpFn op_arg)134 DynValueDeleter::DynValueDeleter(FlagOpFn op_arg) : op(op_arg) {}
135 
operator ()(void * ptr) const136 void DynValueDeleter::operator()(void* ptr) const {
137   if (op == nullptr) return;
138 
139   Delete(op, ptr);
140 }
141 
Init()142 void FlagImpl::Init() {
143   new (&data_guard_) absl::Mutex;
144 
145   auto def_kind = static_cast<FlagDefaultKind>(def_kind_);
146 
147   switch (ValueStorageKind()) {
148     case FlagValueStorageKind::kValueAndInitBit:
149     case FlagValueStorageKind::kOneWordAtomic: {
150       alignas(int64_t) std::array<char, sizeof(int64_t)> buf{};
151       if (def_kind == FlagDefaultKind::kGenFunc) {
152         (*default_value_.gen_func)(buf.data());
153       } else {
154         assert(def_kind != FlagDefaultKind::kDynamicValue);
155         std::memcpy(buf.data(), &default_value_, Sizeof(op_));
156       }
157       if (ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit) {
158         // We presume here the memory layout of FlagValueAndInitBit struct.
159         uint8_t initialized = 1;
160         std::memcpy(buf.data() + Sizeof(op_), &initialized,
161                     sizeof(initialized));
162       }
163       OneWordValue().store(absl::bit_cast<int64_t>(buf),
164                            std::memory_order_release);
165       break;
166     }
167     case FlagValueStorageKind::kSequenceLocked: {
168       // For this storage kind the default_value_ always points to gen_func
169       // during initialization.
170       assert(def_kind == FlagDefaultKind::kGenFunc);
171       (*default_value_.gen_func)(AtomicBufferValue());
172       break;
173     }
174     case FlagValueStorageKind::kAlignedBuffer:
175       // For this storage kind the default_value_ always points to gen_func
176       // during initialization.
177       assert(def_kind == FlagDefaultKind::kGenFunc);
178       (*default_value_.gen_func)(AlignedBufferValue());
179       break;
180   }
181   seq_lock_.MarkInitialized();
182 }
183 
DataGuard() const184 absl::Mutex* FlagImpl::DataGuard() const {
185   absl::call_once(const_cast<FlagImpl*>(this)->init_control_, &FlagImpl::Init,
186                   const_cast<FlagImpl*>(this));
187 
188   // data_guard_ is initialized inside Init.
189   return reinterpret_cast<absl::Mutex*>(&data_guard_);
190 }
191 
AssertValidType(FlagFastTypeId rhs_type_id,const std::type_info * (* gen_rtti)()) const192 void FlagImpl::AssertValidType(FlagFastTypeId rhs_type_id,
193                                const std::type_info* (*gen_rtti)()) const {
194   FlagFastTypeId lhs_type_id = flags_internal::FastTypeId(op_);
195 
196   // `rhs_type_id` is the fast type id corresponding to the declaration
197   // visibile at the call site. `lhs_type_id` is the fast type id
198   // corresponding to the type specified in flag definition. They must match
199   //  for this operation to be well-defined.
200   if (ABSL_PREDICT_TRUE(lhs_type_id == rhs_type_id)) return;
201 
202   const std::type_info* lhs_runtime_type_id =
203       flags_internal::RuntimeTypeId(op_);
204   const std::type_info* rhs_runtime_type_id = (*gen_rtti)();
205 
206   if (lhs_runtime_type_id == rhs_runtime_type_id) return;
207 
208 #if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
209   if (*lhs_runtime_type_id == *rhs_runtime_type_id) return;
210 #endif
211 
212   ABSL_INTERNAL_LOG(
213       FATAL, absl::StrCat("Flag '", Name(),
214                           "' is defined as one type and declared as another"));
215 }
216 
MakeInitValue() const217 std::unique_ptr<void, DynValueDeleter> FlagImpl::MakeInitValue() const {
218   void* res = nullptr;
219   switch (DefaultKind()) {
220     case FlagDefaultKind::kDynamicValue:
221       res = flags_internal::Clone(op_, default_value_.dynamic_value);
222       break;
223     case FlagDefaultKind::kGenFunc:
224       res = flags_internal::Alloc(op_);
225       (*default_value_.gen_func)(res);
226       break;
227     default:
228       res = flags_internal::Clone(op_, &default_value_);
229       break;
230   }
231   return {res, DynValueDeleter{op_}};
232 }
233 
StoreValue(const void * src)234 void FlagImpl::StoreValue(const void* src) {
235   switch (ValueStorageKind()) {
236     case FlagValueStorageKind::kValueAndInitBit:
237     case FlagValueStorageKind::kOneWordAtomic: {
238       // Load the current value to avoid setting 'init' bit manualy.
239       int64_t one_word_val = OneWordValue().load(std::memory_order_acquire);
240       std::memcpy(&one_word_val, src, Sizeof(op_));
241       OneWordValue().store(one_word_val, std::memory_order_release);
242       seq_lock_.IncrementModificationCount();
243       break;
244     }
245     case FlagValueStorageKind::kSequenceLocked: {
246       seq_lock_.Write(AtomicBufferValue(), src, Sizeof(op_));
247       break;
248     }
249     case FlagValueStorageKind::kAlignedBuffer:
250       Copy(op_, src, AlignedBufferValue());
251       seq_lock_.IncrementModificationCount();
252       break;
253   }
254   modified_ = true;
255   InvokeCallback();
256 }
257 
Name() const258 absl::string_view FlagImpl::Name() const { return name_; }
259 
Filename() const260 std::string FlagImpl::Filename() const {
261   return flags_internal::GetUsageConfig().normalize_filename(filename_);
262 }
263 
Help() const264 std::string FlagImpl::Help() const {
265   return HelpSourceKind() == FlagHelpKind::kLiteral ? help_.literal
266                                                     : help_.gen_func();
267 }
268 
TypeId() const269 FlagFastTypeId FlagImpl::TypeId() const {
270   return flags_internal::FastTypeId(op_);
271 }
272 
ModificationCount() const273 int64_t FlagImpl::ModificationCount() const {
274   return seq_lock_.ModificationCount();
275 }
276 
IsSpecifiedOnCommandLine() const277 bool FlagImpl::IsSpecifiedOnCommandLine() const {
278   absl::MutexLock l(DataGuard());
279   return on_command_line_;
280 }
281 
DefaultValue() const282 std::string FlagImpl::DefaultValue() const {
283   absl::MutexLock l(DataGuard());
284 
285   auto obj = MakeInitValue();
286   return flags_internal::Unparse(op_, obj.get());
287 }
288 
CurrentValue() const289 std::string FlagImpl::CurrentValue() const {
290   auto* guard = DataGuard();  // Make sure flag initialized
291   switch (ValueStorageKind()) {
292     case FlagValueStorageKind::kValueAndInitBit:
293     case FlagValueStorageKind::kOneWordAtomic: {
294       const auto one_word_val =
295           absl::bit_cast<std::array<char, sizeof(int64_t)>>(
296               OneWordValue().load(std::memory_order_acquire));
297       return flags_internal::Unparse(op_, one_word_val.data());
298     }
299     case FlagValueStorageKind::kSequenceLocked: {
300       std::unique_ptr<void, DynValueDeleter> cloned(flags_internal::Alloc(op_),
301                                                     DynValueDeleter{op_});
302       ReadSequenceLockedData(cloned.get());
303       return flags_internal::Unparse(op_, cloned.get());
304     }
305     case FlagValueStorageKind::kAlignedBuffer: {
306       absl::MutexLock l(guard);
307       return flags_internal::Unparse(op_, AlignedBufferValue());
308     }
309   }
310 
311   return "";
312 }
313 
SetCallback(const FlagCallbackFunc mutation_callback)314 void FlagImpl::SetCallback(const FlagCallbackFunc mutation_callback) {
315   absl::MutexLock l(DataGuard());
316 
317   if (callback_ == nullptr) {
318     callback_ = new FlagCallback;
319   }
320   callback_->func = mutation_callback;
321 
322   InvokeCallback();
323 }
324 
InvokeCallback() const325 void FlagImpl::InvokeCallback() const {
326   if (!callback_) return;
327 
328   // Make a copy of the C-style function pointer that we are about to invoke
329   // before we release the lock guarding it.
330   FlagCallbackFunc cb = callback_->func;
331 
332   // If the flag has a mutation callback this function invokes it. While the
333   // callback is being invoked the primary flag's mutex is unlocked and it is
334   // re-locked back after call to callback is completed. Callback invocation is
335   // guarded by flag's secondary mutex instead which prevents concurrent
336   // callback invocation. Note that it is possible for other thread to grab the
337   // primary lock and update flag's value at any time during the callback
338   // invocation. This is by design. Callback can get a value of the flag if
339   // necessary, but it might be different from the value initiated the callback
340   // and it also can be different by the time the callback invocation is
341   // completed. Requires that *primary_lock be held in exclusive mode; it may be
342   // released and reacquired by the implementation.
343   MutexRelock relock(*DataGuard());
344   absl::MutexLock lock(&callback_->guard);
345   cb();
346 }
347 
SaveState()348 std::unique_ptr<FlagStateInterface> FlagImpl::SaveState() {
349   absl::MutexLock l(DataGuard());
350 
351   bool modified = modified_;
352   bool on_command_line = on_command_line_;
353   switch (ValueStorageKind()) {
354     case FlagValueStorageKind::kValueAndInitBit:
355     case FlagValueStorageKind::kOneWordAtomic: {
356       return absl::make_unique<FlagState>(
357           *this, OneWordValue().load(std::memory_order_acquire), modified,
358           on_command_line, ModificationCount());
359     }
360     case FlagValueStorageKind::kSequenceLocked: {
361       void* cloned = flags_internal::Alloc(op_);
362       // Read is guaranteed to be successful because we hold the lock.
363       bool success =
364           seq_lock_.TryRead(cloned, AtomicBufferValue(), Sizeof(op_));
365       assert(success);
366       static_cast<void>(success);
367       return absl::make_unique<FlagState>(*this, cloned, modified,
368                                           on_command_line, ModificationCount());
369     }
370     case FlagValueStorageKind::kAlignedBuffer: {
371       return absl::make_unique<FlagState>(
372           *this, flags_internal::Clone(op_, AlignedBufferValue()), modified,
373           on_command_line, ModificationCount());
374     }
375   }
376   return nullptr;
377 }
378 
RestoreState(const FlagState & flag_state)379 bool FlagImpl::RestoreState(const FlagState& flag_state) {
380   absl::MutexLock l(DataGuard());
381   if (flag_state.counter_ == ModificationCount()) {
382     return false;
383   }
384 
385   switch (ValueStorageKind()) {
386     case FlagValueStorageKind::kValueAndInitBit:
387     case FlagValueStorageKind::kOneWordAtomic:
388       StoreValue(&flag_state.value_.one_word);
389       break;
390     case FlagValueStorageKind::kSequenceLocked:
391     case FlagValueStorageKind::kAlignedBuffer:
392       StoreValue(flag_state.value_.heap_allocated);
393       break;
394   }
395 
396   modified_ = flag_state.modified_;
397   on_command_line_ = flag_state.on_command_line_;
398 
399   return true;
400 }
401 
402 template <typename StorageT>
OffsetValue() const403 StorageT* FlagImpl::OffsetValue() const {
404   char* p = reinterpret_cast<char*>(const_cast<FlagImpl*>(this));
405   // The offset is deduced via Flag value type specific op_.
406   size_t offset = flags_internal::ValueOffset(op_);
407 
408   return reinterpret_cast<StorageT*>(p + offset);
409 }
410 
AlignedBufferValue() const411 void* FlagImpl::AlignedBufferValue() const {
412   assert(ValueStorageKind() == FlagValueStorageKind::kAlignedBuffer);
413   return OffsetValue<void>();
414 }
415 
AtomicBufferValue() const416 std::atomic<uint64_t>* FlagImpl::AtomicBufferValue() const {
417   assert(ValueStorageKind() == FlagValueStorageKind::kSequenceLocked);
418   return OffsetValue<std::atomic<uint64_t>>();
419 }
420 
OneWordValue() const421 std::atomic<int64_t>& FlagImpl::OneWordValue() const {
422   assert(ValueStorageKind() == FlagValueStorageKind::kOneWordAtomic ||
423          ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit);
424   return OffsetValue<FlagOneWordValue>()->value;
425 }
426 
427 // Attempts to parse supplied `value` string using parsing routine in the `flag`
428 // argument. If parsing successful, this function replaces the dst with newly
429 // parsed value. In case if any error is encountered in either step, the error
430 // message is stored in 'err'
TryParse(absl::string_view value,std::string & err) const431 std::unique_ptr<void, DynValueDeleter> FlagImpl::TryParse(
432     absl::string_view value, std::string& err) const {
433   std::unique_ptr<void, DynValueDeleter> tentative_value = MakeInitValue();
434 
435   std::string parse_err;
436   if (!flags_internal::Parse(op_, value, tentative_value.get(), &parse_err)) {
437     absl::string_view err_sep = parse_err.empty() ? "" : "; ";
438     err = absl::StrCat("Illegal value '", value, "' specified for flag '",
439                        Name(), "'", err_sep, parse_err);
440     return nullptr;
441   }
442 
443   return tentative_value;
444 }
445 
Read(void * dst) const446 void FlagImpl::Read(void* dst) const {
447   auto* guard = DataGuard();  // Make sure flag initialized
448   switch (ValueStorageKind()) {
449     case FlagValueStorageKind::kValueAndInitBit:
450     case FlagValueStorageKind::kOneWordAtomic: {
451       const int64_t one_word_val =
452           OneWordValue().load(std::memory_order_acquire);
453       std::memcpy(dst, &one_word_val, Sizeof(op_));
454       break;
455     }
456     case FlagValueStorageKind::kSequenceLocked: {
457       ReadSequenceLockedData(dst);
458       break;
459     }
460     case FlagValueStorageKind::kAlignedBuffer: {
461       absl::MutexLock l(guard);
462       flags_internal::CopyConstruct(op_, AlignedBufferValue(), dst);
463       break;
464     }
465   }
466 }
467 
ReadOneWord() const468 int64_t FlagImpl::ReadOneWord() const {
469   assert(ValueStorageKind() == FlagValueStorageKind::kOneWordAtomic ||
470          ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit);
471   auto* guard = DataGuard();  // Make sure flag initialized
472   (void)guard;
473   return OneWordValue().load(std::memory_order_acquire);
474 }
475 
ReadOneBool() const476 bool FlagImpl::ReadOneBool() const {
477   assert(ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit);
478   auto* guard = DataGuard();  // Make sure flag initialized
479   (void)guard;
480   return absl::bit_cast<FlagValueAndInitBit<bool>>(
481              OneWordValue().load(std::memory_order_acquire))
482       .value;
483 }
484 
ReadSequenceLockedData(void * dst) const485 void FlagImpl::ReadSequenceLockedData(void* dst) const {
486   int size = Sizeof(op_);
487   // Attempt to read using the sequence lock.
488   if (ABSL_PREDICT_TRUE(seq_lock_.TryRead(dst, AtomicBufferValue(), size))) {
489     return;
490   }
491   // We failed due to contention. Acquire the lock to prevent contention
492   // and try again.
493   absl::ReaderMutexLock l(DataGuard());
494   bool success = seq_lock_.TryRead(dst, AtomicBufferValue(), size);
495   assert(success);
496   static_cast<void>(success);
497 }
498 
Write(const void * src)499 void FlagImpl::Write(const void* src) {
500   absl::MutexLock l(DataGuard());
501 
502   if (ShouldValidateFlagValue(flags_internal::FastTypeId(op_))) {
503     std::unique_ptr<void, DynValueDeleter> obj{flags_internal::Clone(op_, src),
504                                                DynValueDeleter{op_}};
505     std::string ignored_error;
506     std::string src_as_str = flags_internal::Unparse(op_, src);
507     if (!flags_internal::Parse(op_, src_as_str, obj.get(), &ignored_error)) {
508       ABSL_INTERNAL_LOG(ERROR, absl::StrCat("Attempt to set flag '", Name(),
509                                             "' to invalid value ", src_as_str));
510     }
511   }
512 
513   StoreValue(src);
514 }
515 
516 // Sets the value of the flag based on specified string `value`. If the flag
517 // was successfully set to new value, it returns true. Otherwise, sets `err`
518 // to indicate the error, leaves the flag unchanged, and returns false. There
519 // are three ways to set the flag's value:
520 //  * Update the current flag value
521 //  * Update the flag's default value
522 //  * Update the current flag value if it was never set before
523 // The mode is selected based on 'set_mode' parameter.
ParseFrom(absl::string_view value,FlagSettingMode set_mode,ValueSource source,std::string & err)524 bool FlagImpl::ParseFrom(absl::string_view value, FlagSettingMode set_mode,
525                          ValueSource source, std::string& err) {
526   absl::MutexLock l(DataGuard());
527 
528   switch (set_mode) {
529     case SET_FLAGS_VALUE: {
530       // set or modify the flag's value
531       auto tentative_value = TryParse(value, err);
532       if (!tentative_value) return false;
533 
534       StoreValue(tentative_value.get());
535 
536       if (source == kCommandLine) {
537         on_command_line_ = true;
538       }
539       break;
540     }
541     case SET_FLAG_IF_DEFAULT: {
542       // set the flag's value, but only if it hasn't been set by someone else
543       if (modified_) {
544         // TODO(rogeeff): review and fix this semantic. Currently we do not fail
545         // in this case if flag is modified. This is misleading since the flag's
546         // value is not updated even though we return true.
547         // *err = absl::StrCat(Name(), " is already set to ",
548         //                     CurrentValue(), "\n");
549         // return false;
550         return true;
551       }
552       auto tentative_value = TryParse(value, err);
553       if (!tentative_value) return false;
554 
555       StoreValue(tentative_value.get());
556       break;
557     }
558     case SET_FLAGS_DEFAULT: {
559       auto tentative_value = TryParse(value, err);
560       if (!tentative_value) return false;
561 
562       if (DefaultKind() == FlagDefaultKind::kDynamicValue) {
563         void* old_value = default_value_.dynamic_value;
564         default_value_.dynamic_value = tentative_value.release();
565         tentative_value.reset(old_value);
566       } else {
567         default_value_.dynamic_value = tentative_value.release();
568         def_kind_ = static_cast<uint8_t>(FlagDefaultKind::kDynamicValue);
569       }
570 
571       if (!modified_) {
572         // Need to set both default value *and* current, in this case.
573         StoreValue(default_value_.dynamic_value);
574         modified_ = false;
575       }
576       break;
577     }
578   }
579 
580   return true;
581 }
582 
CheckDefaultValueParsingRoundtrip() const583 void FlagImpl::CheckDefaultValueParsingRoundtrip() const {
584   std::string v = DefaultValue();
585 
586   absl::MutexLock lock(DataGuard());
587 
588   auto dst = MakeInitValue();
589   std::string error;
590   if (!flags_internal::Parse(op_, v, dst.get(), &error)) {
591     ABSL_INTERNAL_LOG(
592         FATAL,
593         absl::StrCat("Flag ", Name(), " (from ", Filename(),
594                      "): string form of default value '", v,
595                      "' could not be parsed; error=", error));
596   }
597 
598   // We do not compare dst to def since parsing/unparsing may make
599   // small changes, e.g., precision loss for floating point types.
600 }
601 
ValidateInputValue(absl::string_view value) const602 bool FlagImpl::ValidateInputValue(absl::string_view value) const {
603   absl::MutexLock l(DataGuard());
604 
605   auto obj = MakeInitValue();
606   std::string ignored_error;
607   return flags_internal::Parse(op_, value, obj.get(), &ignored_error);
608 }
609 
610 }  // namespace flags_internal
611 ABSL_NAMESPACE_END
612 }  // namespace absl
613