| // Copyright 2015 The Chromium Authors. All rights reserved. | 
 | // 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 <stddef.h> | 
 |  | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/logging.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/metrics/field_trial.h" | 
 | #include "base/pickle.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/string_util.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 whether the FeatureList instance was initialized via an accessor. | 
 | bool g_initialized_from_accessor = false; | 
 |  | 
 | // 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 + 1; | 
 |  | 
 |   // Expected size for 32/64-bit check. | 
 |   static constexpr size_t kExpectedInstanceSize = 8; | 
 |  | 
 |   // Specifies whether a feature override enables or disables the feature. Same | 
 |   // values as the OverrideState enum in feature_list.h | 
 |   uint32_t override_state; | 
 |  | 
 |   // Size of the pickled structure, NOT the total size of this entry. | 
 |   uint32_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, 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. | 
 |     auto sink = pickle_iter.ReadStringPiece(trial_name); | 
 |     ALLOW_UNUSED_LOCAL(sink); | 
 |     return true; | 
 |   } | 
 | }; | 
 |  | 
 | // Some characters are not allowed to appear in feature names or the associated | 
 | // field trial names, as they are used as special characters for command-line | 
 | // serialization. This function checks that the strings are ASCII (since they | 
 | // are used in command-line API functions that require ASCII) and whether there | 
 | // are any reserved characters present, returning true if the string is valid. | 
 | // Only called in DCHECKs. | 
 | bool IsValidFeatureOrFieldTrialName(const std::string& name) { | 
 |   return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | #if DCHECK_IS_CONFIGURABLE | 
 | const Feature kDCheckIsFatalFeature{"DcheckIsFatal", | 
 |                                     base::FEATURE_DISABLED_BY_DEFAULT}; | 
 | #endif  // DCHECK_IS_CONFIGURABLE | 
 |  | 
 | FeatureList::FeatureList() = default; | 
 |  | 
 | FeatureList::~FeatureList() = default; | 
 |  | 
 | void FeatureList::InitializeFromCommandLine( | 
 |     const std::string& enable_features, | 
 |     const std::string& disable_features) { | 
 |   DCHECK(!initialized_); | 
 |  | 
 |   // Process disabled features first, so that disabled ones take precedence over | 
 |   // enabled ones (since RegisterOverride() uses insert()). | 
 |   RegisterOverridesFromCommandLine(disable_features, OVERRIDE_DISABLE_FEATURE); | 
 |   RegisterOverridesFromCommandLine(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.as_string()); | 
 |     RegisterOverride(feature_name, override_state, 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_state == state && | 
 |          !it->second.overridden_by_field_trial; | 
 | } | 
 |  | 
 | 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(!ContainsKey(overrides_, feature_name) || | 
 |          !overrides_.find(feature_name)->second.field_trial) | 
 |       << "Feature " << feature_name | 
 |       << " has conflicting field trial overrides: " | 
 |       << overrides_.find(feature_name)->second.field_trial->trial_name() | 
 |       << " / " << field_trial->trial_name(); | 
 |  | 
 |   RegisterOverride(feature_name, override_state, field_trial); | 
 | } | 
 |  | 
 | 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) { | 
 |   GetFeatureOverridesImpl(enable_overrides, disable_overrides, false); | 
 | } | 
 |  | 
 | void FeatureList::GetCommandLineFeatureOverrides( | 
 |     std::string* enable_overrides, | 
 |     std::string* disable_overrides) { | 
 |   GetFeatureOverridesImpl(enable_overrides, disable_overrides, true); | 
 | } | 
 |  | 
 | // static | 
 | bool FeatureList::IsEnabled(const Feature& feature) { | 
 |   if (!g_feature_list_instance) { | 
 |     g_initialized_from_accessor = true; | 
 |     return feature.default_state == FEATURE_ENABLED_BY_DEFAULT; | 
 |   } | 
 |   return g_feature_list_instance->IsFeatureEnabled(feature); | 
 | } | 
 |  | 
 | // static | 
 | FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) { | 
 |   if (!g_feature_list_instance) { | 
 |     g_initialized_from_accessor = true; | 
 |     return nullptr; | 
 |   } | 
 |   return g_feature_list_instance->GetAssociatedFieldTrial(feature); | 
 | } | 
 |  | 
 | // static | 
 | std::vector<base::StringPiece> FeatureList::SplitFeatureListString( | 
 |     base::StringPiece input) { | 
 |   return SplitStringPiece(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); | 
 | } | 
 |  | 
 | // static | 
 | bool FeatureList::InitializeInstance(const std::string& enable_features, | 
 |                                      const std::string& disable_features) { | 
 |   // 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. | 
 |   CHECK(!g_initialized_from_accessor); | 
 |   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<base::FeatureList> feature_list(new base::FeatureList); | 
 |   feature_list->InitializeFromCommandLine(enable_features, disable_features); | 
 |   base::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(); | 
 |  | 
 | #if DCHECK_IS_CONFIGURABLE | 
 |   // Update the behaviour of LOG_DCHECK to match the Feature configuration. | 
 |   // DCHECK is also forced to be FATAL if we are running a death-test. | 
 |   // TODO(asvitkine): If we find other use-cases that need integrating here | 
 |   // then define a proper API/hook for the purpose. | 
 |   if (base::FeatureList::IsEnabled(kDCheckIsFatalFeature) || | 
 |       base::CommandLine::ForCurrentProcess()->HasSwitch( | 
 |           "gtest_internal_run_death_test")) { | 
 |     logging::LOG_DCHECK = logging::LOG_FATAL; | 
 |   } else { | 
 |     logging::LOG_DCHECK = logging::LOG_INFO; | 
 |   } | 
 | #endif  // DCHECK_IS_CONFIGURABLE | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() { | 
 |   FeatureList* old_instance = g_feature_list_instance; | 
 |   g_feature_list_instance = nullptr; | 
 |   g_initialized_from_accessor = false; | 
 |   return base::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(); | 
 | } | 
 |  | 
 | void FeatureList::FinalizeInitialization() { | 
 |   DCHECK(!initialized_); | 
 |   initialized_ = true; | 
 | } | 
 |  | 
 | bool FeatureList::IsFeatureEnabled(const Feature& feature) { | 
 |   DCHECK(initialized_); | 
 |   DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name; | 
 |   DCHECK(CheckFeatureIdentity(feature)) << 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->group(); | 
 |  | 
 |     // TODO(asvitkine) Expand this section as more support is added. | 
 |  | 
 |     // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below. | 
 |     if (entry.overridden_state != OVERRIDE_USE_DEFAULT) | 
 |       return entry.overridden_state == OVERRIDE_ENABLE_FEATURE; | 
 |   } | 
 |   // Otherwise, return the default state. | 
 |   return feature.default_state == FEATURE_ENABLED_BY_DEFAULT; | 
 | } | 
 |  | 
 | FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) { | 
 |   DCHECK(initialized_); | 
 |   DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name; | 
 |   DCHECK(CheckFeatureIdentity(feature)) << feature.name; | 
 |  | 
 |   auto it = overrides_.find(feature.name); | 
 |   if (it != overrides_.end()) { | 
 |     const OverrideEntry& entry = it->second; | 
 |     return entry.field_trial; | 
 |   } | 
 |  | 
 |   return nullptr; | 
 | } | 
 |  | 
 | void FeatureList::RegisterOverridesFromCommandLine( | 
 |     const std::string& feature_list, | 
 |     OverrideState overridden_state) { | 
 |   for (const auto& value : SplitFeatureListString(feature_list)) { | 
 |     StringPiece feature_name = value; | 
 |     base::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.set(value.data(), pos); | 
 |       trial = base::FieldTrialList::Find(value.substr(pos + 1).as_string()); | 
 |     } | 
 |  | 
 |     RegisterOverride(feature_name, overridden_state, trial); | 
 |   } | 
 | } | 
 |  | 
 | void FeatureList::RegisterOverride(StringPiece feature_name, | 
 |                                    OverrideState overridden_state, | 
 |                                    FieldTrial* field_trial) { | 
 |   DCHECK(!initialized_); | 
 |   if (field_trial) { | 
 |     DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name())) | 
 |         << field_trial->trial_name(); | 
 |   } | 
 |   if (feature_name.starts_with("*")) { | 
 |     feature_name = feature_name.substr(1); | 
 |     overridden_state = OVERRIDE_USE_DEFAULT; | 
 |   } | 
 |  | 
 |   // Note: The semantics of insert() 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_.insert(std::make_pair( | 
 |       feature_name.as_string(), OverrideEntry(overridden_state, field_trial))); | 
 | } | 
 |  | 
 | void FeatureList::GetFeatureOverridesImpl(std::string* enable_overrides, | 
 |                                           std::string* disable_overrides, | 
 |                                           bool command_line_only) { | 
 |   DCHECK(initialized_); | 
 |  | 
 |   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) { | 
 |       target_list->push_back('<'); | 
 |       target_list->append(entry.second.field_trial->trial_name()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool FeatureList::CheckFeatureIdentity(const Feature& feature) { | 
 |   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) {} | 
 |  | 
 | }  // namespace base |