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.
4
5 #include "base/feature_list.h"
6
7 #include <stddef.h>
8
9 #include <utility>
10 #include <vector>
11
12 #include "base/logging.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/pickle.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18
19 namespace base {
20
21 namespace {
22
23 // Pointer to the FeatureList instance singleton that was set via
24 // FeatureList::SetInstance(). Does not use base/memory/singleton.h in order to
25 // have more control over initialization timing. Leaky.
26 FeatureList* g_instance = nullptr;
27
28 // Tracks whether the FeatureList instance was initialized via an accessor.
29 bool g_initialized_from_accessor = false;
30
31 // An allocator entry for a feature in shared memory. The FeatureEntry is
32 // followed by a base::Pickle object that contains the feature and trial name.
33 struct FeatureEntry {
34 // SHA1(FeatureEntry): Increment this if structure changes!
35 static constexpr uint32_t kPersistentTypeId = 0x06567CA6 + 1;
36
37 // Expected size for 32/64-bit check.
38 static constexpr size_t kExpectedInstanceSize = 8;
39
40 // Specifies whether a feature override enables or disables the feature. Same
41 // values as the OverrideState enum in feature_list.h
42 uint32_t override_state;
43
44 // Size of the pickled structure, NOT the total size of this entry.
45 uint32_t pickle_size;
46
47 // Reads the feature and trial name from the pickle. Calling this is only
48 // valid on an initialized entry that's in shared memory.
GetFeatureAndTrialNamebase::__anond56830970111::FeatureEntry49 bool GetFeatureAndTrialName(StringPiece* feature_name,
50 StringPiece* trial_name) const {
51 const char* src =
52 reinterpret_cast<const char*>(this) + sizeof(FeatureEntry);
53
54 Pickle pickle(src, pickle_size);
55 PickleIterator pickle_iter(pickle);
56
57 if (!pickle_iter.ReadStringPiece(feature_name))
58 return false;
59
60 // Return true because we are not guaranteed to have a trial name anyways.
61 auto sink = pickle_iter.ReadStringPiece(trial_name);
62 ALLOW_UNUSED_LOCAL(sink);
63 return true;
64 }
65 };
66
67 // Some characters are not allowed to appear in feature names or the associated
68 // field trial names, as they are used as special characters for command-line
69 // serialization. This function checks that the strings are ASCII (since they
70 // are used in command-line API functions that require ASCII) and whether there
71 // are any reserved characters present, returning true if the string is valid.
72 // Only called in DCHECKs.
IsValidFeatureOrFieldTrialName(const std::string & name)73 bool IsValidFeatureOrFieldTrialName(const std::string& name) {
74 return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos;
75 }
76
77 } // namespace
78
FeatureList()79 FeatureList::FeatureList() {}
80
~FeatureList()81 FeatureList::~FeatureList() {}
82
InitializeFromCommandLine(const std::string & enable_features,const std::string & disable_features)83 void FeatureList::InitializeFromCommandLine(
84 const std::string& enable_features,
85 const std::string& disable_features) {
86 DCHECK(!initialized_);
87
88 // Process disabled features first, so that disabled ones take precedence over
89 // enabled ones (since RegisterOverride() uses insert()).
90 RegisterOverridesFromCommandLine(disable_features, OVERRIDE_DISABLE_FEATURE);
91 RegisterOverridesFromCommandLine(enable_features, OVERRIDE_ENABLE_FEATURE);
92
93 initialized_from_command_line_ = true;
94 }
95
InitializeFromSharedMemory(PersistentMemoryAllocator * allocator)96 void FeatureList::InitializeFromSharedMemory(
97 PersistentMemoryAllocator* allocator) {
98 DCHECK(!initialized_);
99
100 PersistentMemoryAllocator::Iterator iter(allocator);
101 const FeatureEntry* entry;
102 while ((entry = iter.GetNextOfObject<FeatureEntry>()) != nullptr) {
103 OverrideState override_state =
104 static_cast<OverrideState>(entry->override_state);
105
106 StringPiece feature_name;
107 StringPiece trial_name;
108 if (!entry->GetFeatureAndTrialName(&feature_name, &trial_name))
109 continue;
110
111 FieldTrial* trial = FieldTrialList::Find(trial_name.as_string());
112 RegisterOverride(feature_name, override_state, trial);
113 }
114 }
115
IsFeatureOverriddenFromCommandLine(const std::string & feature_name,OverrideState state) const116 bool FeatureList::IsFeatureOverriddenFromCommandLine(
117 const std::string& feature_name,
118 OverrideState state) const {
119 auto it = overrides_.find(feature_name);
120 return it != overrides_.end() && it->second.overridden_state == state &&
121 !it->second.overridden_by_field_trial;
122 }
123
AssociateReportingFieldTrial(const std::string & feature_name,OverrideState for_overridden_state,FieldTrial * field_trial)124 void FeatureList::AssociateReportingFieldTrial(
125 const std::string& feature_name,
126 OverrideState for_overridden_state,
127 FieldTrial* field_trial) {
128 DCHECK(
129 IsFeatureOverriddenFromCommandLine(feature_name, for_overridden_state));
130
131 // Only one associated field trial is supported per feature. This is generally
132 // enforced server-side.
133 OverrideEntry* entry = &overrides_.find(feature_name)->second;
134 if (entry->field_trial) {
135 NOTREACHED() << "Feature " << feature_name
136 << " already has trial: " << entry->field_trial->trial_name()
137 << ", associating trial: " << field_trial->trial_name();
138 return;
139 }
140
141 entry->field_trial = field_trial;
142 }
143
RegisterFieldTrialOverride(const std::string & feature_name,OverrideState override_state,FieldTrial * field_trial)144 void FeatureList::RegisterFieldTrialOverride(const std::string& feature_name,
145 OverrideState override_state,
146 FieldTrial* field_trial) {
147 DCHECK(field_trial);
148 DCHECK(!ContainsKey(overrides_, feature_name) ||
149 !overrides_.find(feature_name)->second.field_trial)
150 << "Feature " << feature_name
151 << " has conflicting field trial overrides: "
152 << overrides_.find(feature_name)->second.field_trial->trial_name()
153 << " / " << field_trial->trial_name();
154
155 RegisterOverride(feature_name, override_state, field_trial);
156 }
157
AddFeaturesToAllocator(PersistentMemoryAllocator * allocator)158 void FeatureList::AddFeaturesToAllocator(PersistentMemoryAllocator* allocator) {
159 DCHECK(initialized_);
160
161 for (const auto& override : overrides_) {
162 Pickle pickle;
163 pickle.WriteString(override.first);
164 if (override.second.field_trial)
165 pickle.WriteString(override.second.field_trial->trial_name());
166
167 size_t total_size = sizeof(FeatureEntry) + pickle.size();
168 FeatureEntry* entry = allocator->New<FeatureEntry>(total_size);
169 if (!entry)
170 return;
171
172 entry->override_state = override.second.overridden_state;
173 entry->pickle_size = pickle.size();
174
175 char* dst = reinterpret_cast<char*>(entry) + sizeof(FeatureEntry);
176 memcpy(dst, pickle.data(), pickle.size());
177
178 allocator->MakeIterable(entry);
179 }
180 }
181
GetFeatureOverrides(std::string * enable_overrides,std::string * disable_overrides)182 void FeatureList::GetFeatureOverrides(std::string* enable_overrides,
183 std::string* disable_overrides) {
184 DCHECK(initialized_);
185
186 enable_overrides->clear();
187 disable_overrides->clear();
188
189 // Note: Since |overrides_| is a std::map, iteration will be in alphabetical
190 // order. This not guaranteed to users of this function, but is useful for
191 // tests to assume the order.
192 for (const auto& entry : overrides_) {
193 std::string* target_list = nullptr;
194 switch (entry.second.overridden_state) {
195 case OVERRIDE_USE_DEFAULT:
196 case OVERRIDE_ENABLE_FEATURE:
197 target_list = enable_overrides;
198 break;
199 case OVERRIDE_DISABLE_FEATURE:
200 target_list = disable_overrides;
201 break;
202 }
203
204 if (!target_list->empty())
205 target_list->push_back(',');
206 if (entry.second.overridden_state == OVERRIDE_USE_DEFAULT)
207 target_list->push_back('*');
208 target_list->append(entry.first);
209 if (entry.second.field_trial) {
210 target_list->push_back('<');
211 target_list->append(entry.second.field_trial->trial_name());
212 }
213 }
214 }
215
216 // static
IsEnabled(const Feature & feature)217 bool FeatureList::IsEnabled(const Feature& feature) {
218 if (!g_instance) {
219 g_initialized_from_accessor = true;
220 return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
221 }
222 return g_instance->IsFeatureEnabled(feature);
223 }
224
225 // static
GetFieldTrial(const Feature & feature)226 FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
227 return GetInstance()->GetAssociatedFieldTrial(feature);
228 }
229
230 // static
SplitFeatureListString(base::StringPiece input)231 std::vector<base::StringPiece> FeatureList::SplitFeatureListString(
232 base::StringPiece input) {
233 return SplitStringPiece(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
234 }
235
236 // static
InitializeInstance(const std::string & enable_features,const std::string & disable_features)237 bool FeatureList::InitializeInstance(const std::string& enable_features,
238 const std::string& disable_features) {
239 // We want to initialize a new instance here to support command-line features
240 // in testing better. For example, we initialize a dummy instance in
241 // base/test/test_suite.cc, and override it in content/browser/
242 // browser_main_loop.cc.
243 // On the other hand, we want to avoid re-initialization from command line.
244 // For example, we initialize an instance in chrome/browser/
245 // chrome_browser_main.cc and do not override it in content/browser/
246 // browser_main_loop.cc.
247 // If the singleton was previously initialized from within an accessor, we
248 // want to prevent callers from reinitializing the singleton and masking the
249 // accessor call(s) which likely returned incorrect information.
250 CHECK(!g_initialized_from_accessor);
251 bool instance_existed_before = false;
252 if (g_instance) {
253 if (g_instance->initialized_from_command_line_)
254 return false;
255
256 delete g_instance;
257 g_instance = nullptr;
258 instance_existed_before = true;
259 }
260
261 std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
262 feature_list->InitializeFromCommandLine(enable_features, disable_features);
263 base::FeatureList::SetInstance(std::move(feature_list));
264 return !instance_existed_before;
265 }
266
267 // static
GetInstance()268 FeatureList* FeatureList::GetInstance() {
269 return g_instance;
270 }
271
272 // static
SetInstance(std::unique_ptr<FeatureList> instance)273 void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) {
274 DCHECK(!g_instance);
275 instance->FinalizeInitialization();
276
277 // Note: Intentional leak of global singleton.
278 g_instance = instance.release();
279 }
280
281 // static
ClearInstanceForTesting()282 std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() {
283 FeatureList* old_instance = g_instance;
284 g_instance = nullptr;
285 g_initialized_from_accessor = false;
286 return base::WrapUnique(old_instance);
287 }
288
289 // static
RestoreInstanceForTesting(std::unique_ptr<FeatureList> instance)290 void FeatureList::RestoreInstanceForTesting(
291 std::unique_ptr<FeatureList> instance) {
292 DCHECK(!g_instance);
293 // Note: Intentional leak of global singleton.
294 g_instance = instance.release();
295 }
296
FinalizeInitialization()297 void FeatureList::FinalizeInitialization() {
298 DCHECK(!initialized_);
299 initialized_ = true;
300 }
301
IsFeatureEnabled(const Feature & feature)302 bool FeatureList::IsFeatureEnabled(const Feature& feature) {
303 DCHECK(initialized_);
304 DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
305 DCHECK(CheckFeatureIdentity(feature)) << feature.name;
306
307 auto it = overrides_.find(feature.name);
308 if (it != overrides_.end()) {
309 const OverrideEntry& entry = it->second;
310
311 // Activate the corresponding field trial, if necessary.
312 if (entry.field_trial)
313 entry.field_trial->group();
314
315 // TODO(asvitkine) Expand this section as more support is added.
316
317 // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below.
318 if (entry.overridden_state != OVERRIDE_USE_DEFAULT)
319 return entry.overridden_state == OVERRIDE_ENABLE_FEATURE;
320 }
321 // Otherwise, return the default state.
322 return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
323 }
324
GetAssociatedFieldTrial(const Feature & feature)325 FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) {
326 DCHECK(initialized_);
327 DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
328 DCHECK(CheckFeatureIdentity(feature)) << feature.name;
329
330 auto it = overrides_.find(feature.name);
331 if (it != overrides_.end()) {
332 const OverrideEntry& entry = it->second;
333 return entry.field_trial;
334 }
335
336 return nullptr;
337 }
338
RegisterOverridesFromCommandLine(const std::string & feature_list,OverrideState overridden_state)339 void FeatureList::RegisterOverridesFromCommandLine(
340 const std::string& feature_list,
341 OverrideState overridden_state) {
342 for (const auto& value : SplitFeatureListString(feature_list)) {
343 StringPiece feature_name = value;
344 base::FieldTrial* trial = nullptr;
345
346 // The entry may be of the form FeatureName<FieldTrialName - in which case,
347 // this splits off the field trial name and associates it with the override.
348 std::string::size_type pos = feature_name.find('<');
349 if (pos != std::string::npos) {
350 feature_name.set(value.data(), pos);
351 trial = base::FieldTrialList::Find(value.substr(pos + 1).as_string());
352 }
353
354 RegisterOverride(feature_name, overridden_state, trial);
355 }
356 }
357
RegisterOverride(StringPiece feature_name,OverrideState overridden_state,FieldTrial * field_trial)358 void FeatureList::RegisterOverride(StringPiece feature_name,
359 OverrideState overridden_state,
360 FieldTrial* field_trial) {
361 DCHECK(!initialized_);
362 if (field_trial) {
363 DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name()))
364 << field_trial->trial_name();
365 }
366 if (feature_name.starts_with("*")) {
367 feature_name = feature_name.substr(1);
368 overridden_state = OVERRIDE_USE_DEFAULT;
369 }
370
371 // Note: The semantics of insert() is that it does not overwrite the entry if
372 // one already exists for the key. Thus, only the first override for a given
373 // feature name takes effect.
374 overrides_.insert(std::make_pair(
375 feature_name.as_string(), OverrideEntry(overridden_state, field_trial)));
376 }
377
CheckFeatureIdentity(const Feature & feature)378 bool FeatureList::CheckFeatureIdentity(const Feature& feature) {
379 AutoLock auto_lock(feature_identity_tracker_lock_);
380
381 auto it = feature_identity_tracker_.find(feature.name);
382 if (it == feature_identity_tracker_.end()) {
383 // If it's not tracked yet, register it.
384 feature_identity_tracker_[feature.name] = &feature;
385 return true;
386 }
387 // Compare address of |feature| to the existing tracked entry.
388 return it->second == &feature;
389 }
390
OverrideEntry(OverrideState overridden_state,FieldTrial * field_trial)391 FeatureList::OverrideEntry::OverrideEntry(OverrideState overridden_state,
392 FieldTrial* field_trial)
393 : overridden_state(overridden_state),
394 field_trial(field_trial),
395 overridden_by_field_trial(field_trial != nullptr) {}
396
397 } // namespace base
398