// 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. #ifndef BASE_FEATURE_LIST_H_ #define BASE_FEATURE_LIST_H_ #include #include #include #include #include #include #include #include #include #include "base/base_export.h" #include "base/compiler_specific.h" #include "base/containers/flat_map.h" #include "base/containers/flat_set.h" #include "base/dcheck_is_on.h" #include "base/feature_list_buildflags.h" #include "base/gtest_prod_util.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/synchronization/lock.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" namespace base { class FieldTrial; class FieldTrialList; class PersistentMemoryAllocator; #if BUILDFLAG(IS_CHROMEOS_ASH) class FeatureVisitor; #endif // BUILDFLAG(IS_CHROMEOS_ASH) // Specifies whether a given feature is enabled or disabled by default. // NOTE: The actual runtime state may be different, due to a field trial or a // command line switch. enum FeatureState { FEATURE_DISABLED_BY_DEFAULT, FEATURE_ENABLED_BY_DEFAULT, }; // Recommended macros for declaring and defining features: // // - `kFeature` is the C++ identifier that will be used for the `base::Feature`. // - `name` is the feature name, which must be globally unique. This name is // used to enable/disable features via experiments and command-line flags. // Names should use CamelCase-style naming, e.g. "MyGreatFeature". // - `default_state` is the default state to use for the feature, i.e. // `base::FEATURE_DISABLED_BY_DEFAULT` or `base::FEATURE_ENABLED_BY_DEFAULT`. // As noted above, the actual runtime state may differ from the default state, // due to field trials or command-line switches. // Provides a forward declaration for `kFeature` in a header file, e.g. // // BASE_DECLARE_FEATURE(kMyFeature); // // If the feature needs to be marked as exported, i.e. it is referenced by // multiple components, then write: // // COMPONENT_EXPORT(MY_COMPONENT) BASE_DECLARE_FEATURE(kMyFeature); #define BASE_DECLARE_FEATURE(kFeature) \ extern constinit const base::Feature kFeature // Provides a definition for `kFeature` with `name` and `default_state`, e.g. // // BASE_FEATURE(kMyFeature, "MyFeature", base::FEATURE_DISABLED_BY_DEFAULT); // // Features should *not* be defined in header files; do not use this macro in // header files. #define BASE_FEATURE(feature, name, default_state) \ constinit const base::Feature feature( \ name, default_state, base::internal::FeatureMacroHandshake::kSecret) // Secret handshake to (try to) ensure all places that construct a base::Feature // go through the helper `BASE_FEATURE()` macro above. namespace internal { enum class FeatureMacroHandshake { kSecret }; } // The Feature struct is used to define the default state for a feature. There // must only ever be one struct instance for a given feature name—generally // defined as a constant global variable or file static. Declare and define // features using the `BASE_DECLARE_FEATURE()` and `BASE_FEATURE()` macros // above, as there are some subtleties involved. // // Feature constants are internally mutable, as this allows them to contain a // mutable member to cache their override state, while still remaining declared // as const. This cache member allows for significantly faster IsEnabled() // checks. // // However, the "Mutable Constants" check [1] detects this as a regression, // because this usually means that a readonly symbol is put in writable memory // when readonly memory would be more efficient. // // The performance gains of the cache are large enough to offset the downsides // to having the symbols in bssdata rather than rodata. Use LOGICALLY_CONST to // suppress the "Mutable Constants" check. // // [1]: // https://crsrc.org/c/docs/speed/binary_size/android_binary_size_trybot.md#Mutable-Constants struct BASE_EXPORT LOGICALLY_CONST Feature { constexpr Feature(const char* name, FeatureState default_state, internal::FeatureMacroHandshake) : name(name), default_state(default_state) { #if BUILDFLAG(ENABLE_BANNED_BASE_FEATURE_PREFIX) if (std::string_view(name).find(BUILDFLAG(BANNED_BASE_FEATURE_PREFIX)) == 0) { LOG(FATAL) << "Invalid feature name " << name << " starts with " << BUILDFLAG(BANNED_BASE_FEATURE_PREFIX); } #endif // BUILDFLAG(ENABLE_BANNED_BASE_FEATURE_PREFIX) } // Non-copyable since: // - there should be only one `Feature` instance per unique name. // - a `Feature` contains internal cached state about the override state. Feature(const Feature&) = delete; Feature& operator=(const Feature&) = delete; // The name of the feature. This should be unique to each feature and is used // for enabling/disabling features via command line flags and experiments. // It is strongly recommended to use CamelCase style for feature names, e.g. // "MyGreatFeature". const char* const name; // The default state (i.e. enabled or disabled) for this feature. // NOTE: The actual runtime state may be different, due to a field trial or a // command line switch. const FeatureState default_state; private: friend class FeatureList; // A packed value where the first 8 bits represent the `OverrideState` of this // feature, and the last 16 bits are a caching context ID used to allow // ScopedFeatureLists to invalidate these cached values in testing. A value of // 0 in the caching context ID field indicates that this value has never been // looked up and cached, a value of 1 indicates this value contains the cached // `OverrideState` that was looked up via `base::FeatureList`, and any other // value indicate that this cached value is only valid for a particular // ScopedFeatureList instance. // // Packing these values into a uint32_t makes it so that atomic operations // performed on this fields can be lock free. // // The override state stored in this field is only used if the current // `FeatureList::caching_context_` field is equal to the lower 16 bits of the // packed cached value. Otherwise, the override state is looked up in the // feature list and the cache is updated. mutable std::atomic cached_value = 0; }; #if BUILDFLAG(DCHECK_IS_CONFIGURABLE) // DCHECKs have been built-in, and are configurable at run-time to be fatal, or // not, via a DcheckIsFatal feature. We define the Feature here since it is // checked in FeatureList::SetInstance(). See https://crbug.com/596231. BASE_EXPORT BASE_DECLARE_FEATURE(kDCheckIsFatalFeature); #endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE) // The FeatureList class is used to determine whether a given feature is on or // off. It provides an authoritative answer, taking into account command-line // overrides and experimental control. // // The basic use case is for any feature that can be toggled (e.g. through // command-line or an experiment) to have a defined Feature struct, e.g.: // // const base::Feature kMyGreatFeature { // "MyGreatFeature", base::FEATURE_ENABLED_BY_DEFAULT // }; // // Then, client code that wishes to query the state of the feature would check: // // if (base::FeatureList::IsEnabled(kMyGreatFeature)) { // // Feature code goes here. // } // // Behind the scenes, the above call would take into account any command-line // flags to enable or disable the feature, any experiments that may control it // and finally its default state (in that order of priority), to determine // whether the feature is on. // // Features can be explicitly forced on or off by specifying a list of comma- // separated feature names via the following command-line flags: // // --enable-features=Feature5,Feature7 // --disable-features=Feature1,Feature2,Feature3 // // To enable/disable features in a test, do NOT append --enable-features or // --disable-features to the command-line directly. Instead, use // ScopedFeatureList. See base/test/scoped_feature_list.h for details. // // After initialization (which should be done single-threaded), the FeatureList // API is thread safe. // // Note: This class is a singleton, but does not use base/memory/singleton.h in // order to have control over its initialization sequence. Specifically, the // intended use is to create an instance of this class and fully initialize it, // before setting it as the singleton for a process, via SetInstance(). class BASE_EXPORT FeatureList { public: FeatureList(); FeatureList(const FeatureList&) = delete; FeatureList& operator=(const FeatureList&) = delete; ~FeatureList(); // Used by common test fixture classes to prevent abuse of ScopedFeatureList // after multiple threads have started. class BASE_EXPORT ScopedDisallowOverrides { public: explicit ScopedDisallowOverrides(const char* reason); ScopedDisallowOverrides(const ScopedDisallowOverrides&) = delete; ScopedDisallowOverrides& operator=(const ScopedDisallowOverrides&) = delete; ~ScopedDisallowOverrides(); private: #if DCHECK_IS_ON() const char* const previous_reason_; #endif }; // Specifies whether a feature override enables or disables the feature. enum OverrideState { OVERRIDE_USE_DEFAULT, OVERRIDE_DISABLE_FEATURE, OVERRIDE_ENABLE_FEATURE, }; // Accessor class, used to look up features by _name_ rather than by Feature // object. // Should only be used in limited cases. See ConstructAccessor() for details. class BASE_EXPORT Accessor { public: Accessor(const Accessor&) = delete; Accessor& operator=(const Accessor&) = delete; // Looks up the feature, returning only its override state, rather than // falling back on a default value (since there is no default value given). // Callers of this MUST ensure that there is a consistent, compile-time // default value associated. FeatureList::OverrideState GetOverrideStateByFeatureName( std::string_view feature_name); // Look up the feature, and, if present, populate |params|. // See GetFieldTrialParams in field_trial_params.h for more documentation. bool GetParamsByFeatureName(std::string_view feature_name, std::map* params); private: // Allow FeatureList to construct this class. friend class FeatureList; explicit Accessor(FeatureList* feature_list); // Unowned pointer to the FeatureList object we use to look up feature // enablement. raw_ptr feature_list_; }; // Describes a feature override. The first member is a Feature that will be // overridden with the state given by the second member. using FeatureOverrideInfo = std::pair, OverrideState>; // Initializes feature overrides via command-line flags `--enable-features=` // and `--disable-features=`, each of which is a comma-separated list of // features to enable or disable, respectively. This function also allows // users to set a feature's field trial params via `--enable-features=`. Must // only be invoked during the initialization phase (before // FinalizeInitialization() has been called). // // If a feature appears on both lists, then it will be disabled. If // a list entry has the format "FeatureName& extra_overrides); // Loops through feature overrides and serializes them all into |allocator|. void AddFeaturesToAllocator(PersistentMemoryAllocator* allocator); // Returns comma-separated lists of feature names (in the same format that is // accepted by InitFromCommandLine()) corresponding to features that // have been overridden - either through command-line or via FieldTrials. For // those features that have an associated FieldTrial, the output entry will be // of the format "FeatureName ConstructAccessor(); // Returns whether the given `feature` is enabled. // // If no `FeatureList` instance is registered, this will: // - DCHECK(), if FailOnFeatureAccessWithoutFeatureList() was called. // TODO(crbug.com/40237050): Change the DCHECK to a CHECK when we're // confident that all early accesses have been fixed. We don't want to // get many crash reports from the field in the meantime. // - Return the default state, otherwise. Registering a `FeatureList` later // will fail. // // TODO(crbug.com/40237050): Make early FeatureList access fail on iOS, // Android and ChromeOS. This currently only works on Windows, Mac and Linux. // // A feature with a given name must only have a single corresponding Feature // instance, which is checked in builds with DCHECKs enabled. static bool IsEnabled(const Feature& feature); // 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. static bool IsValidFeatureOrFieldTrialName(std::string_view name); // If the given |feature| is overridden, returns its enabled state; otherwise, // returns an empty optional. Must only be called after the singleton instance // has been registered via SetInstance(). Additionally, a feature with a given // name must only have a single corresponding Feature struct, which is checked // in builds with DCHECKs enabled. static std::optional GetStateIfOverridden(const Feature& feature); // Returns the field trial associated with the given |feature|. Must only be // called after the singleton instance has been registered via SetInstance(). static FieldTrial* GetFieldTrial(const Feature& feature); // Splits a comma-separated string containing feature names into a vector. The // resulting pieces point to parts of |input|. static std::vector SplitFeatureListString( std::string_view input); // Checks and parses the |enable_feature| (e.g. // FeatureName& extra_overrides); // Returns the singleton instance of FeatureList. Will return null until an // instance is registered via SetInstance(). static FeatureList* GetInstance(); // Registers the given |instance| to be the singleton feature list for this // process. This should only be called once and |instance| must not be null. // Note: If you are considering using this for the purposes of testing, take // a look at using base/test/scoped_feature_list.h instead. static void SetInstance(std::unique_ptr instance); // Registers the given `instance` to be the temporary singleton feature list // for this process. While the given `instance` is the singleton feature list, // only the state of features matching `allowed_feature_names` can be checked. // Attempting to query other feature will behave as if no feature list was set // at all. It is expected that this instance is replaced using `SetInstance` // with an instance without limitations as soon as practical. static void SetEarlyAccessInstance( std::unique_ptr instance, base::flat_set allowed_feature_names); // Clears the previously-registered singleton instance for tests and returns // the old instance. // Note: Most tests should never call this directly. Instead consider using // base::test::ScopedFeatureList. static std::unique_ptr ClearInstanceForTesting(); // Sets a given (initialized) |instance| to be the singleton feature list, // for testing. Existing instance must be null. This is primarily intended // to support base::test::ScopedFeatureList helper class. static void RestoreInstanceForTesting(std::unique_ptr instance); // After calling this, an attempt to access feature state when no FeatureList // is registered will DCHECK. // // TODO(crbug.com/40237050): Change the DCHECK to a CHECK when we're confident // that all early accesses have been fixed. We don't want to get many crash // reports from the field in the meantime. // // Note: This isn't the default behavior because accesses are tolerated in // processes that never register a FeatureList. static void FailOnFeatureAccessWithoutFeatureList(); // Returns the first feature that was accessed before a FeatureList was // registered that allows accessing the feature. static const Feature* GetEarlyAccessedFeatureForTesting(); // Resets the state of the early feature access tracker. static void ResetEarlyFeatureAccessTrackerForTesting(); // Adds a feature to the early allowed feature access list for tests. Should // only be called on a FeatureList that was set with SetEarlyAccessInstance(). void AddEarlyAllowedFeatureForTesting(std::string feature_name); #if BUILDFLAG(IS_CHROMEOS_ASH) // Allows a visitor to record override state, parameters, and field trial // associated with each feature. // // NOTE: This is intended only for the special case of needing to get all // overrides. This use case is specific to CrOS-Ash. Most users should call // IsEnabled() to query a feature's state. static void VisitFeaturesAndParams(FeatureVisitor& visitor); #endif // BULDFLAG(IS_CHROMEOS_ASH) private: FRIEND_TEST_ALL_PREFIXES(FeatureListTest, CheckFeatureIdentity); FRIEND_TEST_ALL_PREFIXES(FeatureListTest, StoreAndRetrieveFeaturesFromSharedMemory); FRIEND_TEST_ALL_PREFIXES(FeatureListTest, StoreAndRetrieveAssociatedFeaturesFromSharedMemory); // Allow Accessor to access GetOverrideStateByFeatureName(). friend class Accessor; struct OverrideEntry { // The overridden enable (on/off) state of the feature. OverrideState overridden_state; // An optional associated field trial, which will be activated when the // state of the feature is queried for the first time. Weak pointer to the // FieldTrial object that is owned by the FieldTrialList singleton. raw_ptr field_trial; // Specifies whether the feature's state is overridden by |field_trial|. // If it's not, and |field_trial| is not null, it means it is simply an // associated field trial for reporting purposes (and |overridden_state| // came from the command-line). bool overridden_by_field_trial; // TODO(asvitkine): Expand this as more support is added. // Constructs an OverrideEntry for the given |overridden_state|. If // |field_trial| is not null, it implies that |overridden_state| comes from // the trial, so |overridden_by_field_trial| will be set to true. OverrideEntry(OverrideState overridden_state, FieldTrial* field_trial); }; // Returns the override for the field trial associated with the given feature // |name| or null if the feature is not found. const base::FeatureList::OverrideEntry* GetOverrideEntryByFeatureName( std::string_view name) const; // Finalizes the initialization state of the FeatureList, so that no further // overrides can be registered. This is called by SetInstance() on the // singleton feature list that is being registered. void FinalizeInitialization(); // Returns whether the given |feature| is enabled. This is invoked by the // public FeatureList::IsEnabled() static function on the global singleton. // Requires the FeatureList to have already been fully initialized. bool IsFeatureEnabled(const Feature& feature) const; // Returns whether the given |feature| is enabled. This is invoked by the // public FeatureList::GetStateIfOverridden() static function on the global // singleton. Requires the FeatureList to have already been fully initialized. std::optional IsFeatureEnabledIfOverridden( const Feature& feature) const; // Returns the override state of a given |feature|. If the feature was not // overridden, returns OVERRIDE_USE_DEFAULT. Performs any necessary callbacks // for when the feature state has been observed, e.g. activating field trials. OverrideState GetOverrideState(const Feature& feature) const; // Same as GetOverrideState(), but without a default value. OverrideState GetOverrideStateByFeatureName( std::string_view feature_name) const; // Returns the field trial associated with the given |feature|. This is // invoked by the public FeatureList::GetFieldTrial() static function on the // global singleton. Requires the FeatureList to have already been fully // initialized. base::FieldTrial* GetAssociatedFieldTrial(const Feature& feature) const; // For each feature name in comma-separated list of strings |feature_list|, // registers an override with the specified |overridden_state|. Also, will // associate an optional named field trial if the entry is of the format // "FeatureName overrides_; // Locked map that keeps track of seen features, to ensure a single feature is // only defined once. This verification is only done in builds with DCHECKs // enabled. This is mutable as it's not externally visible and needs to be // usable from const getters. mutable Lock feature_identity_tracker_lock_; mutable std::map feature_identity_tracker_ GUARDED_BY(feature_identity_tracker_lock_); // Tracks the associated FieldTrialList for DCHECKs. This is used to catch // the scenario where multiple FieldTrialList are used with the same // FeatureList - which can lead to overrides pointing to invalid FieldTrial // objects. raw_ptr field_trial_list_ = nullptr; // Whether this object has been fully initialized. This gets set to true as a // result of FinalizeInitialization(). bool initialized_ = false; // Whether this object has been initialized from command line. bool initialized_from_command_line_ = false; // Used when querying `base::Feature` state to determine if the cached value // in the `Feature` object is populated and valid. See the comment on // `base::Feature::cached_value` for more details. const uint16_t caching_context_; // If this instance was set with SetEarlyAccessInstance(), this set contains // the names of the features whose state is allowed to be checked. Attempting // to check the state of a feature not on this list will behave as if no // feature list was initialized at all. base::flat_set allowed_feature_names_; }; } // namespace base #endif // BASE_FEATURE_LIST_H_