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