1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/feature_list.h"
12 #include "base/debug/alias.h"
13 #include "base/logging.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/metrics/field_trial.h"
16 #include "base/pickle.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "build/build_config.h"
25 // Pointer to the FeatureList instance singleton that was set via
26 // FeatureList::SetInstance(). Does not use base/memory/singleton.h in order to
27 // have more control over initialization timing. Leaky.
28 FeatureList* g_feature_list_instance = nullptr;
30 // Tracks whether the FeatureList instance was initialized via an accessor, and
31 // which Feature that accessor was for, if so.
32 const Feature* g_initialized_from_accessor = nullptr;
35 const char* g_reason_overrides_disallowed = nullptr;
37 void DCheckOverridesAllowed() {
38 const bool feature_overrides_allowed = !g_reason_overrides_disallowed;
39 DCHECK(feature_overrides_allowed) << g_reason_overrides_disallowed;
42 void DCheckOverridesAllowed() {}
45 // An allocator entry for a feature in shared memory. The FeatureEntry is
46 // followed by a base::Pickle object that contains the feature and trial name.
48 // SHA1(FeatureEntry): Increment this if structure changes!
49 static constexpr uint32_t kPersistentTypeId = 0x06567CA6 + 1;
51 // Expected size for 32/64-bit check.
52 static constexpr size_t kExpectedInstanceSize = 8;
54 // Specifies whether a feature override enables or disables the feature. Same
55 // values as the OverrideState enum in feature_list.h
56 uint32_t override_state;
58 // Size of the pickled structure, NOT the total size of this entry.
61 // Reads the feature and trial name from the pickle. Calling this is only
62 // valid on an initialized entry that's in shared memory.
63 bool GetFeatureAndTrialName(StringPiece* feature_name,
64 StringPiece* trial_name) const {
66 reinterpret_cast<const char*>(this) + sizeof(FeatureEntry);
68 Pickle pickle(src, pickle_size);
69 PickleIterator pickle_iter(pickle);
71 if (!pickle_iter.ReadStringPiece(feature_name))
74 // Return true because we are not guaranteed to have a trial name anyways.
75 auto sink = pickle_iter.ReadStringPiece(trial_name);
76 ALLOW_UNUSED_LOCAL(sink);
81 // Some characters are not allowed to appear in feature names or the associated
82 // field trial names, as they are used as special characters for command-line
83 // serialization. This function checks that the strings are ASCII (since they
84 // are used in command-line API functions that require ASCII) and whether there
85 // are any reserved characters present, returning true if the string is valid.
86 // Only called in DCHECKs.
87 bool IsValidFeatureOrFieldTrialName(const std::string& name) {
88 return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos;
93 #if defined(DCHECK_IS_CONFIGURABLE)
94 const Feature kDCheckIsFatalFeature{"DcheckIsFatal",
95 FEATURE_DISABLED_BY_DEFAULT};
96 #endif // defined(DCHECK_IS_CONFIGURABLE)
98 FeatureList::FeatureList() = default;
100 FeatureList::~FeatureList() = default;
102 FeatureList::ScopedDisallowOverrides::ScopedDisallowOverrides(
105 : previous_reason_(g_reason_overrides_disallowed) {
106 g_reason_overrides_disallowed = reason;
113 FeatureList::ScopedDisallowOverrides::~ScopedDisallowOverrides() {
115 g_reason_overrides_disallowed = previous_reason_;
119 void FeatureList::InitializeFromCommandLine(
120 const std::string& enable_features,
121 const std::string& disable_features) {
122 DCHECK(!initialized_);
124 // Process disabled features first, so that disabled ones take precedence over
125 // enabled ones (since RegisterOverride() uses insert()).
126 RegisterOverridesFromCommandLine(disable_features, OVERRIDE_DISABLE_FEATURE);
127 RegisterOverridesFromCommandLine(enable_features, OVERRIDE_ENABLE_FEATURE);
129 initialized_from_command_line_ = true;
132 void FeatureList::InitializeFromSharedMemory(
133 PersistentMemoryAllocator* allocator) {
134 DCHECK(!initialized_);
136 PersistentMemoryAllocator::Iterator iter(allocator);
137 const FeatureEntry* entry;
138 while ((entry = iter.GetNextOfObject<FeatureEntry>()) != nullptr) {
139 OverrideState override_state =
140 static_cast<OverrideState>(entry->override_state);
142 StringPiece feature_name;
143 StringPiece trial_name;
144 if (!entry->GetFeatureAndTrialName(&feature_name, &trial_name))
147 FieldTrial* trial = FieldTrialList::Find(trial_name.as_string());
148 RegisterOverride(feature_name, override_state, trial);
152 bool FeatureList::IsFeatureOverriddenFromCommandLine(
153 const std::string& feature_name,
154 OverrideState state) const {
155 auto it = overrides_.find(feature_name);
156 return it != overrides_.end() && it->second.overridden_state == state &&
157 !it->second.overridden_by_field_trial;
160 void FeatureList::AssociateReportingFieldTrial(
161 const std::string& feature_name,
162 OverrideState for_overridden_state,
163 FieldTrial* field_trial) {
165 IsFeatureOverriddenFromCommandLine(feature_name, for_overridden_state));
167 // Only one associated field trial is supported per feature. This is generally
168 // enforced server-side.
169 OverrideEntry* entry = &overrides_.find(feature_name)->second;
170 if (entry->field_trial) {
171 NOTREACHED() << "Feature " << feature_name
172 << " already has trial: " << entry->field_trial->trial_name()
173 << ", associating trial: " << field_trial->trial_name();
177 entry->field_trial = field_trial;
180 void FeatureList::RegisterFieldTrialOverride(const std::string& feature_name,
181 OverrideState override_state,
182 FieldTrial* field_trial) {
184 DCHECK(!Contains(overrides_, feature_name) ||
185 !overrides_.find(feature_name)->second.field_trial)
186 << "Feature " << feature_name
187 << " has conflicting field trial overrides: "
188 << overrides_.find(feature_name)->second.field_trial->trial_name()
189 << " / " << field_trial->trial_name()
190 << ". Please make sure that the trial (study) name is consistent across:"
191 << " (1)The server config, (2)The fieldtrial_testing_config, and"
192 << " (3) The about_flags.cc";
194 RegisterOverride(feature_name, override_state, field_trial);
197 void FeatureList::RegisterExtraFeatureOverrides(
198 const std::vector<FeatureOverrideInfo>& extra_overrides) {
199 for (const FeatureOverrideInfo& override_info : extra_overrides) {
200 RegisterOverride(override_info.first.get().name, override_info.second,
201 /* field_trial = */ nullptr);
205 void FeatureList::AddFeaturesToAllocator(PersistentMemoryAllocator* allocator) {
206 DCHECK(initialized_);
208 for (const auto& override : overrides_) {
210 pickle.WriteString(override.first);
211 if (override.second.field_trial)
212 pickle.WriteString(override.second.field_trial->trial_name());
214 size_t total_size = sizeof(FeatureEntry) + pickle.size();
215 FeatureEntry* entry = allocator->New<FeatureEntry>(total_size);
219 entry->override_state = override.second.overridden_state;
220 entry->pickle_size = pickle.size();
222 char* dst = reinterpret_cast<char*>(entry) + sizeof(FeatureEntry);
223 memcpy(dst, pickle.data(), pickle.size());
225 allocator->MakeIterable(entry);
229 void FeatureList::GetFeatureOverrides(std::string* enable_overrides,
230 std::string* disable_overrides) {
231 GetFeatureOverridesImpl(enable_overrides, disable_overrides, false);
234 void FeatureList::GetCommandLineFeatureOverrides(
235 std::string* enable_overrides,
236 std::string* disable_overrides) {
237 GetFeatureOverridesImpl(enable_overrides, disable_overrides, true);
241 bool FeatureList::IsEnabled(const Feature& feature) {
242 if (!g_feature_list_instance) {
243 g_initialized_from_accessor = &feature;
244 return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
246 return g_feature_list_instance->IsFeatureEnabled(feature);
250 FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
251 if (!g_feature_list_instance) {
252 g_initialized_from_accessor = &feature;
255 return g_feature_list_instance->GetAssociatedFieldTrial(feature);
259 std::vector<StringPiece> FeatureList::SplitFeatureListString(
261 return SplitStringPiece(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
265 bool FeatureList::InitializeInstance(const std::string& enable_features,
266 const std::string& disable_features) {
267 return InitializeInstance(enable_features, disable_features,
268 std::vector<FeatureOverrideInfo>());
272 bool FeatureList::InitializeInstance(
273 const std::string& enable_features,
274 const std::string& disable_features,
275 const std::vector<FeatureOverrideInfo>& extra_overrides) {
276 // We want to initialize a new instance here to support command-line features
277 // in testing better. For example, we initialize a dummy instance in
278 // base/test/test_suite.cc, and override it in content/browser/
279 // browser_main_loop.cc.
280 // On the other hand, we want to avoid re-initialization from command line.
281 // For example, we initialize an instance in chrome/browser/
282 // chrome_browser_main.cc and do not override it in content/browser/
283 // browser_main_loop.cc.
284 // If the singleton was previously initialized from within an accessor, we
285 // want to prevent callers from reinitializing the singleton and masking the
286 // accessor call(s) which likely returned incorrect information.
287 if (g_initialized_from_accessor) {
288 DEBUG_ALIAS_FOR_CSTR(accessor_name, g_initialized_from_accessor->name, 128);
289 CHECK(!g_initialized_from_accessor);
291 bool instance_existed_before = false;
292 if (g_feature_list_instance) {
293 if (g_feature_list_instance->initialized_from_command_line_)
296 delete g_feature_list_instance;
297 g_feature_list_instance = nullptr;
298 instance_existed_before = true;
301 std::unique_ptr<FeatureList> feature_list(new FeatureList);
302 feature_list->InitializeFromCommandLine(enable_features, disable_features);
303 feature_list->RegisterExtraFeatureOverrides(extra_overrides);
304 FeatureList::SetInstance(std::move(feature_list));
305 return !instance_existed_before;
309 FeatureList* FeatureList::GetInstance() {
310 return g_feature_list_instance;
314 void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) {
315 DCHECK(!g_feature_list_instance);
316 instance->FinalizeInitialization();
318 // Note: Intentional leak of global singleton.
319 g_feature_list_instance = instance.release();
321 #if defined(DCHECK_IS_CONFIGURABLE)
322 // Update the behaviour of LOG_DCHECK to match the Feature configuration.
323 // DCHECK is also forced to be FATAL if we are running a death-test.
324 // TODO(crbug.com/1057995#c11): --gtest_internal_run_death_test doesn't
325 // currently run through this codepath, mitigated in
326 // base::TestSuite::Initialize() for now.
327 // TODO(asvitkine): If we find other use-cases that need integrating here
328 // then define a proper API/hook for the purpose.
329 if (FeatureList::IsEnabled(kDCheckIsFatalFeature) ||
330 CommandLine::ForCurrentProcess()->HasSwitch(
331 "gtest_internal_run_death_test")) {
332 logging::LOG_DCHECK = logging::LOG_FATAL;
334 logging::LOG_DCHECK = logging::LOG_INFO;
336 #endif // defined(DCHECK_IS_CONFIGURABLE)
340 std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() {
341 FeatureList* old_instance = g_feature_list_instance;
342 g_feature_list_instance = nullptr;
343 g_initialized_from_accessor = nullptr;
344 return WrapUnique(old_instance);
348 void FeatureList::RestoreInstanceForTesting(
349 std::unique_ptr<FeatureList> instance) {
350 DCHECK(!g_feature_list_instance);
351 // Note: Intentional leak of global singleton.
352 g_feature_list_instance = instance.release();
355 void FeatureList::FinalizeInitialization() {
356 DCHECK(!initialized_);
357 // Store the field trial list pointer for DCHECKing.
358 field_trial_list_ = FieldTrialList::GetInstance();
362 bool FeatureList::IsFeatureEnabled(const Feature& feature) {
363 DCHECK(initialized_);
364 DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
365 DCHECK(CheckFeatureIdentity(feature)) << feature.name;
367 auto it = overrides_.find(feature.name);
368 if (it != overrides_.end()) {
369 const OverrideEntry& entry = it->second;
371 // Activate the corresponding field trial, if necessary.
372 if (entry.field_trial)
373 entry.field_trial->group();
375 // TODO(asvitkine) Expand this section as more support is added.
377 // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below.
378 if (entry.overridden_state != OVERRIDE_USE_DEFAULT)
379 return entry.overridden_state == OVERRIDE_ENABLE_FEATURE;
381 // Otherwise, return the default state.
382 return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
385 FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) {
386 DCHECK(initialized_);
387 DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
388 DCHECK(CheckFeatureIdentity(feature)) << feature.name;
390 auto it = overrides_.find(feature.name);
391 if (it != overrides_.end()) {
392 const OverrideEntry& entry = it->second;
393 return entry.field_trial;
399 void FeatureList::RegisterOverridesFromCommandLine(
400 const std::string& feature_list,
401 OverrideState overridden_state) {
402 for (const auto& value : SplitFeatureListString(feature_list)) {
403 StringPiece feature_name = value;
404 FieldTrial* trial = nullptr;
406 // The entry may be of the form FeatureName<FieldTrialName - in which case,
407 // this splits off the field trial name and associates it with the override.
408 std::string::size_type pos = feature_name.find('<');
409 if (pos != std::string::npos) {
410 feature_name = StringPiece(value.data(), pos);
411 trial = FieldTrialList::Find(value.substr(pos + 1).as_string());
412 #if !defined(OS_NACL)
413 // If the below DCHECK fires, it means a non-existent trial name was
414 // specified via the "Feature<Trial" command-line syntax.
415 DCHECK(trial) << "trial=" << value.substr(pos + 1);
416 #endif // !defined(OS_NACL)
419 RegisterOverride(feature_name, overridden_state, trial);
423 void FeatureList::RegisterOverride(StringPiece feature_name,
424 OverrideState overridden_state,
425 FieldTrial* field_trial) {
426 DCHECK(!initialized_);
427 DCheckOverridesAllowed();
429 DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name()))
430 << field_trial->trial_name();
432 if (feature_name.starts_with("*")) {
433 feature_name = feature_name.substr(1);
434 overridden_state = OVERRIDE_USE_DEFAULT;
437 // Note: The semantics of insert() is that it does not overwrite the entry if
438 // one already exists for the key. Thus, only the first override for a given
439 // feature name takes effect.
440 overrides_.insert(std::make_pair(
441 feature_name.as_string(), OverrideEntry(overridden_state, field_trial)));
444 void FeatureList::GetFeatureOverridesImpl(std::string* enable_overrides,
445 std::string* disable_overrides,
446 bool command_line_only) {
447 DCHECK(initialized_);
449 // Check that the FieldTrialList this is associated with, if any, is the
450 // active one. If not, it likely indicates that this FeatureList has override
451 // entries from a freed FieldTrial, which may be caused by an incorrect test
453 if (field_trial_list_)
454 DCHECK_EQ(field_trial_list_, FieldTrialList::GetInstance());
456 enable_overrides->clear();
457 disable_overrides->clear();
459 // Note: Since |overrides_| is a std::map, iteration will be in alphabetical
460 // order. This is not guaranteed to users of this function, but is useful for
461 // tests to assume the order.
462 for (const auto& entry : overrides_) {
463 if (command_line_only &&
464 (entry.second.field_trial != nullptr ||
465 entry.second.overridden_state == OVERRIDE_USE_DEFAULT)) {
469 std::string* target_list = nullptr;
470 switch (entry.second.overridden_state) {
471 case OVERRIDE_USE_DEFAULT:
472 case OVERRIDE_ENABLE_FEATURE:
473 target_list = enable_overrides;
475 case OVERRIDE_DISABLE_FEATURE:
476 target_list = disable_overrides;
480 if (!target_list->empty())
481 target_list->push_back(',');
482 if (entry.second.overridden_state == OVERRIDE_USE_DEFAULT)
483 target_list->push_back('*');
484 target_list->append(entry.first);
485 if (entry.second.field_trial) {
486 target_list->push_back('<');
487 target_list->append(entry.second.field_trial->trial_name());
492 bool FeatureList::CheckFeatureIdentity(const Feature& feature) {
493 AutoLock auto_lock(feature_identity_tracker_lock_);
495 auto it = feature_identity_tracker_.find(feature.name);
496 if (it == feature_identity_tracker_.end()) {
497 // If it's not tracked yet, register it.
498 feature_identity_tracker_[feature.name] = &feature;
501 // Compare address of |feature| to the existing tracked entry.
502 return it->second == &feature;
505 FeatureList::OverrideEntry::OverrideEntry(OverrideState overridden_state,
506 FieldTrial* field_trial)
507 : overridden_state(overridden_state),
508 field_trial(field_trial),
509 overridden_by_field_trial(field_trial != nullptr) {}