| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/feature_list.h" |
| |
| #include <string> |
| #include <tuple> |
| |
| #include <stddef.h> |
| |
| #include "base/base_switches.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_param_associator.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/persistent_memory_allocator.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/pickle.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| |
| namespace base { |
| |
| namespace { |
| |
| // Pointer to the FeatureList instance singleton that was set via |
| // FeatureList::SetInstance(). Does not use base/memory/singleton.h in order to |
| // have more control over initialization timing. Leaky. |
| FeatureList* g_feature_list_instance = nullptr; |
| |
| // Tracks access to Feature state before FeatureList registration. |
| class EarlyFeatureAccessTracker { |
| public: |
| static EarlyFeatureAccessTracker* GetInstance() { |
| static NoDestructor<EarlyFeatureAccessTracker> instance; |
| return instance.get(); |
| } |
| |
| // Invoked when `feature` is accessed before FeatureList registration. |
| void AccessedFeature(const Feature& feature) { |
| AutoLock lock(lock_); |
| if (fail_instantly_) |
| Fail(&feature); |
| else if (!feature_) |
| feature_ = &feature; |
| } |
| |
| // Asserts that no feature was accessed before FeatureList registration. |
| void AssertNoAccess() { |
| AutoLock lock(lock_); |
| if (feature_) |
| Fail(feature_); |
| } |
| |
| // Makes calls to AccessedFeature() fail instantly. |
| void FailOnFeatureAccessWithoutFeatureList() { |
| AutoLock lock(lock_); |
| if (feature_) |
| Fail(feature_); |
| fail_instantly_ = true; |
| } |
| |
| // Resets the state of this tracker. |
| void Reset() { |
| AutoLock lock(lock_); |
| feature_ = nullptr; |
| fail_instantly_ = false; |
| } |
| |
| private: |
| void Fail(const Feature* feature) { |
| // TODO(crbug.com/1358639): Enable this check on all platforms. |
| #if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS) |
| #if !BUILDFLAG(IS_NACL) |
| // Create a crash key with the name of the feature accessed too early, to |
| // facilitate crash triage. |
| SCOPED_CRASH_KEY_STRING256("FeatureList", "feature-accessed-too-early", |
| feature->name); |
| #endif // !BUILDFLAG(IS_NACL) |
| // Fail if DCHECKs are enabled. |
| DCHECK(!feature) << "Accessed feature " << feature->name |
| << " before FeatureList registration."; |
| // TODO(crbug.com/1383852): When we believe that all early accesses have |
| // been fixed, remove this base::debug::DumpWithoutCrashing() and change the |
| // above DCHECK to a CHECK. |
| base::debug::DumpWithoutCrashing(); |
| #endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && |
| // !BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| friend class NoDestructor<EarlyFeatureAccessTracker>; |
| |
| EarlyFeatureAccessTracker() = default; |
| ~EarlyFeatureAccessTracker() = default; |
| |
| Lock lock_; |
| |
| // First feature to be accessed before FeatureList registration. |
| raw_ptr<const Feature> feature_ GUARDED_BY(lock_) = nullptr; |
| |
| // Whether AccessedFeature() should fail instantly. |
| bool fail_instantly_ GUARDED_BY(lock_) = false; |
| }; |
| |
| #if DCHECK_IS_ON() |
| const char* g_reason_overrides_disallowed = nullptr; |
| |
| void DCheckOverridesAllowed() { |
| const bool feature_overrides_allowed = !g_reason_overrides_disallowed; |
| DCHECK(feature_overrides_allowed) << g_reason_overrides_disallowed; |
| } |
| #else |
| void DCheckOverridesAllowed() {} |
| #endif |
| |
| // An allocator entry for a feature in shared memory. The FeatureEntry is |
| // followed by a base::Pickle object that contains the feature and trial name. |
| struct FeatureEntry { |
| // SHA1(FeatureEntry): Increment this if structure changes! |
| static constexpr uint32_t kPersistentTypeId = 0x06567CA6 + 2; |
| |
| // Expected size for 32/64-bit check. |
| static constexpr size_t kExpectedInstanceSize = 16; |
| |
| // Specifies whether a feature override enables or disables the feature. Same |
| // values as the OverrideState enum in feature_list.h |
| uint32_t override_state; |
| |
| // On e.g. x86, alignof(uint64_t) is 4. Ensure consistent size and alignment |
| // of `pickle_size` across platforms. |
| uint32_t padding; |
| |
| // Size of the pickled structure, NOT the total size of this entry. |
| uint64_t pickle_size; |
| |
| // Reads the feature and trial name from the pickle. Calling this is only |
| // valid on an initialized entry that's in shared memory. |
| bool GetFeatureAndTrialName(StringPiece* feature_name, |
| StringPiece* trial_name) const { |
| const char* src = |
| reinterpret_cast<const char*>(this) + sizeof(FeatureEntry); |
| |
| Pickle pickle(src, checked_cast<size_t>(pickle_size)); |
| PickleIterator pickle_iter(pickle); |
| |
| if (!pickle_iter.ReadStringPiece(feature_name)) |
| return false; |
| |
| // Return true because we are not guaranteed to have a trial name anyways. |
| std::ignore = pickle_iter.ReadStringPiece(trial_name); |
| return true; |
| } |
| }; |
| |
| // Splits |text| into two parts by the |separator| where the first part will be |
| // returned updated in |first| and the second part will be returned as |second|. |
| // This function returns false if there is more than one |separator| in |first|. |
| // If there is no |separator| presented in |first|, this function will not |
| // modify |first| and |second|. It's used for splitting the |enable_features| |
| // flag into feature name, field trial name and feature parameters. |
| bool SplitIntoTwo(StringPiece text, |
| StringPiece separator, |
| StringPiece* first, |
| std::string* second) { |
| std::vector<StringPiece> parts = |
| SplitStringPiece(text, separator, TRIM_WHITESPACE, SPLIT_WANT_ALL); |
| if (parts.size() == 2) { |
| *second = std::string(parts[1]); |
| } else if (parts.size() > 2) { |
| DLOG(ERROR) << "Only one '" << separator |
| << "' is allowed but got: " << *first; |
| return false; |
| } |
| *first = parts[0]; |
| return true; |
| } |
| |
| // Checks and parses the |enable_features| flag and sets |
| // |parsed_enable_features| to be a comma-separated list of features, |
| // |force_fieldtrials| to be a comma-separated list of field trials that each |
| // feature want to associate with and |force_fieldtrial_params| to be the field |
| // trial parameters for each field trial. |
| // Returns true if |enable_features| is parsable, otherwise false. |
| bool ParseEnableFeatures(const std::string& enable_features, |
| std::string* parsed_enable_features, |
| std::string* force_fieldtrials, |
| std::string* force_fieldtrial_params) { |
| std::vector<std::string> enable_features_list; |
| std::vector<std::string> force_fieldtrials_list; |
| std::vector<std::string> force_fieldtrial_params_list; |
| for (const auto& enable_feature : |
| FeatureList::SplitFeatureListString(enable_features)) { |
| std::string feature_name; |
| std::string study; |
| std::string group; |
| std::string feature_params; |
| if (!FeatureList::ParseEnableFeatureString( |
| enable_feature, &feature_name, &study, &group, &feature_params)) { |
| return false; |
| } |
| |
| // If feature params were set but group and study weren't, associate the |
| // feature and its feature params to a synthetic field trial as the |
| // feature params only make sense when it's combined with a field trial. |
| if (!feature_params.empty()) { |
| force_fieldtrials_list.push_back(study + "/" + group); |
| force_fieldtrial_params_list.push_back(study + "." + group + ":" + |
| feature_params); |
| } |
| enable_features_list.push_back( |
| study.empty() ? feature_name : (feature_name + "<" + study)); |
| } |
| |
| *parsed_enable_features = JoinString(enable_features_list, ","); |
| // Field trial separator is currently a slash. See |
| // |kPersistentStringSeparator| in base/metrics/field_trial.cc. |
| *force_fieldtrials = JoinString(force_fieldtrials_list, "/"); |
| *force_fieldtrial_params = JoinString(force_fieldtrial_params_list, ","); |
| return true; |
| } |
| |
| std::pair<FeatureList::OverrideState, uint16_t> UnpackFeatureCache( |
| uint32_t packed_cache_value) { |
| return std::make_pair( |
| static_cast<FeatureList::OverrideState>(packed_cache_value >> 24), |
| packed_cache_value & 0xFFFF); |
| } |
| |
| uint32_t PackFeatureCache(FeatureList::OverrideState override_state, |
| uint32_t caching_context) { |
| return (static_cast<uint32_t>(override_state) << 24) | |
| (caching_context & 0xFFFF); |
| } |
| |
| } // namespace |
| |
| #if BUILDFLAG(DCHECK_IS_CONFIGURABLE) |
| BASE_FEATURE(kDCheckIsFatalFeature, |
| "DcheckIsFatal", |
| FEATURE_DISABLED_BY_DEFAULT); |
| #endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE) |
| |
| FeatureList::FeatureList() = default; |
| |
| FeatureList::~FeatureList() = default; |
| |
| FeatureList::ScopedDisallowOverrides::ScopedDisallowOverrides( |
| const char* reason) |
| #if DCHECK_IS_ON() |
| : previous_reason_(g_reason_overrides_disallowed) { |
| g_reason_overrides_disallowed = reason; |
| } |
| #else |
| { |
| } |
| #endif |
| |
| FeatureList::ScopedDisallowOverrides::~ScopedDisallowOverrides() { |
| #if DCHECK_IS_ON() |
| g_reason_overrides_disallowed = previous_reason_; |
| #endif |
| } |
| |
| void FeatureList::InitializeFromCommandLine( |
| const std::string& enable_features, |
| const std::string& disable_features) { |
| DCHECK(!initialized_); |
| |
| std::string parsed_enable_features; |
| std::string force_fieldtrials; |
| std::string force_fieldtrial_params; |
| bool parse_enable_features_result = |
| ParseEnableFeatures(enable_features, &parsed_enable_features, |
| &force_fieldtrials, &force_fieldtrial_params); |
| DCHECK(parse_enable_features_result) << StringPrintf( |
| "The --%s list is unparsable or invalid, please check the format.", |
| ::switches::kEnableFeatures); |
| |
| // Only create field trials when field_trial_list is available. Some tests |
| // don't have field trial list available. |
| if (FieldTrialList::GetInstance()) { |
| bool associate_params_result = AssociateFieldTrialParamsFromString( |
| force_fieldtrial_params, &UnescapeValue); |
| DCHECK(associate_params_result) << StringPrintf( |
| "The field trial parameters part of the --%s list is invalid. Make " |
| "sure " |
| "you %%-encode the following characters in param values: %%:/.,", |
| ::switches::kEnableFeatures); |
| |
| bool create_trials_result = |
| FieldTrialList::CreateTrialsFromString(force_fieldtrials); |
| DCHECK(create_trials_result) |
| << StringPrintf("Invalid field trials are specified in --%s.", |
| ::switches::kEnableFeatures); |
| } |
| |
| // Process disabled features first, so that disabled ones take precedence over |
| // enabled ones (since RegisterOverride() uses insert()). |
| RegisterOverridesFromCommandLine(disable_features, OVERRIDE_DISABLE_FEATURE); |
| RegisterOverridesFromCommandLine(parsed_enable_features, |
| OVERRIDE_ENABLE_FEATURE); |
| |
| initialized_from_command_line_ = true; |
| } |
| |
| void FeatureList::InitializeFromSharedMemory( |
| PersistentMemoryAllocator* allocator) { |
| DCHECK(!initialized_); |
| |
| PersistentMemoryAllocator::Iterator iter(allocator); |
| const FeatureEntry* entry; |
| while ((entry = iter.GetNextOfObject<FeatureEntry>()) != nullptr) { |
| OverrideState override_state = |
| static_cast<OverrideState>(entry->override_state); |
| |
| StringPiece feature_name; |
| StringPiece trial_name; |
| if (!entry->GetFeatureAndTrialName(&feature_name, &trial_name)) |
| continue; |
| |
| FieldTrial* trial = FieldTrialList::Find(trial_name); |
| RegisterOverride(feature_name, override_state, trial); |
| } |
| } |
| |
| bool FeatureList::IsFeatureOverridden(const std::string& feature_name) const { |
| return overrides_.count(feature_name); |
| } |
| |
| bool FeatureList::IsFeatureOverriddenFromCommandLine( |
| const std::string& feature_name) const { |
| auto it = overrides_.find(feature_name); |
| return it != overrides_.end() && !it->second.overridden_by_field_trial; |
| } |
| |
| bool FeatureList::IsFeatureOverriddenFromCommandLine( |
| const std::string& feature_name, |
| OverrideState state) const { |
| auto it = overrides_.find(feature_name); |
| return it != overrides_.end() && !it->second.overridden_by_field_trial && |
| it->second.overridden_state == state; |
| } |
| |
| void FeatureList::AssociateReportingFieldTrial( |
| const std::string& feature_name, |
| OverrideState for_overridden_state, |
| FieldTrial* field_trial) { |
| DCHECK( |
| IsFeatureOverriddenFromCommandLine(feature_name, for_overridden_state)); |
| |
| // Only one associated field trial is supported per feature. This is generally |
| // enforced server-side. |
| OverrideEntry* entry = &overrides_.find(feature_name)->second; |
| if (entry->field_trial) { |
| NOTREACHED() << "Feature " << feature_name |
| << " already has trial: " << entry->field_trial->trial_name() |
| << ", associating trial: " << field_trial->trial_name(); |
| return; |
| } |
| |
| entry->field_trial = field_trial; |
| } |
| |
| void FeatureList::RegisterFieldTrialOverride(const std::string& feature_name, |
| OverrideState override_state, |
| FieldTrial* field_trial) { |
| DCHECK(field_trial); |
| DCHECK(!HasAssociatedFieldTrialByFeatureName(feature_name)) |
| << "Feature " << feature_name << " is overriden multiple times in these " |
| << "trials: " |
| << overrides_.find(feature_name)->second.field_trial->trial_name() |
| << " and " << field_trial->trial_name() << ". " |
| << "Check the trial (study) in (1) the server config, " |
| << "(2) fieldtrial_testing_config.json, (3) about_flags.cc, and " |
| << "(4) client-side field trials."; |
| |
| RegisterOverride(feature_name, override_state, field_trial); |
| } |
| |
| void FeatureList::RegisterExtraFeatureOverrides( |
| const std::vector<FeatureOverrideInfo>& extra_overrides) { |
| for (const FeatureOverrideInfo& override_info : extra_overrides) { |
| RegisterOverride(override_info.first.get().name, override_info.second, |
| /* field_trial = */ nullptr); |
| } |
| } |
| |
| void FeatureList::AddFeaturesToAllocator(PersistentMemoryAllocator* allocator) { |
| DCHECK(initialized_); |
| |
| for (const auto& override : overrides_) { |
| Pickle pickle; |
| pickle.WriteString(override.first); |
| if (override.second.field_trial) |
| pickle.WriteString(override.second.field_trial->trial_name()); |
| |
| size_t total_size = sizeof(FeatureEntry) + pickle.size(); |
| FeatureEntry* entry = allocator->New<FeatureEntry>(total_size); |
| if (!entry) |
| return; |
| |
| entry->override_state = override.second.overridden_state; |
| entry->pickle_size = pickle.size(); |
| |
| char* dst = reinterpret_cast<char*>(entry) + sizeof(FeatureEntry); |
| memcpy(dst, pickle.data(), pickle.size()); |
| |
| allocator->MakeIterable(entry); |
| } |
| } |
| |
| void FeatureList::GetFeatureOverrides(std::string* enable_overrides, |
| std::string* disable_overrides, |
| bool include_group_name) const { |
| GetFeatureOverridesImpl(enable_overrides, disable_overrides, false, |
| include_group_name); |
| } |
| |
| void FeatureList::GetCommandLineFeatureOverrides( |
| std::string* enable_overrides, |
| std::string* disable_overrides) const { |
| GetFeatureOverridesImpl(enable_overrides, disable_overrides, true); |
| } |
| |
| // static |
| bool FeatureList::IsEnabled(const Feature& feature) { |
| if (!g_feature_list_instance) { |
| EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature); |
| return feature.default_state == FEATURE_ENABLED_BY_DEFAULT; |
| } |
| return g_feature_list_instance->IsFeatureEnabled(feature); |
| } |
| |
| // static |
| bool FeatureList::IsValidFeatureOrFieldTrialName(StringPiece name) { |
| return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos; |
| } |
| |
| // static |
| absl::optional<bool> FeatureList::GetStateIfOverridden(const Feature& feature) { |
| if (!g_feature_list_instance) { |
| EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature); |
| // If there is no feature list, there can be no overrides. |
| return absl::nullopt; |
| } |
| return g_feature_list_instance->IsFeatureEnabledIfOverridden(feature); |
| } |
| |
| // static |
| FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) { |
| if (!g_feature_list_instance) { |
| EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature); |
| return nullptr; |
| } |
| return g_feature_list_instance->GetAssociatedFieldTrial(feature); |
| } |
| |
| // static |
| std::vector<StringPiece> FeatureList::SplitFeatureListString( |
| StringPiece input) { |
| return SplitStringPiece(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); |
| } |
| |
| // static |
| bool FeatureList::ParseEnableFeatureString(StringPiece enable_feature, |
| std::string* feature_name, |
| std::string* study_name, |
| std::string* group_name, |
| std::string* params) { |
| StringPiece first; |
| // First, check whether ":" is present. If true, feature parameters were |
| // set for this feature. |
| std::string feature_params; |
| if (!SplitIntoTwo(enable_feature, ":", &first, &feature_params)) |
| return false; |
| // Then, check whether "." is present. If true, a group was specified for |
| // this feature. |
| std::string group; |
| if (!SplitIntoTwo(first, ".", &first, &group)) |
| return false; |
| // Finally, check whether "<" is present. If true, a study was specified for |
| // this feature. |
| std::string study; |
| if (!SplitIntoTwo(first, "<", &first, &study)) |
| return false; |
| |
| std::string enable_feature_name(first); |
| // If feature params were set but group and study weren't, associate the |
| // feature and its feature params to a synthetic field trial as the |
| // feature params only make sense when it's combined with a field trial. |
| if (!feature_params.empty()) { |
| study = study.empty() ? "Study" + enable_feature_name : study; |
| group = group.empty() ? "Group" + enable_feature_name : group; |
| } |
| |
| feature_name->swap(enable_feature_name); |
| study_name->swap(study); |
| group_name->swap(group); |
| params->swap(feature_params); |
| return true; |
| } |
| |
| // static |
| bool FeatureList::InitializeInstance(const std::string& enable_features, |
| const std::string& disable_features) { |
| return InitializeInstance(enable_features, disable_features, |
| std::vector<FeatureOverrideInfo>()); |
| } |
| |
| // static |
| bool FeatureList::InitializeInstance( |
| const std::string& enable_features, |
| const std::string& disable_features, |
| const std::vector<FeatureOverrideInfo>& extra_overrides) { |
| // We want to initialize a new instance here to support command-line features |
| // in testing better. For example, we initialize a dummy instance in |
| // base/test/test_suite.cc, and override it in content/browser/ |
| // browser_main_loop.cc. |
| // On the other hand, we want to avoid re-initialization from command line. |
| // For example, we initialize an instance in chrome/browser/ |
| // chrome_browser_main.cc and do not override it in content/browser/ |
| // browser_main_loop.cc. |
| // If the singleton was previously initialized from within an accessor, we |
| // want to prevent callers from reinitializing the singleton and masking the |
| // accessor call(s) which likely returned incorrect information. |
| EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess(); |
| bool instance_existed_before = false; |
| if (g_feature_list_instance) { |
| if (g_feature_list_instance->initialized_from_command_line_) |
| return false; |
| |
| delete g_feature_list_instance; |
| g_feature_list_instance = nullptr; |
| instance_existed_before = true; |
| } |
| |
| std::unique_ptr<FeatureList> feature_list(new FeatureList); |
| feature_list->InitializeFromCommandLine(enable_features, disable_features); |
| feature_list->RegisterExtraFeatureOverrides(extra_overrides); |
| FeatureList::SetInstance(std::move(feature_list)); |
| return !instance_existed_before; |
| } |
| |
| // static |
| FeatureList* FeatureList::GetInstance() { |
| return g_feature_list_instance; |
| } |
| |
| // static |
| void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) { |
| DCHECK(!g_feature_list_instance); |
| instance->FinalizeInitialization(); |
| |
| // Note: Intentional leak of global singleton. |
| g_feature_list_instance = instance.release(); |
| |
| EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess(); |
| |
| #if !BUILDFLAG(IS_NACL) |
| // Configured first because it takes precedence over the getrandom() trial. |
| internal::ConfigureBoringSSLBackedRandBytesFieldTrial(); |
| #endif |
| |
| #if BUILDFLAG(IS_ANDROID) |
| internal::ConfigureRandBytesFieldTrial(); |
| #endif |
| |
| #if BUILDFLAG(DCHECK_IS_CONFIGURABLE) |
| // Update the behaviour of LOGGING_DCHECK to match the Feature configuration. |
| // DCHECK is also forced to be FATAL if we are running a death-test. |
| // TODO(crbug.com/1057995#c11): --gtest_internal_run_death_test doesn't |
| // currently run through this codepath, mitigated in |
| // base::TestSuite::Initialize() for now. |
| // TODO(asvitkine): If we find other use-cases that need integrating here |
| // then define a proper API/hook for the purpose. |
| if (FeatureList::IsEnabled(kDCheckIsFatalFeature) || |
| CommandLine::ForCurrentProcess()->HasSwitch( |
| "gtest_internal_run_death_test")) { |
| logging::LOGGING_DCHECK = logging::LOG_FATAL; |
| } else { |
| logging::LOGGING_DCHECK = logging::LOG_INFO; |
| } |
| #endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE) |
| } |
| |
| // static |
| std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() { |
| FeatureList* old_instance = g_feature_list_instance; |
| g_feature_list_instance = nullptr; |
| EarlyFeatureAccessTracker::GetInstance()->Reset(); |
| return WrapUnique(old_instance); |
| } |
| |
| // static |
| void FeatureList::RestoreInstanceForTesting( |
| std::unique_ptr<FeatureList> instance) { |
| DCHECK(!g_feature_list_instance); |
| // Note: Intentional leak of global singleton. |
| g_feature_list_instance = instance.release(); |
| } |
| |
| // static |
| void FeatureList::FailOnFeatureAccessWithoutFeatureList() { |
| EarlyFeatureAccessTracker::GetInstance() |
| ->FailOnFeatureAccessWithoutFeatureList(); |
| } |
| |
| void FeatureList::SetCachingContextForTesting(uint16_t caching_context) { |
| caching_context_ = caching_context; |
| } |
| |
| void FeatureList::FinalizeInitialization() { |
| DCHECK(!initialized_); |
| // Store the field trial list pointer for DCHECKing. |
| field_trial_list_ = FieldTrialList::GetInstance(); |
| initialized_ = true; |
| } |
| |
| bool FeatureList::IsFeatureEnabled(const Feature& feature) const { |
| OverrideState overridden_state = GetOverrideState(feature); |
| |
| // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below. |
| if (overridden_state != OVERRIDE_USE_DEFAULT) |
| return overridden_state == OVERRIDE_ENABLE_FEATURE; |
| |
| return feature.default_state == FEATURE_ENABLED_BY_DEFAULT; |
| } |
| |
| absl::optional<bool> FeatureList::IsFeatureEnabledIfOverridden( |
| const Feature& feature) const { |
| OverrideState overridden_state = GetOverrideState(feature); |
| |
| // If marked as OVERRIDE_USE_DEFAULT, fall through to returning empty. |
| if (overridden_state != OVERRIDE_USE_DEFAULT) |
| return overridden_state == OVERRIDE_ENABLE_FEATURE; |
| |
| return absl::nullopt; |
| } |
| |
| FeatureList::OverrideState FeatureList::GetOverrideState( |
| const Feature& feature) const { |
| DCHECK(initialized_); |
| DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name; |
| DCHECK(CheckFeatureIdentity(feature)) |
| << feature.name |
| << " has multiple definitions. Either it is defined more than once in " |
| "code or (for component builds) the code is built into multiple " |
| "components (shared libraries) without a corresponding export " |
| "statement"; |
| |
| uint32_t current_cache_value = |
| feature.cached_value.load(std::memory_order_relaxed); |
| |
| auto unpacked = UnpackFeatureCache(current_cache_value); |
| |
| if (unpacked.second == caching_context_) |
| return unpacked.first; |
| |
| OverrideState state = GetOverrideStateByFeatureName(feature.name); |
| uint32_t new_cache_value = PackFeatureCache(state, caching_context_); |
| |
| // Update the cache with the new value. |
| // In non-test code, this value can be in one of 2 states: either it's unset, |
| // or another thread has updated it to the same value we're about to write. |
| // Because of this, a plain `store` yields the correct result in all cases. |
| // In test code, it's possible for a different thread to have installed a new |
| // `ScopedFeatureList` and written a value that's different than the one we're |
| // about to write, although that would be a thread safety violation already |
| // and such tests should be fixed. |
| feature.cached_value.store(new_cache_value, std::memory_order_relaxed); |
| |
| return state; |
| } |
| |
| FeatureList::OverrideState FeatureList::GetOverrideStateByFeatureName( |
| StringPiece feature_name) const { |
| DCHECK(initialized_); |
| DCHECK(IsValidFeatureOrFieldTrialName(feature_name)) << feature_name; |
| |
| auto it = overrides_.find(feature_name); |
| if (it != overrides_.end()) { |
| const OverrideEntry& entry = it->second; |
| |
| // Activate the corresponding field trial, if necessary. |
| if (entry.field_trial) |
| entry.field_trial->Activate(); |
| |
| // TODO(asvitkine) Expand this section as more support is added. |
| |
| return entry.overridden_state; |
| } |
| // Otherwise, report that we want to use the default state. |
| return OVERRIDE_USE_DEFAULT; |
| } |
| |
| FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) const { |
| DCHECK(initialized_); |
| DCHECK(CheckFeatureIdentity(feature)) << feature.name; |
| |
| return GetAssociatedFieldTrialByFeatureName(feature.name); |
| } |
| |
| const base::FeatureList::OverrideEntry* |
| FeatureList::GetOverrideEntryByFeatureName(StringPiece name) const { |
| DCHECK(initialized_); |
| DCHECK(IsValidFeatureOrFieldTrialName(name)) << name; |
| |
| auto it = overrides_.find(name); |
| if (it != overrides_.end()) { |
| const OverrideEntry& entry = it->second; |
| return &entry; |
| } |
| return nullptr; |
| } |
| |
| FieldTrial* FeatureList::GetAssociatedFieldTrialByFeatureName( |
| StringPiece name) const { |
| DCHECK(initialized_); |
| |
| const base::FeatureList::OverrideEntry* entry = |
| GetOverrideEntryByFeatureName(name); |
| if (entry) { |
| return entry->field_trial; |
| } |
| return nullptr; |
| } |
| |
| bool FeatureList::HasAssociatedFieldTrialByFeatureName(StringPiece name) const { |
| DCHECK(!initialized_); |
| auto entry = overrides_.find(name); |
| return entry != overrides_.end() && entry->second.field_trial != nullptr; |
| } |
| |
| FieldTrial* FeatureList::GetEnabledFieldTrialByFeatureName( |
| StringPiece name) const { |
| DCHECK(initialized_); |
| |
| const base::FeatureList::OverrideEntry* entry = |
| GetOverrideEntryByFeatureName(name); |
| if (entry && |
| entry->overridden_state == base::FeatureList::OVERRIDE_ENABLE_FEATURE) { |
| return entry->field_trial; |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<FeatureList::Accessor> FeatureList::ConstructAccessor() { |
| if (initialized_) { |
| // This function shouldn't be called after initialization. |
| NOTREACHED(); |
| return nullptr; |
| } |
| // Use new and WrapUnique because we want to restrict access to the Accessor's |
| // constructor. |
| return base::WrapUnique(new Accessor(this)); |
| } |
| |
| void FeatureList::RegisterOverridesFromCommandLine( |
| const std::string& feature_list, |
| OverrideState overridden_state) { |
| for (const auto& value : SplitFeatureListString(feature_list)) { |
| StringPiece feature_name = value; |
| FieldTrial* trial = nullptr; |
| |
| // The entry may be of the form FeatureName<FieldTrialName - in which case, |
| // this splits off the field trial name and associates it with the override. |
| std::string::size_type pos = feature_name.find('<'); |
| if (pos != std::string::npos) { |
| feature_name = StringPiece(value.data(), pos); |
| trial = FieldTrialList::Find(value.substr(pos + 1)); |
| #if !BUILDFLAG(IS_NACL) |
| // If the below DCHECK fires, it means a non-existent trial name was |
| // specified via the "Feature<Trial" command-line syntax. |
| DCHECK(trial) << "trial='" << value.substr(pos + 1) << "' does not exist"; |
| #endif // !BUILDFLAG(IS_NACL) |
| } |
| |
| RegisterOverride(feature_name, overridden_state, trial); |
| } |
| } |
| |
| void FeatureList::RegisterOverride(StringPiece feature_name, |
| OverrideState overridden_state, |
| FieldTrial* field_trial) { |
| DCHECK(!initialized_); |
| DCheckOverridesAllowed(); |
| if (field_trial) { |
| DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name())) |
| << field_trial->trial_name(); |
| } |
| if (StartsWith(feature_name, "*")) { |
| feature_name = feature_name.substr(1); |
| overridden_state = OVERRIDE_USE_DEFAULT; |
| } |
| |
| // Note: The semantics of emplace() is that it does not overwrite the entry if |
| // one already exists for the key. Thus, only the first override for a given |
| // feature name takes effect. |
| overrides_.emplace(std::string(feature_name), |
| OverrideEntry(overridden_state, field_trial)); |
| } |
| |
| void FeatureList::GetFeatureOverridesImpl(std::string* enable_overrides, |
| std::string* disable_overrides, |
| bool command_line_only, |
| bool include_group_name) const { |
| DCHECK(initialized_); |
| |
| // Check that the FieldTrialList this is associated with, if any, is the |
| // active one. If not, it likely indicates that this FeatureList has override |
| // entries from a freed FieldTrial, which may be caused by an incorrect test |
| // set up. |
| if (field_trial_list_) |
| DCHECK_EQ(field_trial_list_, FieldTrialList::GetInstance()); |
| |
| enable_overrides->clear(); |
| disable_overrides->clear(); |
| |
| // Note: Since |overrides_| is a std::map, iteration will be in alphabetical |
| // order. This is not guaranteed to users of this function, but is useful for |
| // tests to assume the order. |
| for (const auto& entry : overrides_) { |
| if (command_line_only && |
| (entry.second.field_trial != nullptr || |
| entry.second.overridden_state == OVERRIDE_USE_DEFAULT)) { |
| continue; |
| } |
| |
| std::string* target_list = nullptr; |
| switch (entry.second.overridden_state) { |
| case OVERRIDE_USE_DEFAULT: |
| case OVERRIDE_ENABLE_FEATURE: |
| target_list = enable_overrides; |
| break; |
| case OVERRIDE_DISABLE_FEATURE: |
| target_list = disable_overrides; |
| break; |
| } |
| |
| if (!target_list->empty()) |
| target_list->push_back(','); |
| if (entry.second.overridden_state == OVERRIDE_USE_DEFAULT) |
| target_list->push_back('*'); |
| target_list->append(entry.first); |
| if (entry.second.field_trial) { |
| auto* const field_trial = entry.second.field_trial.get(); |
| target_list->push_back('<'); |
| target_list->append(field_trial->trial_name()); |
| if (include_group_name) { |
| target_list->push_back('.'); |
| target_list->append(field_trial->GetGroupNameWithoutActivation()); |
| } |
| } |
| } |
| } |
| |
| bool FeatureList::CheckFeatureIdentity(const Feature& feature) const { |
| AutoLock auto_lock(feature_identity_tracker_lock_); |
| |
| auto it = feature_identity_tracker_.find(feature.name); |
| if (it == feature_identity_tracker_.end()) { |
| // If it's not tracked yet, register it. |
| feature_identity_tracker_[feature.name] = &feature; |
| return true; |
| } |
| // Compare address of |feature| to the existing tracked entry. |
| return it->second == &feature; |
| } |
| |
| FeatureList::OverrideEntry::OverrideEntry(OverrideState overridden_state, |
| FieldTrial* field_trial) |
| : overridden_state(overridden_state), |
| field_trial(field_trial), |
| overridden_by_field_trial(field_trial != nullptr) {} |
| |
| FeatureList::Accessor::Accessor(FeatureList* feature_list) |
| : feature_list_(feature_list) {} |
| |
| FeatureList::OverrideState FeatureList::Accessor::GetOverrideStateByFeatureName( |
| StringPiece feature_name) { |
| return feature_list_->GetOverrideStateByFeatureName(feature_name); |
| } |
| |
| bool FeatureList::Accessor::GetParamsByFeatureName( |
| StringPiece feature_name, |
| std::map<std::string, std::string>* params) { |
| base::FieldTrial* trial = |
| feature_list_->GetAssociatedFieldTrialByFeatureName(feature_name); |
| return FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(trial, |
| params); |
| } |
| |
| } // namespace base |