1 // Copyright (c) 2012 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/metrics/field_trial.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/base_switches.h"
11 #include "base/build_time.h"
12 #include "base/command_line.h"
13 #include "base/debug/activity_tracker.h"
14 #include "base/logging.h"
15 #include "base/metrics/field_trial_param_associator.h"
16 #include "base/process/memory.h"
17 #include "base/process/process_handle.h"
18 #include "base/process/process_info.h"
19 #include "base/rand_util.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/unguessable_token.h"
26
27 // On POSIX, the fd is shared using the mapping in GlobalDescriptors.
28 #if defined(OS_POSIX) && !defined(OS_NACL)
29 #include "base/posix/global_descriptors.h"
30 #endif
31
32 namespace base {
33
34 namespace {
35
36 // Define a separator character to use when creating a persistent form of an
37 // instance. This is intended for use as a command line argument, passed to a
38 // second process to mimic our state (i.e., provide the same group name).
39 const char kPersistentStringSeparator = '/'; // Currently a slash.
40
41 // Define a marker character to be used as a prefix to a trial name on the
42 // command line which forces its activation.
43 const char kActivationMarker = '*';
44
45 // Use shared memory to communicate field trial (experiment) state. Set to false
46 // for now while the implementation is fleshed out (e.g. data format, single
47 // shared memory segment). See https://codereview.chromium.org/2365273004/ and
48 // crbug.com/653874
49 // The browser is the only process that has write access to the shared memory.
50 // This is safe from race conditions because MakeIterable is a release operation
51 // and GetNextOfType is an acquire operation, so memory writes before
52 // MakeIterable happen before memory reads after GetNextOfType.
53 #if defined(OS_FUCHSIA) // TODO(752368): Not yet supported on Fuchsia.
54 const bool kUseSharedMemoryForFieldTrials = false;
55 #else
56 const bool kUseSharedMemoryForFieldTrials = true;
57 #endif
58
59 // Constants for the field trial allocator.
60 const char kAllocatorName[] = "FieldTrialAllocator";
61
62 // We allocate 128 KiB to hold all the field trial data. This should be enough,
63 // as most people use 3 - 25 KiB for field trials (as of 11/25/2016).
64 // This also doesn't allocate all 128 KiB at once -- the pages only get mapped
65 // to physical memory when they are touched. If the size of the allocated field
66 // trials does get larger than 128 KiB, then we will drop some field trials in
67 // child processes, leading to an inconsistent view between browser and child
68 // processes and possibly causing crashes (see crbug.com/661617).
69 const size_t kFieldTrialAllocationSize = 128 << 10; // 128 KiB
70
71 // Writes out string1 and then string2 to pickle.
WriteStringPair(Pickle * pickle,const StringPiece & string1,const StringPiece & string2)72 void WriteStringPair(Pickle* pickle,
73 const StringPiece& string1,
74 const StringPiece& string2) {
75 pickle->WriteString(string1);
76 pickle->WriteString(string2);
77 }
78
79 // Writes out the field trial's contents (via trial_state) to the pickle. The
80 // format of the pickle looks like:
81 // TrialName, GroupName, ParamKey1, ParamValue1, ParamKey2, ParamValue2, ...
82 // If there are no parameters, then it just ends at GroupName.
PickleFieldTrial(const FieldTrial::State & trial_state,Pickle * pickle)83 void PickleFieldTrial(const FieldTrial::State& trial_state, Pickle* pickle) {
84 WriteStringPair(pickle, *trial_state.trial_name, *trial_state.group_name);
85
86 // Get field trial params.
87 std::map<std::string, std::string> params;
88 FieldTrialParamAssociator::GetInstance()->GetFieldTrialParamsWithoutFallback(
89 *trial_state.trial_name, *trial_state.group_name, ¶ms);
90
91 // Write params to pickle.
92 for (const auto& param : params)
93 WriteStringPair(pickle, param.first, param.second);
94 }
95
96 // Created a time value based on |year|, |month| and |day_of_month| parameters.
CreateTimeFromParams(int year,int month,int day_of_month)97 Time CreateTimeFromParams(int year, int month, int day_of_month) {
98 DCHECK_GT(year, 1970);
99 DCHECK_GT(month, 0);
100 DCHECK_LT(month, 13);
101 DCHECK_GT(day_of_month, 0);
102 DCHECK_LT(day_of_month, 32);
103
104 Time::Exploded exploded;
105 exploded.year = year;
106 exploded.month = month;
107 exploded.day_of_week = 0; // Should be unused.
108 exploded.day_of_month = day_of_month;
109 exploded.hour = 0;
110 exploded.minute = 0;
111 exploded.second = 0;
112 exploded.millisecond = 0;
113 Time out_time;
114 if (!Time::FromLocalExploded(exploded, &out_time)) {
115 // TODO(maksims): implement failure handling.
116 // We might just return |out_time|, which is Time(0).
117 NOTIMPLEMENTED();
118 }
119
120 return out_time;
121 }
122
123 // Returns the boundary value for comparing against the FieldTrial's added
124 // groups for a given |divisor| (total probability) and |entropy_value|.
GetGroupBoundaryValue(FieldTrial::Probability divisor,double entropy_value)125 FieldTrial::Probability GetGroupBoundaryValue(
126 FieldTrial::Probability divisor,
127 double entropy_value) {
128 // Add a tiny epsilon value to get consistent results when converting floating
129 // points to int. Without it, boundary values have inconsistent results, e.g.:
130 //
131 // static_cast<FieldTrial::Probability>(100 * 0.56) == 56
132 // static_cast<FieldTrial::Probability>(100 * 0.57) == 56
133 // static_cast<FieldTrial::Probability>(100 * 0.58) == 57
134 // static_cast<FieldTrial::Probability>(100 * 0.59) == 59
135 const double kEpsilon = 1e-8;
136 const FieldTrial::Probability result =
137 static_cast<FieldTrial::Probability>(divisor * entropy_value + kEpsilon);
138 // Ensure that adding the epsilon still results in a value < |divisor|.
139 return std::min(result, divisor - 1);
140 }
141
142 // Separate type from FieldTrial::State so that it can use StringPieces.
143 struct FieldTrialStringEntry {
144 StringPiece trial_name;
145 StringPiece group_name;
146 bool activated = false;
147 };
148
149 // Parses the --force-fieldtrials string |trials_string| into |entries|.
150 // Returns true if the string was parsed correctly. On failure, the |entries|
151 // array may end up being partially filled.
ParseFieldTrialsString(const std::string & trials_string,std::vector<FieldTrialStringEntry> * entries)152 bool ParseFieldTrialsString(const std::string& trials_string,
153 std::vector<FieldTrialStringEntry>* entries) {
154 const StringPiece trials_string_piece(trials_string);
155
156 size_t next_item = 0;
157 while (next_item < trials_string.length()) {
158 size_t name_end = trials_string.find(kPersistentStringSeparator, next_item);
159 if (name_end == trials_string.npos || next_item == name_end)
160 return false;
161 size_t group_name_end =
162 trials_string.find(kPersistentStringSeparator, name_end + 1);
163 if (name_end + 1 == group_name_end)
164 return false;
165 if (group_name_end == trials_string.npos)
166 group_name_end = trials_string.length();
167
168 FieldTrialStringEntry entry;
169 // Verify if the trial should be activated or not.
170 if (trials_string[next_item] == kActivationMarker) {
171 // Name cannot be only the indicator.
172 if (name_end - next_item == 1)
173 return false;
174 next_item++;
175 entry.activated = true;
176 }
177 entry.trial_name =
178 trials_string_piece.substr(next_item, name_end - next_item);
179 entry.group_name =
180 trials_string_piece.substr(name_end + 1, group_name_end - name_end - 1);
181 next_item = group_name_end + 1;
182
183 entries->push_back(std::move(entry));
184 }
185 return true;
186 }
187
AddFeatureAndFieldTrialFlags(const char * enable_features_switch,const char * disable_features_switch,CommandLine * cmd_line)188 void AddFeatureAndFieldTrialFlags(const char* enable_features_switch,
189 const char* disable_features_switch,
190 CommandLine* cmd_line) {
191 std::string enabled_features;
192 std::string disabled_features;
193 FeatureList::GetInstance()->GetFeatureOverrides(&enabled_features,
194 &disabled_features);
195
196 if (!enabled_features.empty())
197 cmd_line->AppendSwitchASCII(enable_features_switch, enabled_features);
198 if (!disabled_features.empty())
199 cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features);
200
201 std::string field_trial_states;
202 FieldTrialList::AllStatesToString(&field_trial_states, false);
203 if (!field_trial_states.empty()) {
204 cmd_line->AppendSwitchASCII(switches::kForceFieldTrials,
205 field_trial_states);
206 }
207 }
208
OnOutOfMemory(size_t size)209 void OnOutOfMemory(size_t size) {
210 #if defined(OS_NACL)
211 NOTREACHED();
212 #else
213 TerminateBecauseOutOfMemory(size);
214 #endif
215 }
216
217 #if !defined(OS_NACL)
218 // Returns whether the operation succeeded.
DeserializeGUIDFromStringPieces(base::StringPiece first,base::StringPiece second,base::UnguessableToken * guid)219 bool DeserializeGUIDFromStringPieces(base::StringPiece first,
220 base::StringPiece second,
221 base::UnguessableToken* guid) {
222 uint64_t high = 0;
223 uint64_t low = 0;
224 if (!base::StringToUint64(first, &high) ||
225 !base::StringToUint64(second, &low)) {
226 return false;
227 }
228
229 *guid = base::UnguessableToken::Deserialize(high, low);
230 return true;
231 }
232
233 // Extract a read-only SharedMemoryHandle from an existing |shared_memory|
234 // handle. Note that on Android, this also makes the whole region read-only.
GetSharedMemoryReadOnlyHandle(SharedMemory * shared_memory)235 SharedMemoryHandle GetSharedMemoryReadOnlyHandle(SharedMemory* shared_memory) {
236 SharedMemoryHandle result = shared_memory->GetReadOnlyHandle();
237 #if defined(OS_ANDROID)
238 // On Android, turn the region read-only. This prevents any future
239 // writable mapping attempts, but the original one in |shm| survives
240 // and is still usable in the current process.
241 result.SetRegionReadOnly();
242 #endif // OS_ANDROID
243 return result;
244 }
245 #endif // !OS_NACL
246
247 } // namespace
248
249 // statics
250 const int FieldTrial::kNotFinalized = -1;
251 const int FieldTrial::kDefaultGroupNumber = 0;
252 bool FieldTrial::enable_benchmarking_ = false;
253
254 int FieldTrialList::kNoExpirationYear = 0;
255
256 //------------------------------------------------------------------------------
257 // FieldTrial methods and members.
258
259 FieldTrial::EntropyProvider::~EntropyProvider() = default;
260
261 FieldTrial::State::State() = default;
262
263 FieldTrial::State::State(const State& other) = default;
264
265 FieldTrial::State::~State() = default;
266
GetTrialAndGroupName(StringPiece * trial_name,StringPiece * group_name) const267 bool FieldTrial::FieldTrialEntry::GetTrialAndGroupName(
268 StringPiece* trial_name,
269 StringPiece* group_name) const {
270 PickleIterator iter = GetPickleIterator();
271 return ReadStringPair(&iter, trial_name, group_name);
272 }
273
GetParams(std::map<std::string,std::string> * params) const274 bool FieldTrial::FieldTrialEntry::GetParams(
275 std::map<std::string, std::string>* params) const {
276 PickleIterator iter = GetPickleIterator();
277 StringPiece tmp;
278 // Skip reading trial and group name.
279 if (!ReadStringPair(&iter, &tmp, &tmp))
280 return false;
281
282 while (true) {
283 StringPiece key;
284 StringPiece value;
285 if (!ReadStringPair(&iter, &key, &value))
286 return key.empty(); // Non-empty is bad: got one of a pair.
287 (*params)[key.as_string()] = value.as_string();
288 }
289 }
290
GetPickleIterator() const291 PickleIterator FieldTrial::FieldTrialEntry::GetPickleIterator() const {
292 const char* src =
293 reinterpret_cast<const char*>(this) + sizeof(FieldTrialEntry);
294
295 Pickle pickle(src, pickle_size);
296 return PickleIterator(pickle);
297 }
298
ReadStringPair(PickleIterator * iter,StringPiece * trial_name,StringPiece * group_name) const299 bool FieldTrial::FieldTrialEntry::ReadStringPair(
300 PickleIterator* iter,
301 StringPiece* trial_name,
302 StringPiece* group_name) const {
303 if (!iter->ReadStringPiece(trial_name))
304 return false;
305 if (!iter->ReadStringPiece(group_name))
306 return false;
307 return true;
308 }
309
Disable()310 void FieldTrial::Disable() {
311 DCHECK(!group_reported_);
312 enable_field_trial_ = false;
313
314 // In case we are disabled after initialization, we need to switch
315 // the trial to the default group.
316 if (group_ != kNotFinalized) {
317 // Only reset when not already the default group, because in case we were
318 // forced to the default group, the group number may not be
319 // kDefaultGroupNumber, so we should keep it as is.
320 if (group_name_ != default_group_name_)
321 SetGroupChoice(default_group_name_, kDefaultGroupNumber);
322 }
323 }
324
AppendGroup(const std::string & name,Probability group_probability)325 int FieldTrial::AppendGroup(const std::string& name,
326 Probability group_probability) {
327 // When the group choice was previously forced, we only need to return the
328 // the id of the chosen group, and anything can be returned for the others.
329 if (forced_) {
330 DCHECK(!group_name_.empty());
331 if (name == group_name_) {
332 // Note that while |group_| may be equal to |kDefaultGroupNumber| on the
333 // forced trial, it will not have the same value as the default group
334 // number returned from the non-forced |FactoryGetFieldTrial()| call,
335 // which takes care to ensure that this does not happen.
336 return group_;
337 }
338 DCHECK_NE(next_group_number_, group_);
339 // We still return different numbers each time, in case some caller need
340 // them to be different.
341 return next_group_number_++;
342 }
343
344 DCHECK_LE(group_probability, divisor_);
345 DCHECK_GE(group_probability, 0);
346
347 if (enable_benchmarking_ || !enable_field_trial_)
348 group_probability = 0;
349
350 accumulated_group_probability_ += group_probability;
351
352 DCHECK_LE(accumulated_group_probability_, divisor_);
353 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) {
354 // This is the group that crossed the random line, so we do the assignment.
355 SetGroupChoice(name, next_group_number_);
356 }
357 return next_group_number_++;
358 }
359
group()360 int FieldTrial::group() {
361 FinalizeGroupChoice();
362 if (trial_registered_)
363 FieldTrialList::NotifyFieldTrialGroupSelection(this);
364 return group_;
365 }
366
group_name()367 const std::string& FieldTrial::group_name() {
368 // Call |group()| to ensure group gets assigned and observers are notified.
369 group();
370 DCHECK(!group_name_.empty());
371 return group_name_;
372 }
373
GetGroupNameWithoutActivation()374 const std::string& FieldTrial::GetGroupNameWithoutActivation() {
375 FinalizeGroupChoice();
376 return group_name_;
377 }
378
SetForced()379 void FieldTrial::SetForced() {
380 // We might have been forced before (e.g., by CreateFieldTrial) and it's
381 // first come first served, e.g., command line switch has precedence.
382 if (forced_)
383 return;
384
385 // And we must finalize the group choice before we mark ourselves as forced.
386 FinalizeGroupChoice();
387 forced_ = true;
388 }
389
390 // static
EnableBenchmarking()391 void FieldTrial::EnableBenchmarking() {
392 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
393 enable_benchmarking_ = true;
394 }
395
396 // static
CreateSimulatedFieldTrial(const std::string & trial_name,Probability total_probability,const std::string & default_group_name,double entropy_value)397 FieldTrial* FieldTrial::CreateSimulatedFieldTrial(
398 const std::string& trial_name,
399 Probability total_probability,
400 const std::string& default_group_name,
401 double entropy_value) {
402 return new FieldTrial(trial_name, total_probability, default_group_name,
403 entropy_value);
404 }
405
FieldTrial(const std::string & trial_name,const Probability total_probability,const std::string & default_group_name,double entropy_value)406 FieldTrial::FieldTrial(const std::string& trial_name,
407 const Probability total_probability,
408 const std::string& default_group_name,
409 double entropy_value)
410 : trial_name_(trial_name),
411 divisor_(total_probability),
412 default_group_name_(default_group_name),
413 random_(GetGroupBoundaryValue(total_probability, entropy_value)),
414 accumulated_group_probability_(0),
415 next_group_number_(kDefaultGroupNumber + 1),
416 group_(kNotFinalized),
417 enable_field_trial_(true),
418 forced_(false),
419 group_reported_(false),
420 trial_registered_(false),
421 ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull) {
422 DCHECK_GT(total_probability, 0);
423 DCHECK(!trial_name_.empty());
424 DCHECK(!default_group_name_.empty())
425 << "Trial " << trial_name << " is missing a default group name.";
426 }
427
428 FieldTrial::~FieldTrial() = default;
429
SetTrialRegistered()430 void FieldTrial::SetTrialRegistered() {
431 DCHECK_EQ(kNotFinalized, group_);
432 DCHECK(!trial_registered_);
433 trial_registered_ = true;
434 }
435
SetGroupChoice(const std::string & group_name,int number)436 void FieldTrial::SetGroupChoice(const std::string& group_name, int number) {
437 group_ = number;
438 if (group_name.empty())
439 StringAppendF(&group_name_, "%d", group_);
440 else
441 group_name_ = group_name;
442 DVLOG(1) << "Field trial: " << trial_name_ << " Group choice:" << group_name_;
443 }
444
FinalizeGroupChoice()445 void FieldTrial::FinalizeGroupChoice() {
446 FinalizeGroupChoiceImpl(false);
447 }
448
FinalizeGroupChoiceImpl(bool is_locked)449 void FieldTrial::FinalizeGroupChoiceImpl(bool is_locked) {
450 if (group_ != kNotFinalized)
451 return;
452 accumulated_group_probability_ = divisor_;
453 // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not
454 // finalized.
455 DCHECK(!forced_);
456 SetGroupChoice(default_group_name_, kDefaultGroupNumber);
457
458 // Add the field trial to shared memory.
459 if (kUseSharedMemoryForFieldTrials && trial_registered_)
460 FieldTrialList::OnGroupFinalized(is_locked, this);
461 }
462
GetActiveGroup(ActiveGroup * active_group) const463 bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const {
464 if (!group_reported_ || !enable_field_trial_)
465 return false;
466 DCHECK_NE(group_, kNotFinalized);
467 active_group->trial_name = trial_name_;
468 active_group->group_name = group_name_;
469 return true;
470 }
471
GetStateWhileLocked(State * field_trial_state,bool include_expired)472 bool FieldTrial::GetStateWhileLocked(State* field_trial_state,
473 bool include_expired) {
474 if (!include_expired && !enable_field_trial_)
475 return false;
476 FinalizeGroupChoiceImpl(true);
477 field_trial_state->trial_name = &trial_name_;
478 field_trial_state->group_name = &group_name_;
479 field_trial_state->activated = group_reported_;
480 return true;
481 }
482
483 //------------------------------------------------------------------------------
484 // FieldTrialList methods and members.
485
486 // static
487 FieldTrialList* FieldTrialList::global_ = nullptr;
488
489 // static
490 bool FieldTrialList::used_without_global_ = false;
491
492 FieldTrialList::Observer::~Observer() = default;
493
FieldTrialList(std::unique_ptr<const FieldTrial::EntropyProvider> entropy_provider)494 FieldTrialList::FieldTrialList(
495 std::unique_ptr<const FieldTrial::EntropyProvider> entropy_provider)
496 : entropy_provider_(std::move(entropy_provider)),
497 observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>(
498 ObserverListPolicy::EXISTING_ONLY)) {
499 DCHECK(!global_);
500 DCHECK(!used_without_global_);
501 global_ = this;
502
503 Time two_years_from_build_time = GetBuildTime() + TimeDelta::FromDays(730);
504 Time::Exploded exploded;
505 two_years_from_build_time.LocalExplode(&exploded);
506 kNoExpirationYear = exploded.year;
507 }
508
~FieldTrialList()509 FieldTrialList::~FieldTrialList() {
510 AutoLock auto_lock(lock_);
511 while (!registered_.empty()) {
512 RegistrationMap::iterator it = registered_.begin();
513 it->second->Release();
514 registered_.erase(it->first);
515 }
516 DCHECK_EQ(this, global_);
517 global_ = nullptr;
518 }
519
520 // static
FactoryGetFieldTrial(const std::string & trial_name,FieldTrial::Probability total_probability,const std::string & default_group_name,const int year,const int month,const int day_of_month,FieldTrial::RandomizationType randomization_type,int * default_group_number)521 FieldTrial* FieldTrialList::FactoryGetFieldTrial(
522 const std::string& trial_name,
523 FieldTrial::Probability total_probability,
524 const std::string& default_group_name,
525 const int year,
526 const int month,
527 const int day_of_month,
528 FieldTrial::RandomizationType randomization_type,
529 int* default_group_number) {
530 return FactoryGetFieldTrialWithRandomizationSeed(
531 trial_name, total_probability, default_group_name, year, month,
532 day_of_month, randomization_type, 0, default_group_number, nullptr);
533 }
534
535 // static
FactoryGetFieldTrialWithRandomizationSeed(const std::string & trial_name,FieldTrial::Probability total_probability,const std::string & default_group_name,const int year,const int month,const int day_of_month,FieldTrial::RandomizationType randomization_type,uint32_t randomization_seed,int * default_group_number,const FieldTrial::EntropyProvider * override_entropy_provider)536 FieldTrial* FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
537 const std::string& trial_name,
538 FieldTrial::Probability total_probability,
539 const std::string& default_group_name,
540 const int year,
541 const int month,
542 const int day_of_month,
543 FieldTrial::RandomizationType randomization_type,
544 uint32_t randomization_seed,
545 int* default_group_number,
546 const FieldTrial::EntropyProvider* override_entropy_provider) {
547 if (default_group_number)
548 *default_group_number = FieldTrial::kDefaultGroupNumber;
549 // Check if the field trial has already been created in some other way.
550 FieldTrial* existing_trial = Find(trial_name);
551 if (existing_trial) {
552 CHECK(existing_trial->forced_);
553 // If the default group name differs between the existing forced trial
554 // and this trial, then use a different value for the default group number.
555 if (default_group_number &&
556 default_group_name != existing_trial->default_group_name()) {
557 // If the new default group number corresponds to the group that was
558 // chosen for the forced trial (which has been finalized when it was
559 // forced), then set the default group number to that.
560 if (default_group_name == existing_trial->group_name_internal()) {
561 *default_group_number = existing_trial->group_;
562 } else {
563 // Otherwise, use |kNonConflictingGroupNumber| (-2) for the default
564 // group number, so that it does not conflict with the |AppendGroup()|
565 // result for the chosen group.
566 const int kNonConflictingGroupNumber = -2;
567 static_assert(
568 kNonConflictingGroupNumber != FieldTrial::kDefaultGroupNumber,
569 "The 'non-conflicting' group number conflicts");
570 static_assert(kNonConflictingGroupNumber != FieldTrial::kNotFinalized,
571 "The 'non-conflicting' group number conflicts");
572 *default_group_number = kNonConflictingGroupNumber;
573 }
574 }
575 return existing_trial;
576 }
577
578 double entropy_value;
579 if (randomization_type == FieldTrial::ONE_TIME_RANDOMIZED) {
580 // If an override entropy provider is given, use it.
581 const FieldTrial::EntropyProvider* entropy_provider =
582 override_entropy_provider ? override_entropy_provider
583 : GetEntropyProviderForOneTimeRandomization();
584 CHECK(entropy_provider);
585 entropy_value = entropy_provider->GetEntropyForTrial(trial_name,
586 randomization_seed);
587 } else {
588 DCHECK_EQ(FieldTrial::SESSION_RANDOMIZED, randomization_type);
589 DCHECK_EQ(0U, randomization_seed);
590 entropy_value = RandDouble();
591 }
592
593 FieldTrial* field_trial = new FieldTrial(trial_name, total_probability,
594 default_group_name, entropy_value);
595 if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month))
596 field_trial->Disable();
597 FieldTrialList::Register(field_trial);
598 return field_trial;
599 }
600
601 // static
Find(const std::string & trial_name)602 FieldTrial* FieldTrialList::Find(const std::string& trial_name) {
603 if (!global_)
604 return nullptr;
605 AutoLock auto_lock(global_->lock_);
606 return global_->PreLockedFind(trial_name);
607 }
608
609 // static
FindValue(const std::string & trial_name)610 int FieldTrialList::FindValue(const std::string& trial_name) {
611 FieldTrial* field_trial = Find(trial_name);
612 if (field_trial)
613 return field_trial->group();
614 return FieldTrial::kNotFinalized;
615 }
616
617 // static
FindFullName(const std::string & trial_name)618 std::string FieldTrialList::FindFullName(const std::string& trial_name) {
619 FieldTrial* field_trial = Find(trial_name);
620 if (field_trial)
621 return field_trial->group_name();
622 return std::string();
623 }
624
625 // static
TrialExists(const std::string & trial_name)626 bool FieldTrialList::TrialExists(const std::string& trial_name) {
627 return Find(trial_name) != nullptr;
628 }
629
630 // static
IsTrialActive(const std::string & trial_name)631 bool FieldTrialList::IsTrialActive(const std::string& trial_name) {
632 FieldTrial* field_trial = Find(trial_name);
633 FieldTrial::ActiveGroup active_group;
634 return field_trial && field_trial->GetActiveGroup(&active_group);
635 }
636
637 // static
StatesToString(std::string * output)638 void FieldTrialList::StatesToString(std::string* output) {
639 FieldTrial::ActiveGroups active_groups;
640 GetActiveFieldTrialGroups(&active_groups);
641 for (FieldTrial::ActiveGroups::const_iterator it = active_groups.begin();
642 it != active_groups.end(); ++it) {
643 DCHECK_EQ(std::string::npos,
644 it->trial_name.find(kPersistentStringSeparator));
645 DCHECK_EQ(std::string::npos,
646 it->group_name.find(kPersistentStringSeparator));
647 output->append(it->trial_name);
648 output->append(1, kPersistentStringSeparator);
649 output->append(it->group_name);
650 output->append(1, kPersistentStringSeparator);
651 }
652 }
653
654 // static
AllStatesToString(std::string * output,bool include_expired)655 void FieldTrialList::AllStatesToString(std::string* output,
656 bool include_expired) {
657 if (!global_)
658 return;
659 AutoLock auto_lock(global_->lock_);
660
661 for (const auto& registered : global_->registered_) {
662 FieldTrial::State trial;
663 if (!registered.second->GetStateWhileLocked(&trial, include_expired))
664 continue;
665 DCHECK_EQ(std::string::npos,
666 trial.trial_name->find(kPersistentStringSeparator));
667 DCHECK_EQ(std::string::npos,
668 trial.group_name->find(kPersistentStringSeparator));
669 if (trial.activated)
670 output->append(1, kActivationMarker);
671 output->append(*trial.trial_name);
672 output->append(1, kPersistentStringSeparator);
673 output->append(*trial.group_name);
674 output->append(1, kPersistentStringSeparator);
675 }
676 }
677
678 // static
AllParamsToString(bool include_expired,EscapeDataFunc encode_data_func)679 std::string FieldTrialList::AllParamsToString(bool include_expired,
680 EscapeDataFunc encode_data_func) {
681 FieldTrialParamAssociator* params_associator =
682 FieldTrialParamAssociator::GetInstance();
683 std::string output;
684 for (const auto& registered : GetRegisteredTrials()) {
685 FieldTrial::State trial;
686 if (!registered.second->GetStateWhileLocked(&trial, include_expired))
687 continue;
688 DCHECK_EQ(std::string::npos,
689 trial.trial_name->find(kPersistentStringSeparator));
690 DCHECK_EQ(std::string::npos,
691 trial.group_name->find(kPersistentStringSeparator));
692 std::map<std::string, std::string> params;
693 if (params_associator->GetFieldTrialParamsWithoutFallback(
694 *trial.trial_name, *trial.group_name, ¶ms)) {
695 if (params.size() > 0) {
696 // Add comma to seprate from previous entry if it exists.
697 if (!output.empty())
698 output.append(1, ',');
699
700 output.append(encode_data_func(*trial.trial_name));
701 output.append(1, '.');
702 output.append(encode_data_func(*trial.group_name));
703 output.append(1, ':');
704
705 std::string param_str;
706 for (const auto& param : params) {
707 // Add separator from previous param information if it exists.
708 if (!param_str.empty())
709 param_str.append(1, kPersistentStringSeparator);
710 param_str.append(encode_data_func(param.first));
711 param_str.append(1, kPersistentStringSeparator);
712 param_str.append(encode_data_func(param.second));
713 }
714
715 output.append(param_str);
716 }
717 }
718 }
719 return output;
720 }
721
722 // static
GetActiveFieldTrialGroups(FieldTrial::ActiveGroups * active_groups)723 void FieldTrialList::GetActiveFieldTrialGroups(
724 FieldTrial::ActiveGroups* active_groups) {
725 DCHECK(active_groups->empty());
726 if (!global_)
727 return;
728 AutoLock auto_lock(global_->lock_);
729
730 for (RegistrationMap::iterator it = global_->registered_.begin();
731 it != global_->registered_.end(); ++it) {
732 FieldTrial::ActiveGroup active_group;
733 if (it->second->GetActiveGroup(&active_group))
734 active_groups->push_back(active_group);
735 }
736 }
737
738 // static
GetActiveFieldTrialGroupsFromString(const std::string & trials_string,FieldTrial::ActiveGroups * active_groups)739 void FieldTrialList::GetActiveFieldTrialGroupsFromString(
740 const std::string& trials_string,
741 FieldTrial::ActiveGroups* active_groups) {
742 std::vector<FieldTrialStringEntry> entries;
743 if (!ParseFieldTrialsString(trials_string, &entries))
744 return;
745
746 for (const auto& entry : entries) {
747 if (entry.activated) {
748 FieldTrial::ActiveGroup group;
749 group.trial_name = entry.trial_name.as_string();
750 group.group_name = entry.group_name.as_string();
751 active_groups->push_back(group);
752 }
753 }
754 }
755
756 // static
GetInitiallyActiveFieldTrials(const base::CommandLine & command_line,FieldTrial::ActiveGroups * active_groups)757 void FieldTrialList::GetInitiallyActiveFieldTrials(
758 const base::CommandLine& command_line,
759 FieldTrial::ActiveGroups* active_groups) {
760 DCHECK(global_);
761 DCHECK(global_->create_trials_from_command_line_called_);
762
763 if (!global_->field_trial_allocator_) {
764 GetActiveFieldTrialGroupsFromString(
765 command_line.GetSwitchValueASCII(switches::kForceFieldTrials),
766 active_groups);
767 return;
768 }
769
770 FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
771 FieldTrialAllocator::Iterator mem_iter(allocator);
772 const FieldTrial::FieldTrialEntry* entry;
773 while ((entry = mem_iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
774 nullptr) {
775 StringPiece trial_name;
776 StringPiece group_name;
777 if (subtle::NoBarrier_Load(&entry->activated) &&
778 entry->GetTrialAndGroupName(&trial_name, &group_name)) {
779 FieldTrial::ActiveGroup group;
780 group.trial_name = trial_name.as_string();
781 group.group_name = group_name.as_string();
782 active_groups->push_back(group);
783 }
784 }
785 }
786
787 // static
CreateTrialsFromString(const std::string & trials_string,const std::set<std::string> & ignored_trial_names)788 bool FieldTrialList::CreateTrialsFromString(
789 const std::string& trials_string,
790 const std::set<std::string>& ignored_trial_names) {
791 DCHECK(global_);
792 if (trials_string.empty() || !global_)
793 return true;
794
795 std::vector<FieldTrialStringEntry> entries;
796 if (!ParseFieldTrialsString(trials_string, &entries))
797 return false;
798
799 for (const auto& entry : entries) {
800 const std::string trial_name = entry.trial_name.as_string();
801 const std::string group_name = entry.group_name.as_string();
802
803 if (ContainsKey(ignored_trial_names, trial_name)) {
804 // This is to warn that the field trial forced through command-line
805 // input is unforcable.
806 // Use --enable-logging or --enable-logging=stderr to see this warning.
807 LOG(WARNING) << "Field trial: " << trial_name << " cannot be forced.";
808 continue;
809 }
810
811 FieldTrial* trial = CreateFieldTrial(trial_name, group_name);
812 if (!trial)
813 return false;
814 if (entry.activated) {
815 // Call |group()| to mark the trial as "used" and notify observers, if
816 // any. This is useful to ensure that field trials created in child
817 // processes are properly reported in crash reports.
818 trial->group();
819 }
820 }
821 return true;
822 }
823
824 // static
CreateTrialsFromCommandLine(const CommandLine & cmd_line,const char * field_trial_handle_switch,int fd_key)825 void FieldTrialList::CreateTrialsFromCommandLine(
826 const CommandLine& cmd_line,
827 const char* field_trial_handle_switch,
828 int fd_key) {
829 global_->create_trials_from_command_line_called_ = true;
830
831 #if defined(OS_WIN) || defined(OS_FUCHSIA)
832 if (cmd_line.HasSwitch(field_trial_handle_switch)) {
833 std::string switch_value =
834 cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
835 bool result = CreateTrialsFromSwitchValue(switch_value);
836 DCHECK(result);
837 }
838 #elif defined(OS_POSIX) && !defined(OS_NACL)
839 // On POSIX, we check if the handle is valid by seeing if the browser process
840 // sent over the switch (we don't care about the value). Invalid handles
841 // occur in some browser tests which don't initialize the allocator.
842 if (cmd_line.HasSwitch(field_trial_handle_switch)) {
843 std::string switch_value =
844 cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
845 bool result = CreateTrialsFromDescriptor(fd_key, switch_value);
846 DCHECK(result);
847 }
848 #endif
849
850 if (cmd_line.HasSwitch(switches::kForceFieldTrials)) {
851 bool result = FieldTrialList::CreateTrialsFromString(
852 cmd_line.GetSwitchValueASCII(switches::kForceFieldTrials),
853 std::set<std::string>());
854 DCHECK(result);
855 }
856 }
857
858 // static
CreateFeaturesFromCommandLine(const base::CommandLine & command_line,const char * enable_features_switch,const char * disable_features_switch,FeatureList * feature_list)859 void FieldTrialList::CreateFeaturesFromCommandLine(
860 const base::CommandLine& command_line,
861 const char* enable_features_switch,
862 const char* disable_features_switch,
863 FeatureList* feature_list) {
864 // Fallback to command line if not using shared memory.
865 if (!kUseSharedMemoryForFieldTrials ||
866 !global_->field_trial_allocator_.get()) {
867 return feature_list->InitializeFromCommandLine(
868 command_line.GetSwitchValueASCII(enable_features_switch),
869 command_line.GetSwitchValueASCII(disable_features_switch));
870 }
871
872 feature_list->InitializeFromSharedMemory(
873 global_->field_trial_allocator_.get());
874 }
875
876 #if defined(OS_WIN)
877 // static
AppendFieldTrialHandleIfNeeded(HandlesToInheritVector * handles)878 void FieldTrialList::AppendFieldTrialHandleIfNeeded(
879 HandlesToInheritVector* handles) {
880 if (!global_)
881 return;
882 if (kUseSharedMemoryForFieldTrials) {
883 InstantiateFieldTrialAllocatorIfNeeded();
884 if (global_->readonly_allocator_handle_.IsValid())
885 handles->push_back(global_->readonly_allocator_handle_.GetHandle());
886 }
887 }
888 #elif defined(OS_FUCHSIA)
889 // TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368).
890 #elif defined(OS_POSIX) && !defined(OS_NACL)
891 // static
GetFieldTrialHandle()892 SharedMemoryHandle FieldTrialList::GetFieldTrialHandle() {
893 if (global_ && kUseSharedMemoryForFieldTrials) {
894 InstantiateFieldTrialAllocatorIfNeeded();
895 // We check for an invalid handle where this gets called.
896 return global_->readonly_allocator_handle_;
897 }
898 return SharedMemoryHandle();
899 }
900 #endif
901
902 // static
CopyFieldTrialStateToFlags(const char * field_trial_handle_switch,const char * enable_features_switch,const char * disable_features_switch,CommandLine * cmd_line)903 void FieldTrialList::CopyFieldTrialStateToFlags(
904 const char* field_trial_handle_switch,
905 const char* enable_features_switch,
906 const char* disable_features_switch,
907 CommandLine* cmd_line) {
908 // TODO(lawrencewu): Ideally, having the global would be guaranteed. However,
909 // content browser tests currently don't create a FieldTrialList because they
910 // don't run ChromeBrowserMainParts code where it's done for Chrome.
911 // Some tests depend on the enable and disable features flag switch, though,
912 // so we can still add those even though AllStatesToString() will be a no-op.
913 if (!global_) {
914 AddFeatureAndFieldTrialFlags(enable_features_switch,
915 disable_features_switch, cmd_line);
916 return;
917 }
918
919 // Use shared memory to pass the state if the feature is enabled, otherwise
920 // fallback to passing it via the command line as a string.
921 if (kUseSharedMemoryForFieldTrials) {
922 InstantiateFieldTrialAllocatorIfNeeded();
923 // If the readonly handle didn't get duplicated properly, then fallback to
924 // original behavior.
925 if (!global_->readonly_allocator_handle_.IsValid()) {
926 AddFeatureAndFieldTrialFlags(enable_features_switch,
927 disable_features_switch, cmd_line);
928 return;
929 }
930
931 global_->field_trial_allocator_->UpdateTrackingHistograms();
932 std::string switch_value = SerializeSharedMemoryHandleMetadata(
933 global_->readonly_allocator_handle_);
934 cmd_line->AppendSwitchASCII(field_trial_handle_switch, switch_value);
935
936 // Append --enable-features and --disable-features switches corresponding
937 // to the features enabled on the command-line, so that child and browser
938 // process command lines match and clearly show what has been specified
939 // explicitly by the user.
940 std::string enabled_features;
941 std::string disabled_features;
942 FeatureList::GetInstance()->GetCommandLineFeatureOverrides(
943 &enabled_features, &disabled_features);
944
945 if (!enabled_features.empty())
946 cmd_line->AppendSwitchASCII(enable_features_switch, enabled_features);
947 if (!disabled_features.empty())
948 cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features);
949
950 return;
951 }
952
953 AddFeatureAndFieldTrialFlags(enable_features_switch, disable_features_switch,
954 cmd_line);
955 }
956
957 // static
CreateFieldTrial(const std::string & name,const std::string & group_name)958 FieldTrial* FieldTrialList::CreateFieldTrial(
959 const std::string& name,
960 const std::string& group_name) {
961 DCHECK(global_);
962 DCHECK_GE(name.size(), 0u);
963 DCHECK_GE(group_name.size(), 0u);
964 if (name.empty() || group_name.empty() || !global_)
965 return nullptr;
966
967 FieldTrial* field_trial = FieldTrialList::Find(name);
968 if (field_trial) {
969 // In single process mode, or when we force them from the command line,
970 // we may have already created the field trial.
971 if (field_trial->group_name_internal() != group_name)
972 return nullptr;
973 return field_trial;
974 }
975 const int kTotalProbability = 100;
976 field_trial = new FieldTrial(name, kTotalProbability, group_name, 0);
977 FieldTrialList::Register(field_trial);
978 // Force the trial, which will also finalize the group choice.
979 field_trial->SetForced();
980 return field_trial;
981 }
982
983 // static
AddObserver(Observer * observer)984 bool FieldTrialList::AddObserver(Observer* observer) {
985 if (!global_)
986 return false;
987 global_->observer_list_->AddObserver(observer);
988 return true;
989 }
990
991 // static
RemoveObserver(Observer * observer)992 void FieldTrialList::RemoveObserver(Observer* observer) {
993 if (!global_)
994 return;
995 global_->observer_list_->RemoveObserver(observer);
996 }
997
998 // static
SetSynchronousObserver(Observer * observer)999 void FieldTrialList::SetSynchronousObserver(Observer* observer) {
1000 DCHECK(!global_->synchronous_observer_);
1001 global_->synchronous_observer_ = observer;
1002 }
1003
1004 // static
RemoveSynchronousObserver(Observer * observer)1005 void FieldTrialList::RemoveSynchronousObserver(Observer* observer) {
1006 DCHECK_EQ(global_->synchronous_observer_, observer);
1007 global_->synchronous_observer_ = nullptr;
1008 }
1009
1010 // static
OnGroupFinalized(bool is_locked,FieldTrial * field_trial)1011 void FieldTrialList::OnGroupFinalized(bool is_locked, FieldTrial* field_trial) {
1012 if (!global_)
1013 return;
1014 if (is_locked) {
1015 AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(),
1016 field_trial);
1017 } else {
1018 AutoLock auto_lock(global_->lock_);
1019 AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(),
1020 field_trial);
1021 }
1022 }
1023
1024 // static
NotifyFieldTrialGroupSelection(FieldTrial * field_trial)1025 void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) {
1026 if (!global_)
1027 return;
1028
1029 {
1030 AutoLock auto_lock(global_->lock_);
1031 if (field_trial->group_reported_)
1032 return;
1033 field_trial->group_reported_ = true;
1034
1035 if (!field_trial->enable_field_trial_)
1036 return;
1037
1038 if (kUseSharedMemoryForFieldTrials)
1039 ActivateFieldTrialEntryWhileLocked(field_trial);
1040 }
1041
1042 // Recording for stability debugging has to be done inline as a task posted
1043 // to an observer may not get executed before a crash.
1044 base::debug::GlobalActivityTracker* tracker =
1045 base::debug::GlobalActivityTracker::Get();
1046 if (tracker) {
1047 tracker->RecordFieldTrial(field_trial->trial_name(),
1048 field_trial->group_name_internal());
1049 }
1050
1051 if (global_->synchronous_observer_) {
1052 global_->synchronous_observer_->OnFieldTrialGroupFinalized(
1053 field_trial->trial_name(), field_trial->group_name_internal());
1054 }
1055
1056 global_->observer_list_->Notify(
1057 FROM_HERE, &FieldTrialList::Observer::OnFieldTrialGroupFinalized,
1058 field_trial->trial_name(), field_trial->group_name_internal());
1059 }
1060
1061 // static
GetFieldTrialCount()1062 size_t FieldTrialList::GetFieldTrialCount() {
1063 if (!global_)
1064 return 0;
1065 AutoLock auto_lock(global_->lock_);
1066 return global_->registered_.size();
1067 }
1068
1069 // static
GetParamsFromSharedMemory(FieldTrial * field_trial,std::map<std::string,std::string> * params)1070 bool FieldTrialList::GetParamsFromSharedMemory(
1071 FieldTrial* field_trial,
1072 std::map<std::string, std::string>* params) {
1073 DCHECK(global_);
1074 // If the field trial allocator is not set up yet, then there are several
1075 // cases:
1076 // - We are in the browser process and the allocator has not been set up
1077 // yet. If we got here, then we couldn't find the params in
1078 // FieldTrialParamAssociator, so it's definitely not here. Return false.
1079 // - Using shared memory for field trials is not enabled. If we got here,
1080 // then there's nothing in shared memory. Return false.
1081 // - We are in the child process and the allocator has not been set up yet.
1082 // If this is the case, then you are calling this too early. The field trial
1083 // allocator should get set up very early in the lifecycle. Try to see if
1084 // you can call it after it's been set up.
1085 AutoLock auto_lock(global_->lock_);
1086 if (!global_->field_trial_allocator_)
1087 return false;
1088
1089 // If ref_ isn't set, then the field trial data can't be in shared memory.
1090 if (!field_trial->ref_)
1091 return false;
1092
1093 const FieldTrial::FieldTrialEntry* entry =
1094 global_->field_trial_allocator_->GetAsObject<FieldTrial::FieldTrialEntry>(
1095 field_trial->ref_);
1096
1097 size_t allocated_size =
1098 global_->field_trial_allocator_->GetAllocSize(field_trial->ref_);
1099 size_t actual_size = sizeof(FieldTrial::FieldTrialEntry) + entry->pickle_size;
1100 if (allocated_size < actual_size)
1101 return false;
1102
1103 return entry->GetParams(params);
1104 }
1105
1106 // static
ClearParamsFromSharedMemoryForTesting()1107 void FieldTrialList::ClearParamsFromSharedMemoryForTesting() {
1108 if (!global_)
1109 return;
1110
1111 AutoLock auto_lock(global_->lock_);
1112 if (!global_->field_trial_allocator_)
1113 return;
1114
1115 // To clear the params, we iterate through every item in the allocator, copy
1116 // just the trial and group name into a newly-allocated segment and then clear
1117 // the existing item.
1118 FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
1119 FieldTrialAllocator::Iterator mem_iter(allocator);
1120
1121 // List of refs to eventually be made iterable. We can't make it in the loop,
1122 // since it would go on forever.
1123 std::vector<FieldTrial::FieldTrialRef> new_refs;
1124
1125 FieldTrial::FieldTrialRef prev_ref;
1126 while ((prev_ref = mem_iter.GetNextOfType<FieldTrial::FieldTrialEntry>()) !=
1127 FieldTrialAllocator::kReferenceNull) {
1128 // Get the existing field trial entry in shared memory.
1129 const FieldTrial::FieldTrialEntry* prev_entry =
1130 allocator->GetAsObject<FieldTrial::FieldTrialEntry>(prev_ref);
1131 StringPiece trial_name;
1132 StringPiece group_name;
1133 if (!prev_entry->GetTrialAndGroupName(&trial_name, &group_name))
1134 continue;
1135
1136 // Write a new entry, minus the params.
1137 Pickle pickle;
1138 pickle.WriteString(trial_name);
1139 pickle.WriteString(group_name);
1140 size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size();
1141 FieldTrial::FieldTrialEntry* new_entry =
1142 allocator->New<FieldTrial::FieldTrialEntry>(total_size);
1143 subtle::NoBarrier_Store(&new_entry->activated,
1144 subtle::NoBarrier_Load(&prev_entry->activated));
1145 new_entry->pickle_size = pickle.size();
1146
1147 // TODO(lawrencewu): Modify base::Pickle to be able to write over a section
1148 // in memory, so we can avoid this memcpy.
1149 char* dst = reinterpret_cast<char*>(new_entry) +
1150 sizeof(FieldTrial::FieldTrialEntry);
1151 memcpy(dst, pickle.data(), pickle.size());
1152
1153 // Update the ref on the field trial and add it to the list to be made
1154 // iterable.
1155 FieldTrial::FieldTrialRef new_ref = allocator->GetAsReference(new_entry);
1156 FieldTrial* trial = global_->PreLockedFind(trial_name.as_string());
1157 trial->ref_ = new_ref;
1158 new_refs.push_back(new_ref);
1159
1160 // Mark the existing entry as unused.
1161 allocator->ChangeType(prev_ref, 0,
1162 FieldTrial::FieldTrialEntry::kPersistentTypeId,
1163 /*clear=*/false);
1164 }
1165
1166 for (const auto& ref : new_refs) {
1167 allocator->MakeIterable(ref);
1168 }
1169 }
1170
1171 // static
DumpAllFieldTrialsToPersistentAllocator(PersistentMemoryAllocator * allocator)1172 void FieldTrialList::DumpAllFieldTrialsToPersistentAllocator(
1173 PersistentMemoryAllocator* allocator) {
1174 if (!global_)
1175 return;
1176 AutoLock auto_lock(global_->lock_);
1177 for (const auto& registered : global_->registered_) {
1178 AddToAllocatorWhileLocked(allocator, registered.second);
1179 }
1180 }
1181
1182 // static
1183 std::vector<const FieldTrial::FieldTrialEntry*>
GetAllFieldTrialsFromPersistentAllocator(PersistentMemoryAllocator const & allocator)1184 FieldTrialList::GetAllFieldTrialsFromPersistentAllocator(
1185 PersistentMemoryAllocator const& allocator) {
1186 std::vector<const FieldTrial::FieldTrialEntry*> entries;
1187 FieldTrialAllocator::Iterator iter(&allocator);
1188 const FieldTrial::FieldTrialEntry* entry;
1189 while ((entry = iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
1190 nullptr) {
1191 entries.push_back(entry);
1192 }
1193 return entries;
1194 }
1195
1196 // static
IsGlobalSetForTesting()1197 bool FieldTrialList::IsGlobalSetForTesting() {
1198 return global_ != nullptr;
1199 }
1200
1201 // static
SerializeSharedMemoryHandleMetadata(const SharedMemoryHandle & shm)1202 std::string FieldTrialList::SerializeSharedMemoryHandleMetadata(
1203 const SharedMemoryHandle& shm) {
1204 std::stringstream ss;
1205 #if defined(OS_WIN)
1206 // Tell the child process the name of the inherited HANDLE.
1207 uintptr_t uintptr_handle = reinterpret_cast<uintptr_t>(shm.GetHandle());
1208 ss << uintptr_handle << ",";
1209 #elif defined(OS_FUCHSIA)
1210 ss << shm.GetHandle() << ",";
1211 #elif !defined(OS_POSIX)
1212 #error Unsupported OS
1213 #endif
1214
1215 base::UnguessableToken guid = shm.GetGUID();
1216 ss << guid.GetHighForSerialization() << "," << guid.GetLowForSerialization();
1217 ss << "," << shm.GetSize();
1218 return ss.str();
1219 }
1220
1221 #if defined(OS_WIN) || defined(OS_FUCHSIA)
1222
1223 // static
DeserializeSharedMemoryHandleMetadata(const std::string & switch_value)1224 SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
1225 const std::string& switch_value) {
1226 std::vector<base::StringPiece> tokens = base::SplitStringPiece(
1227 switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
1228
1229 if (tokens.size() != 4)
1230 return SharedMemoryHandle();
1231
1232 int field_trial_handle = 0;
1233 if (!base::StringToInt(tokens[0], &field_trial_handle))
1234 return SharedMemoryHandle();
1235 #if defined(OS_FUCHSIA)
1236 zx_handle_t handle = static_cast<zx_handle_t>(field_trial_handle);
1237 #elif defined(OS_WIN)
1238 HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
1239 if (base::IsCurrentProcessElevated()) {
1240 // base::LaunchElevatedProcess doesn't have a way to duplicate the handle,
1241 // but this process can since by definition it's not sandboxed.
1242 base::ProcessId parent_pid = base::GetParentProcessId(GetCurrentProcess());
1243 HANDLE parent_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, parent_pid);
1244 DuplicateHandle(parent_handle, handle, GetCurrentProcess(), &handle, 0,
1245 FALSE, DUPLICATE_SAME_ACCESS);
1246 CloseHandle(parent_handle);
1247 }
1248 #endif // defined(OS_WIN)
1249
1250 base::UnguessableToken guid;
1251 if (!DeserializeGUIDFromStringPieces(tokens[1], tokens[2], &guid))
1252 return SharedMemoryHandle();
1253
1254 int size;
1255 if (!base::StringToInt(tokens[3], &size))
1256 return SharedMemoryHandle();
1257
1258 return SharedMemoryHandle(handle, static_cast<size_t>(size), guid);
1259 }
1260
1261 #elif defined(OS_POSIX) && !defined(OS_NACL)
1262
1263 // static
DeserializeSharedMemoryHandleMetadata(int fd,const std::string & switch_value)1264 SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
1265 int fd,
1266 const std::string& switch_value) {
1267 std::vector<base::StringPiece> tokens = base::SplitStringPiece(
1268 switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
1269
1270 if (tokens.size() != 3)
1271 return SharedMemoryHandle();
1272
1273 base::UnguessableToken guid;
1274 if (!DeserializeGUIDFromStringPieces(tokens[0], tokens[1], &guid))
1275 return SharedMemoryHandle();
1276
1277 int size;
1278 if (!base::StringToInt(tokens[2], &size))
1279 return SharedMemoryHandle();
1280
1281 return SharedMemoryHandle(FileDescriptor(fd, true), static_cast<size_t>(size),
1282 guid);
1283 }
1284
1285 #endif
1286
1287 #if defined(OS_WIN) || defined(OS_FUCHSIA)
1288 // static
CreateTrialsFromSwitchValue(const std::string & switch_value)1289 bool FieldTrialList::CreateTrialsFromSwitchValue(
1290 const std::string& switch_value) {
1291 SharedMemoryHandle shm = DeserializeSharedMemoryHandleMetadata(switch_value);
1292 if (!shm.IsValid())
1293 return false;
1294 return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
1295 }
1296 #elif defined(OS_POSIX) && !defined(OS_NACL)
1297 // static
CreateTrialsFromDescriptor(int fd_key,const std::string & switch_value)1298 bool FieldTrialList::CreateTrialsFromDescriptor(
1299 int fd_key,
1300 const std::string& switch_value) {
1301 if (!kUseSharedMemoryForFieldTrials)
1302 return false;
1303
1304 if (fd_key == -1)
1305 return false;
1306
1307 int fd = GlobalDescriptors::GetInstance()->MaybeGet(fd_key);
1308 if (fd == -1)
1309 return false;
1310
1311 SharedMemoryHandle shm =
1312 DeserializeSharedMemoryHandleMetadata(fd, switch_value);
1313 if (!shm.IsValid())
1314 return false;
1315
1316 bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
1317 DCHECK(result);
1318 return true;
1319 }
1320 #endif // defined(OS_POSIX) && !defined(OS_NACL)
1321
1322 // static
CreateTrialsFromSharedMemoryHandle(SharedMemoryHandle shm_handle)1323 bool FieldTrialList::CreateTrialsFromSharedMemoryHandle(
1324 SharedMemoryHandle shm_handle) {
1325 // shm gets deleted when it gets out of scope, but that's OK because we need
1326 // it only for the duration of this method.
1327 std::unique_ptr<SharedMemory> shm(new SharedMemory(shm_handle, true));
1328 if (!shm.get()->Map(kFieldTrialAllocationSize))
1329 OnOutOfMemory(kFieldTrialAllocationSize);
1330
1331 return FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm));
1332 }
1333
1334 // static
CreateTrialsFromSharedMemory(std::unique_ptr<SharedMemory> shm)1335 bool FieldTrialList::CreateTrialsFromSharedMemory(
1336 std::unique_ptr<SharedMemory> shm) {
1337 global_->field_trial_allocator_.reset(
1338 new FieldTrialAllocator(std::move(shm), 0, kAllocatorName, true));
1339 FieldTrialAllocator* shalloc = global_->field_trial_allocator_.get();
1340 FieldTrialAllocator::Iterator mem_iter(shalloc);
1341
1342 const FieldTrial::FieldTrialEntry* entry;
1343 while ((entry = mem_iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
1344 nullptr) {
1345 StringPiece trial_name;
1346 StringPiece group_name;
1347 if (!entry->GetTrialAndGroupName(&trial_name, &group_name))
1348 return false;
1349
1350 // TODO(lawrencewu): Convert the API for CreateFieldTrial to take
1351 // StringPieces.
1352 FieldTrial* trial =
1353 CreateFieldTrial(trial_name.as_string(), group_name.as_string());
1354
1355 trial->ref_ = mem_iter.GetAsReference(entry);
1356 if (subtle::NoBarrier_Load(&entry->activated)) {
1357 // Call |group()| to mark the trial as "used" and notify observers, if
1358 // any. This is useful to ensure that field trials created in child
1359 // processes are properly reported in crash reports.
1360 trial->group();
1361 }
1362 }
1363 return true;
1364 }
1365
1366 // static
InstantiateFieldTrialAllocatorIfNeeded()1367 void FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded() {
1368 if (!global_)
1369 return;
1370 AutoLock auto_lock(global_->lock_);
1371 // Create the allocator if not already created and add all existing trials.
1372 if (global_->field_trial_allocator_ != nullptr)
1373 return;
1374
1375 SharedMemoryCreateOptions options;
1376 options.size = kFieldTrialAllocationSize;
1377 options.share_read_only = true;
1378 #if defined(OS_MACOSX) && !defined(OS_IOS)
1379 options.type = SharedMemoryHandle::POSIX;
1380 #endif
1381
1382 std::unique_ptr<SharedMemory> shm(new SharedMemory());
1383 if (!shm->Create(options))
1384 OnOutOfMemory(kFieldTrialAllocationSize);
1385
1386 if (!shm->Map(kFieldTrialAllocationSize))
1387 OnOutOfMemory(kFieldTrialAllocationSize);
1388
1389 global_->field_trial_allocator_.reset(
1390 new FieldTrialAllocator(std::move(shm), 0, kAllocatorName, false));
1391 global_->field_trial_allocator_->CreateTrackingHistograms(kAllocatorName);
1392
1393 // Add all existing field trials.
1394 for (const auto& registered : global_->registered_) {
1395 AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(),
1396 registered.second);
1397 }
1398
1399 // Add all existing features.
1400 FeatureList::GetInstance()->AddFeaturesToAllocator(
1401 global_->field_trial_allocator_.get());
1402
1403 #if !defined(OS_NACL)
1404 global_->readonly_allocator_handle_ = GetSharedMemoryReadOnlyHandle(
1405 global_->field_trial_allocator_->shared_memory());
1406 #endif
1407 }
1408
1409 // static
AddToAllocatorWhileLocked(PersistentMemoryAllocator * allocator,FieldTrial * field_trial)1410 void FieldTrialList::AddToAllocatorWhileLocked(
1411 PersistentMemoryAllocator* allocator,
1412 FieldTrial* field_trial) {
1413 // Don't do anything if the allocator hasn't been instantiated yet.
1414 if (allocator == nullptr)
1415 return;
1416
1417 // Or if the allocator is read only, which means we are in a child process and
1418 // shouldn't be writing to it.
1419 if (allocator->IsReadonly())
1420 return;
1421
1422 FieldTrial::State trial_state;
1423 if (!field_trial->GetStateWhileLocked(&trial_state, false))
1424 return;
1425
1426 // Or if we've already added it. We must check after GetState since it can
1427 // also add to the allocator.
1428 if (field_trial->ref_)
1429 return;
1430
1431 Pickle pickle;
1432 PickleFieldTrial(trial_state, &pickle);
1433
1434 size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size();
1435 FieldTrial::FieldTrialRef ref = allocator->Allocate(
1436 total_size, FieldTrial::FieldTrialEntry::kPersistentTypeId);
1437 if (ref == FieldTrialAllocator::kReferenceNull) {
1438 NOTREACHED();
1439 return;
1440 }
1441
1442 FieldTrial::FieldTrialEntry* entry =
1443 allocator->GetAsObject<FieldTrial::FieldTrialEntry>(ref);
1444 subtle::NoBarrier_Store(&entry->activated, trial_state.activated);
1445 entry->pickle_size = pickle.size();
1446
1447 // TODO(lawrencewu): Modify base::Pickle to be able to write over a section in
1448 // memory, so we can avoid this memcpy.
1449 char* dst =
1450 reinterpret_cast<char*>(entry) + sizeof(FieldTrial::FieldTrialEntry);
1451 memcpy(dst, pickle.data(), pickle.size());
1452
1453 allocator->MakeIterable(ref);
1454 field_trial->ref_ = ref;
1455 }
1456
1457 // static
ActivateFieldTrialEntryWhileLocked(FieldTrial * field_trial)1458 void FieldTrialList::ActivateFieldTrialEntryWhileLocked(
1459 FieldTrial* field_trial) {
1460 FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
1461
1462 // Check if we're in the child process and return early if so.
1463 if (!allocator || allocator->IsReadonly())
1464 return;
1465
1466 FieldTrial::FieldTrialRef ref = field_trial->ref_;
1467 if (ref == FieldTrialAllocator::kReferenceNull) {
1468 // It's fine to do this even if the allocator hasn't been instantiated
1469 // yet -- it'll just return early.
1470 AddToAllocatorWhileLocked(allocator, field_trial);
1471 } else {
1472 // It's also okay to do this even though the callee doesn't have a lock --
1473 // the only thing that happens on a stale read here is a slight performance
1474 // hit from the child re-synchronizing activation state.
1475 FieldTrial::FieldTrialEntry* entry =
1476 allocator->GetAsObject<FieldTrial::FieldTrialEntry>(ref);
1477 subtle::NoBarrier_Store(&entry->activated, 1);
1478 }
1479 }
1480
1481 // static
1482 const FieldTrial::EntropyProvider*
GetEntropyProviderForOneTimeRandomization()1483 FieldTrialList::GetEntropyProviderForOneTimeRandomization() {
1484 if (!global_) {
1485 used_without_global_ = true;
1486 return nullptr;
1487 }
1488
1489 return global_->entropy_provider_.get();
1490 }
1491
PreLockedFind(const std::string & name)1492 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
1493 RegistrationMap::iterator it = registered_.find(name);
1494 if (registered_.end() == it)
1495 return nullptr;
1496 return it->second;
1497 }
1498
1499 // static
Register(FieldTrial * trial)1500 void FieldTrialList::Register(FieldTrial* trial) {
1501 if (!global_) {
1502 used_without_global_ = true;
1503 return;
1504 }
1505 AutoLock auto_lock(global_->lock_);
1506 CHECK(!global_->PreLockedFind(trial->trial_name())) << trial->trial_name();
1507 trial->AddRef();
1508 trial->SetTrialRegistered();
1509 global_->registered_[trial->trial_name()] = trial;
1510 }
1511
1512 // static
GetRegisteredTrials()1513 FieldTrialList::RegistrationMap FieldTrialList::GetRegisteredTrials() {
1514 RegistrationMap output;
1515 if (global_) {
1516 AutoLock auto_lock(global_->lock_);
1517 output = global_->registered_;
1518 }
1519 return output;
1520 }
1521
1522 } // namespace base
1523