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, &params);
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, &params)) {
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