1 // Copyright 2012 The Chromium Authors
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 <string_view>
9 #include <utility>
10
11 #include "base/auto_reset.h"
12 #include "base/base_switches.h"
13 #include "base/command_line.h"
14 #include "base/containers/span.h"
15 #include "base/debug/crash_logging.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/metrics/field_trial_param_associator.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/no_destructor.h"
22 #include "base/notreached.h"
23 #include "base/numerics/safe_conversions.h"
24 #include "base/process/memory.h"
25 #include "base/process/process_handle.h"
26 #include "base/process/process_info.h"
27 #include "base/rand_util.h"
28 #include "base/strings/strcat.h"
29 #include "base/strings/string_number_conversions.h"
30
31 #include "base/strings/string_split.h"
32 #include "base/strings/string_util.h"
33 #include "base/strings/stringprintf.h"
34 #include "base/strings/utf_string_conversions.h"
35 #include "base/unguessable_token.h"
36 #include "build/build_config.h"
37
38 #if BUILDFLAG(USE_BLINK)
39 #include "base/memory/shared_memory_switch.h"
40 #include "base/process/launch.h"
41 #endif
42
43 #if BUILDFLAG(IS_APPLE) && BUILDFLAG(USE_BLINK)
44 #include "base/mac/mach_port_rendezvous.h"
45 #endif
46
47 #if BUILDFLAG(IS_POSIX) && BUILDFLAG(USE_BLINK)
48 #include <unistd.h> // For getppid().
49 #include "base/threading/platform_thread.h"
50 // On POSIX, the fd is shared using the mapping in GlobalDescriptors.
51 #include "base/posix/global_descriptors.h"
52 #endif
53
54 #if BUILDFLAG(IS_WIN)
55 #include <windows.h>
56 #endif
57
58 #if BUILDFLAG(IS_FUCHSIA)
59 #include <lib/zx/vmo.h>
60 #include <zircon/process.h>
61
62 #include "base/fuchsia/fuchsia_logging.h"
63 #endif
64
65 namespace base {
66
67 namespace {
68
69 #if BUILDFLAG(USE_BLINK)
70 using shared_memory::SharedMemoryError;
71 #endif
72
73 // Define a separator character to use when creating a persistent form of an
74 // instance. This is intended for use as a command line argument, passed to a
75 // second process to mimic our state (i.e., provide the same group name).
76 const char kPersistentStringSeparator = '/'; // Currently a slash.
77
78 // Define a marker character to be used as a prefix to a trial name on the
79 // command line which forces its activation.
80 const char kActivationMarker = '*';
81
82 // Constants for the field trial allocator.
83 const char kAllocatorName[] = "FieldTrialAllocator";
84
85 // We allocate 256 KiB to hold all the field trial data. This should be enough,
86 // as most people use 3 - 25 KiB for field trials (as of 11/25/2016).
87 // This also doesn't allocate all 256 KiB at once -- the pages only get mapped
88 // to physical memory when they are touched. If the size of the allocated field
89 // trials does get larger than 256 KiB, then we will drop some field trials in
90 // child processes, leading to an inconsistent view between browser and child
91 // processes and possibly causing crashes (see crbug.com/661617).
92 const size_t kFieldTrialAllocationSize = 256 << 10; // 256 KiB
93
94 #if BUILDFLAG(IS_APPLE) && BUILDFLAG(USE_BLINK)
95 constexpr MachPortsForRendezvous::key_type kFieldTrialRendezvousKey = 'fldt';
96 #endif
97
98 // Writes out string1 and then string2 to pickle.
WriteStringPair(Pickle * pickle,std::string_view string1,std::string_view string2)99 void WriteStringPair(Pickle* pickle,
100 std::string_view string1,
101 std::string_view string2) {
102 pickle->WriteString(string1);
103 pickle->WriteString(string2);
104 }
105
106 // Writes out the field trial's contents (via trial_state) to the pickle. The
107 // format of the pickle looks like:
108 // TrialName, GroupName, is_overridden, ParamKey1, ParamValue1, ParamKey2,
109 // ParamValue2, ... If there are no parameters, then it just ends at
110 // is_overridden.
PickleFieldTrial(const FieldTrial::PickleState & trial_state,Pickle * pickle)111 void PickleFieldTrial(const FieldTrial::PickleState& trial_state,
112 Pickle* pickle) {
113 WriteStringPair(pickle, *trial_state.trial_name, *trial_state.group_name);
114 pickle->WriteBool(trial_state.is_overridden);
115
116 // Get field trial params.
117 std::map<std::string, std::string> params;
118 FieldTrialParamAssociator::GetInstance()->GetFieldTrialParamsWithoutFallback(
119 *trial_state.trial_name, *trial_state.group_name, ¶ms);
120
121 // Write params to pickle.
122 for (const auto& param : params)
123 WriteStringPair(pickle, param.first, param.second);
124 }
125
126 // Returns the boundary value for comparing against the FieldTrial's added
127 // groups for a given |divisor| (total probability) and |entropy_value|.
GetGroupBoundaryValue(FieldTrial::Probability divisor,double entropy_value)128 FieldTrial::Probability GetGroupBoundaryValue(
129 FieldTrial::Probability divisor,
130 double entropy_value) {
131 // Add a tiny epsilon value to get consistent results when converting floating
132 // points to int. Without it, boundary values have inconsistent results, e.g.:
133 //
134 // static_cast<FieldTrial::Probability>(100 * 0.56) == 56
135 // static_cast<FieldTrial::Probability>(100 * 0.57) == 56
136 // static_cast<FieldTrial::Probability>(100 * 0.58) == 57
137 // static_cast<FieldTrial::Probability>(100 * 0.59) == 59
138 const double kEpsilon = 1e-8;
139 const FieldTrial::Probability result =
140 static_cast<FieldTrial::Probability>(divisor * entropy_value + kEpsilon);
141 // Ensure that adding the epsilon still results in a value < |divisor|.
142 return std::min(result, divisor - 1);
143 }
144
OnOutOfMemory(size_t size)145 void OnOutOfMemory(size_t size) {
146 TerminateBecauseOutOfMemory(size);
147 }
148
AppendFieldTrialGroupToString(bool activated,std::string_view trial_name,std::string_view group_name,std::string & field_trials_string)149 void AppendFieldTrialGroupToString(bool activated,
150 std::string_view trial_name,
151 std::string_view group_name,
152 std::string& field_trials_string) {
153 DCHECK_EQ(std::string::npos, trial_name.find(kPersistentStringSeparator))
154 << " in name " << trial_name;
155 DCHECK_EQ(std::string::npos, group_name.find(kPersistentStringSeparator))
156 << " in name " << group_name;
157
158 if (!field_trials_string.empty()) {
159 // Add a '/' in-between field trial groups.
160 field_trials_string.push_back(kPersistentStringSeparator);
161 }
162 if (activated) {
163 field_trials_string.push_back(kActivationMarker);
164 }
165
166 base::StrAppend(&field_trials_string,
167 {trial_name, std::string_view(&kPersistentStringSeparator, 1),
168 group_name});
169 }
170
171 } // namespace
172
173 // statics
174 const int FieldTrial::kNotFinalized = -1;
175 const int FieldTrial::kDefaultGroupNumber = 0;
176 bool FieldTrial::enable_benchmarking_ = false;
177
178 //------------------------------------------------------------------------------
179 // FieldTrial methods and members.
180
181 FieldTrial::EntropyProvider::~EntropyProvider() = default;
182
GetPseudorandomValue(uint32_t salt,uint32_t output_range) const183 uint32_t FieldTrial::EntropyProvider::GetPseudorandomValue(
184 uint32_t salt,
185 uint32_t output_range) const {
186 // Passing a different salt is sufficient to get a "different" result from
187 // GetEntropyForTrial (ignoring collisions).
188 double entropy_value = GetEntropyForTrial(/*trial_name=*/"", salt);
189
190 // Convert the [0,1) double to an [0, output_range) integer.
191 return static_cast<uint32_t>(GetGroupBoundaryValue(
192 static_cast<FieldTrial::Probability>(output_range), entropy_value));
193 }
194
195 FieldTrial::PickleState::PickleState() = default;
196
197 FieldTrial::PickleState::PickleState(const PickleState& other) = default;
198
199 FieldTrial::PickleState::~PickleState() = default;
200
GetState(std::string_view & trial_name,std::string_view & group_name,bool & overridden) const201 bool FieldTrial::FieldTrialEntry::GetState(std::string_view& trial_name,
202 std::string_view& group_name,
203 bool& overridden) const {
204 PickleIterator iter = GetPickleIterator();
205 return ReadHeader(iter, trial_name, group_name, overridden);
206 }
207
GetParams(std::map<std::string,std::string> * params) const208 bool FieldTrial::FieldTrialEntry::GetParams(
209 std::map<std::string, std::string>* params) const {
210 PickleIterator iter = GetPickleIterator();
211 std::string_view tmp_string;
212 bool tmp_bool;
213 // Skip reading trial and group name, and overridden bit.
214 if (!ReadHeader(iter, tmp_string, tmp_string, tmp_bool)) {
215 return false;
216 }
217
218 while (true) {
219 std::string_view key;
220 std::string_view value;
221 if (!ReadStringPair(&iter, &key, &value))
222 return key.empty(); // Non-empty is bad: got one of a pair.
223 (*params)[std::string(key)] = std::string(value);
224 }
225 }
226
GetPickleIterator() const227 PickleIterator FieldTrial::FieldTrialEntry::GetPickleIterator() const {
228 Pickle pickle = Pickle::WithUnownedBuffer(
229 span(GetPickledDataPtr(), checked_cast<size_t>(pickle_size)));
230 return PickleIterator(pickle);
231 }
232
ReadHeader(PickleIterator & iter,std::string_view & trial_name,std::string_view & group_name,bool & overridden) const233 bool FieldTrial::FieldTrialEntry::ReadHeader(PickleIterator& iter,
234 std::string_view& trial_name,
235 std::string_view& group_name,
236 bool& overridden) const {
237 return ReadStringPair(&iter, &trial_name, &group_name) &&
238 iter.ReadBool(&overridden);
239 }
240
ReadStringPair(PickleIterator * iter,std::string_view * trial_name,std::string_view * group_name) const241 bool FieldTrial::FieldTrialEntry::ReadStringPair(
242 PickleIterator* iter,
243 std::string_view* trial_name,
244 std::string_view* group_name) const {
245 if (!iter->ReadStringPiece(trial_name))
246 return false;
247 if (!iter->ReadStringPiece(group_name))
248 return false;
249 return true;
250 }
251
AppendGroup(const std::string & name,Probability group_probability)252 void FieldTrial::AppendGroup(const std::string& name,
253 Probability group_probability) {
254 // When the group choice was previously forced, we only need to return the
255 // the id of the chosen group, and anything can be returned for the others.
256 if (forced_) {
257 DCHECK(!group_name_.empty());
258 if (name == group_name_) {
259 // Note that while |group_| may be equal to |kDefaultGroupNumber| on the
260 // forced trial, it will not have the same value as the default group
261 // number returned from the non-forced |FactoryGetFieldTrial()| call,
262 // which takes care to ensure that this does not happen.
263 return;
264 }
265 DCHECK_NE(next_group_number_, group_);
266 // We still return different numbers each time, in case some caller need
267 // them to be different.
268 next_group_number_++;
269 return;
270 }
271
272 DCHECK_LE(group_probability, divisor_);
273 DCHECK_GE(group_probability, 0);
274
275 if (enable_benchmarking_)
276 group_probability = 0;
277
278 accumulated_group_probability_ += group_probability;
279
280 DCHECK_LE(accumulated_group_probability_, divisor_);
281 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) {
282 // This is the group that crossed the random line, so we do the assignment.
283 SetGroupChoice(name, next_group_number_);
284 }
285 next_group_number_++;
286 return;
287 }
288
Activate()289 void FieldTrial::Activate() {
290 FinalizeGroupChoice();
291 if (trial_registered_)
292 FieldTrialList::NotifyFieldTrialGroupSelection(this);
293 }
294
group_name()295 const std::string& FieldTrial::group_name() {
296 // Call |Activate()| to ensure group gets assigned and observers are notified.
297 Activate();
298 DCHECK(!group_name_.empty());
299 return group_name_;
300 }
301
GetGroupNameWithoutActivation()302 const std::string& FieldTrial::GetGroupNameWithoutActivation() {
303 FinalizeGroupChoice();
304 return group_name_;
305 }
306
SetForced()307 void FieldTrial::SetForced() {
308 // We might have been forced before (e.g., by CreateFieldTrial) and it's
309 // first come first served, e.g., command line switch has precedence.
310 if (forced_)
311 return;
312
313 // And we must finalize the group choice before we mark ourselves as forced.
314 FinalizeGroupChoice();
315 forced_ = true;
316 }
317
IsOverridden() const318 bool FieldTrial::IsOverridden() const {
319 return is_overridden_;
320 }
321
322 // static
EnableBenchmarking()323 void FieldTrial::EnableBenchmarking() {
324 // We don't need to see field trials created via CreateFieldTrial() for
325 // benchmarking, because such field trials have only a single group and are
326 // not affected by randomization that |enable_benchmarking_| would disable.
327 DCHECK_EQ(0u, FieldTrialList::GetRandomizedFieldTrialCount());
328 enable_benchmarking_ = true;
329 }
330
331 // static
CreateSimulatedFieldTrial(std::string_view trial_name,Probability total_probability,std::string_view default_group_name,double entropy_value)332 FieldTrial* FieldTrial::CreateSimulatedFieldTrial(
333 std::string_view trial_name,
334 Probability total_probability,
335 std::string_view default_group_name,
336 double entropy_value) {
337 return new FieldTrial(trial_name, total_probability, default_group_name,
338 entropy_value, /*is_low_anonymity=*/false,
339 /*is_overridden=*/false);
340 }
341
342 // static
ParseFieldTrialsString(std::string_view trials_string,bool override_trials,std::vector<State> & entries)343 bool FieldTrial::ParseFieldTrialsString(std::string_view trials_string,
344 bool override_trials,
345 std::vector<State>& entries) {
346 size_t next_item = 0;
347 while (next_item < trials_string.length()) {
348 // Parse one entry. Entries have the format
349 // TrialName1/GroupName1/TrialName2/GroupName2. Each loop parses one trial
350 // and group name.
351
352 // Find the first delimiter starting at next_item, or quit.
353 size_t trial_name_end =
354 trials_string.find(kPersistentStringSeparator, next_item);
355 if (trial_name_end == trials_string.npos || next_item == trial_name_end) {
356 return false;
357 }
358 // Find the second delimiter, or end of string.
359 size_t group_name_end =
360 trials_string.find(kPersistentStringSeparator, trial_name_end + 1);
361 if (group_name_end == trials_string.npos) {
362 group_name_end = trials_string.length();
363 }
364 // Group names should not be empty, so quit if it is.
365 if (trial_name_end + 1 == group_name_end) {
366 return false;
367 }
368
369 FieldTrial::State entry;
370 // Verify if the trial should be activated or not.
371 if (trials_string[next_item] == kActivationMarker) {
372 // Name cannot be only the indicator.
373 if (trial_name_end - next_item == 1) {
374 return false;
375 }
376 next_item++;
377 entry.activated = true;
378 }
379 entry.trial_name =
380 trials_string.substr(next_item, trial_name_end - next_item);
381 entry.group_name = trials_string.substr(
382 trial_name_end + 1, group_name_end - trial_name_end - 1);
383 entry.is_overridden = override_trials;
384 // The next item starts after the delimiter, if it exists.
385 next_item = group_name_end + 1;
386
387 entries.push_back(std::move(entry));
388 }
389 return true;
390 }
391
392 // static
BuildFieldTrialStateString(const std::vector<State> & states)393 std::string FieldTrial::BuildFieldTrialStateString(
394 const std::vector<State>& states) {
395 std::string result;
396 for (const State& state : states) {
397 AppendFieldTrialGroupToString(state.activated, state.trial_name,
398 state.group_name, result);
399 }
400 return result;
401 }
402
FieldTrial(std::string_view trial_name,const Probability total_probability,std::string_view default_group_name,double entropy_value,bool is_low_anonymity,bool is_overridden)403 FieldTrial::FieldTrial(std::string_view trial_name,
404 const Probability total_probability,
405 std::string_view default_group_name,
406 double entropy_value,
407 bool is_low_anonymity,
408 bool is_overridden)
409 : trial_name_(trial_name),
410 divisor_(total_probability),
411 default_group_name_(default_group_name),
412 random_(GetGroupBoundaryValue(total_probability, entropy_value)),
413 accumulated_group_probability_(0),
414 next_group_number_(kDefaultGroupNumber + 1),
415 group_(kNotFinalized),
416 forced_(false),
417 is_overridden_(is_overridden),
418 group_reported_(false),
419 trial_registered_(false),
420 ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull),
421 is_low_anonymity_(is_low_anonymity) {
422 DCHECK_GT(total_probability, 0);
423 DCHECK(!trial_name_.empty());
424 DCHECK(!default_group_name_.empty())
425 << "Trial " << trial_name << " is missing a default group name.";
426 }
427
428 FieldTrial::~FieldTrial() = default;
429
SetTrialRegistered()430 void FieldTrial::SetTrialRegistered() {
431 DCHECK_EQ(kNotFinalized, group_);
432 DCHECK(!trial_registered_);
433 trial_registered_ = true;
434 }
435
SetGroupChoice(const std::string & group_name,int number)436 void FieldTrial::SetGroupChoice(const std::string& group_name, int number) {
437 group_ = number;
438 if (group_name.empty())
439 StringAppendF(&group_name_, "%d", group_);
440 else
441 group_name_ = group_name;
442 DVLOG(1) << "Field trial: " << trial_name_ << " Group choice:" << group_name_;
443 }
444
FinalizeGroupChoice()445 void FieldTrial::FinalizeGroupChoice() {
446 if (group_ != kNotFinalized)
447 return;
448 accumulated_group_probability_ = divisor_;
449 // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not
450 // finalized.
451 DCHECK(!forced_);
452 SetGroupChoice(default_group_name_, kDefaultGroupNumber);
453 }
454
GetActiveGroup(ActiveGroup * active_group) const455 bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const {
456 if (!group_reported_)
457 return false;
458 DCHECK_NE(group_, kNotFinalized);
459 active_group->trial_name = trial_name_;
460 active_group->group_name = group_name_;
461 active_group->is_overridden = is_overridden_;
462 return true;
463 }
464
GetStateWhileLocked(PickleState * field_trial_state)465 void FieldTrial::GetStateWhileLocked(PickleState* field_trial_state) {
466 FinalizeGroupChoice();
467 field_trial_state->trial_name = &trial_name_;
468 field_trial_state->group_name = &group_name_;
469 field_trial_state->activated = group_reported_;
470 field_trial_state->is_overridden = is_overridden_;
471 }
472
473 //------------------------------------------------------------------------------
474 // FieldTrialList methods and members.
475
476 // static
477 FieldTrialList* FieldTrialList::global_ = nullptr;
478
479 FieldTrialList::Observer::~Observer() = default;
480
FieldTrialList()481 FieldTrialList::FieldTrialList() {
482 DCHECK(!global_);
483 global_ = this;
484 }
485
~FieldTrialList()486 FieldTrialList::~FieldTrialList() {
487 AutoLock auto_lock(lock_);
488 while (!registered_.empty()) {
489 auto it = registered_.begin();
490 it->second->Release();
491 registered_.erase(it->first);
492 }
493 // Note: If this DCHECK fires in a test that uses ScopedFeatureList, it is
494 // likely caused by nested ScopedFeatureLists being destroyed in a different
495 // order than they are initialized.
496 if (!was_reset_) {
497 DCHECK_EQ(this, global_);
498 global_ = nullptr;
499 }
500 }
501
502 // static
FactoryGetFieldTrial(std::string_view trial_name,FieldTrial::Probability total_probability,std::string_view default_group_name,const FieldTrial::EntropyProvider & entropy_provider,uint32_t randomization_seed,bool is_low_anonymity,bool is_overridden)503 FieldTrial* FieldTrialList::FactoryGetFieldTrial(
504 std::string_view trial_name,
505 FieldTrial::Probability total_probability,
506 std::string_view default_group_name,
507 const FieldTrial::EntropyProvider& entropy_provider,
508 uint32_t randomization_seed,
509 bool is_low_anonymity,
510 bool is_overridden) {
511 // Check if the field trial has already been created in some other way.
512 FieldTrial* existing_trial = Find(trial_name);
513 if (existing_trial) {
514 CHECK(existing_trial->forced_);
515 return existing_trial;
516 }
517
518 double entropy_value =
519 entropy_provider.GetEntropyForTrial(trial_name, randomization_seed);
520
521 FieldTrial* field_trial =
522 new FieldTrial(trial_name, total_probability, default_group_name,
523 entropy_value, is_low_anonymity, is_overridden);
524 FieldTrialList::Register(field_trial, /*is_randomized_trial=*/true);
525 return field_trial;
526 }
527
528 // static
Find(std::string_view trial_name)529 FieldTrial* FieldTrialList::Find(std::string_view trial_name) {
530 if (!global_)
531 return nullptr;
532 AutoLock auto_lock(global_->lock_);
533 return global_->PreLockedFind(trial_name);
534 }
535
536 // static
FindFullName(std::string_view trial_name)537 std::string FieldTrialList::FindFullName(std::string_view trial_name) {
538 FieldTrial* field_trial = Find(trial_name);
539 if (field_trial)
540 return field_trial->group_name();
541 return std::string();
542 }
543
544 // static
TrialExists(std::string_view trial_name)545 bool FieldTrialList::TrialExists(std::string_view trial_name) {
546 return Find(trial_name) != nullptr;
547 }
548
549 // static
IsTrialActive(std::string_view trial_name)550 bool FieldTrialList::IsTrialActive(std::string_view trial_name) {
551 FieldTrial* field_trial = Find(trial_name);
552 return field_trial && field_trial->group_reported_;
553 }
554
555 // static
GetAllFieldTrialStates(PassKey<test::ScopedFeatureList>)556 std::vector<FieldTrial::State> FieldTrialList::GetAllFieldTrialStates(
557 PassKey<test::ScopedFeatureList>) {
558 std::vector<FieldTrial::State> states;
559
560 if (!global_)
561 return states;
562
563 AutoLock auto_lock(global_->lock_);
564 for (const auto& registered : global_->registered_) {
565 FieldTrial::PickleState trial;
566 registered.second->GetStateWhileLocked(&trial);
567 DCHECK_EQ(std::string::npos,
568 trial.trial_name->find(kPersistentStringSeparator));
569 DCHECK_EQ(std::string::npos,
570 trial.group_name->find(kPersistentStringSeparator));
571 FieldTrial::State entry;
572 entry.activated = trial.activated;
573 entry.trial_name = *trial.trial_name;
574 entry.group_name = *trial.group_name;
575 states.push_back(std::move(entry));
576 }
577 return states;
578 }
579
580 // static
AllStatesToString(std::string * output)581 void FieldTrialList::AllStatesToString(std::string* output) {
582 if (!global_)
583 return;
584 AutoLock auto_lock(global_->lock_);
585
586 for (const auto& registered : global_->registered_) {
587 FieldTrial::PickleState trial;
588 registered.second->GetStateWhileLocked(&trial);
589 AppendFieldTrialGroupToString(trial.activated, *trial.trial_name,
590 *trial.group_name, *output);
591 }
592 }
593
594 // static
AllParamsToString(EscapeDataFunc encode_data_func)595 std::string FieldTrialList::AllParamsToString(EscapeDataFunc encode_data_func) {
596 FieldTrialParamAssociator* params_associator =
597 FieldTrialParamAssociator::GetInstance();
598 std::string output;
599 for (const auto& registered : GetRegisteredTrials()) {
600 FieldTrial::PickleState trial;
601 registered.second->GetStateWhileLocked(&trial);
602 DCHECK_EQ(std::string::npos,
603 trial.trial_name->find(kPersistentStringSeparator));
604 DCHECK_EQ(std::string::npos,
605 trial.group_name->find(kPersistentStringSeparator));
606 std::map<std::string, std::string> params;
607 if (params_associator->GetFieldTrialParamsWithoutFallback(
608 *trial.trial_name, *trial.group_name, ¶ms)) {
609 if (params.size() > 0) {
610 // Add comma to seprate from previous entry if it exists.
611 if (!output.empty())
612 output.append(1, ',');
613
614 output.append(encode_data_func(*trial.trial_name));
615 output.append(1, '.');
616 output.append(encode_data_func(*trial.group_name));
617 output.append(1, ':');
618
619 std::string param_str;
620 for (const auto& param : params) {
621 // Add separator from previous param information if it exists.
622 if (!param_str.empty()) {
623 param_str.append(1, kPersistentStringSeparator);
624 }
625 param_str.append(encode_data_func(param.first));
626 param_str.append(1, kPersistentStringSeparator);
627 param_str.append(encode_data_func(param.second));
628 }
629
630 output.append(param_str);
631 }
632 }
633 }
634 return output;
635 }
636
637 // static
GetActiveFieldTrialGroups(FieldTrial::ActiveGroups * active_groups)638 void FieldTrialList::GetActiveFieldTrialGroups(
639 FieldTrial::ActiveGroups* active_groups) {
640 GetActiveFieldTrialGroupsInternal(active_groups,
641 /*include_low_anonymity=*/false);
642 }
643
644 // static
GetActiveTrialsOfParentProcess()645 std::set<std::string> FieldTrialList::GetActiveTrialsOfParentProcess() {
646 CHECK(global_);
647 CHECK(global_->create_trials_in_child_process_called_);
648
649 std::set<std::string> result;
650 // CreateTrialsInChildProcess() may not have created the allocator if
651 // kFieldTrialHandle was not passed on the command line.
652 if (!global_->field_trial_allocator_) {
653 return result;
654 }
655
656 FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
657 FieldTrialAllocator::Iterator mem_iter(allocator);
658 const FieldTrial::FieldTrialEntry* entry;
659 while ((entry = mem_iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
660 nullptr) {
661 std::string_view trial_name;
662 std::string_view group_name;
663 bool is_overridden;
664 if (subtle::NoBarrier_Load(&entry->activated) &&
665 entry->GetState(trial_name, group_name, is_overridden)) {
666 result.emplace(trial_name);
667 }
668 }
669 return result;
670 }
671
672 // static
CreateTrialsFromString(const std::string & trials_string,bool override_trials)673 bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string,
674 bool override_trials) {
675 DCHECK(global_);
676 if (trials_string.empty() || !global_)
677 return true;
678
679 std::vector<FieldTrial::State> entries;
680 if (!FieldTrial::ParseFieldTrialsString(trials_string, override_trials,
681 entries)) {
682 return false;
683 }
684
685 return CreateTrialsFromFieldTrialStatesInternal(entries);
686 }
687
688 // static
CreateTrialsFromFieldTrialStates(PassKey<test::ScopedFeatureList>,const std::vector<FieldTrial::State> & entries)689 bool FieldTrialList::CreateTrialsFromFieldTrialStates(
690 PassKey<test::ScopedFeatureList>,
691 const std::vector<FieldTrial::State>& entries) {
692 return CreateTrialsFromFieldTrialStatesInternal(entries);
693 }
694
695 // static
CreateTrialsInChildProcess(const CommandLine & cmd_line)696 void FieldTrialList::CreateTrialsInChildProcess(const CommandLine& cmd_line) {
697 CHECK(!global_->create_trials_in_child_process_called_);
698 global_->create_trials_in_child_process_called_ = true;
699
700 #if BUILDFLAG(USE_BLINK)
701 // TODO(crbug.com/867558): Change to a CHECK.
702 if (cmd_line.HasSwitch(switches::kFieldTrialHandle)) {
703 std::string switch_value =
704 cmd_line.GetSwitchValueASCII(switches::kFieldTrialHandle);
705 SharedMemoryError result = CreateTrialsFromSwitchValue(switch_value);
706 SCOPED_CRASH_KEY_NUMBER("FieldTrialList", "SharedMemoryError",
707 static_cast<int>(result));
708 CHECK_EQ(result, SharedMemoryError::kNoError);
709 }
710 #endif // BUILDFLAG(USE_BLINK)
711 }
712
713 // static
ApplyFeatureOverridesInChildProcess(FeatureList * feature_list)714 void FieldTrialList::ApplyFeatureOverridesInChildProcess(
715 FeatureList* feature_list) {
716 CHECK(global_->create_trials_in_child_process_called_);
717 // TODO(crbug.com/867558): Change to a CHECK.
718 if (global_->field_trial_allocator_) {
719 feature_list->InitFromSharedMemory(global_->field_trial_allocator_.get());
720 }
721 }
722
723 #if BUILDFLAG(USE_BLINK)
724 // static
PopulateLaunchOptionsWithFieldTrialState(GlobalDescriptors::Key descriptor_key,ScopedFD & descriptor_to_share,CommandLine * command_line,LaunchOptions * launch_options)725 void FieldTrialList::PopulateLaunchOptionsWithFieldTrialState(
726 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
727 GlobalDescriptors::Key descriptor_key,
728 ScopedFD& descriptor_to_share,
729 #endif
730 CommandLine* command_line,
731 LaunchOptions* launch_options) {
732 CHECK(command_line);
733
734 // Use shared memory to communicate field trial state to child processes.
735 // The browser is the only process that has write access to the shared memory.
736 InstantiateFieldTrialAllocatorIfNeeded();
737 CHECK(global_);
738 CHECK(global_->readonly_allocator_region_.IsValid());
739
740 global_->field_trial_allocator_->UpdateTrackingHistograms();
741 shared_memory::AddToLaunchParameters(
742 switches::kFieldTrialHandle,
743 global_->readonly_allocator_region_.Duplicate(),
744 #if BUILDFLAG(IS_APPLE)
745 kFieldTrialRendezvousKey,
746 #elif BUILDFLAG(IS_POSIX)
747 descriptor_key, descriptor_to_share,
748 #endif
749 command_line, launch_options);
750
751 // Append --enable-features and --disable-features switches corresponding
752 // to the features enabled on the command-line, so that child and browser
753 // process command lines match and clearly show what has been specified
754 // explicitly by the user.
755 std::string enabled_features;
756 std::string disabled_features;
757 FeatureList::GetInstance()->GetCommandLineFeatureOverrides(
758 &enabled_features, &disabled_features);
759
760 if (!enabled_features.empty()) {
761 command_line->AppendSwitchASCII(switches::kEnableFeatures,
762 enabled_features);
763 }
764 if (!disabled_features.empty()) {
765 command_line->AppendSwitchASCII(switches::kDisableFeatures,
766 disabled_features);
767 }
768 }
769 #endif // BUILDFLAG(USE_BLINK)
770
771 // static
772 ReadOnlySharedMemoryRegion
DuplicateFieldTrialSharedMemoryForTesting()773 FieldTrialList::DuplicateFieldTrialSharedMemoryForTesting() {
774 if (!global_)
775 return ReadOnlySharedMemoryRegion();
776
777 return global_->readonly_allocator_region_.Duplicate();
778 }
779
780 // static
CreateFieldTrial(std::string_view name,std::string_view group_name,bool is_low_anonymity,bool is_overridden)781 FieldTrial* FieldTrialList::CreateFieldTrial(std::string_view name,
782 std::string_view group_name,
783 bool is_low_anonymity,
784 bool is_overridden) {
785 DCHECK(global_);
786 DCHECK_GE(name.size(), 0u);
787 DCHECK_GE(group_name.size(), 0u);
788 if (name.empty() || group_name.empty() || !global_)
789 return nullptr;
790
791 FieldTrial* field_trial = FieldTrialList::Find(name);
792 if (field_trial) {
793 // In single process mode, or when we force them from the command line,
794 // we may have already created the field trial.
795 if (field_trial->group_name_internal() != group_name)
796 return nullptr;
797 return field_trial;
798 }
799 const int kTotalProbability = 100;
800 field_trial = new FieldTrial(name, kTotalProbability, group_name, 0,
801 is_low_anonymity, is_overridden);
802 // The group choice will be finalized in this method. So
803 // |is_randomized_trial| should be false.
804 FieldTrialList::Register(field_trial, /*is_randomized_trial=*/false);
805 // Force the trial, which will also finalize the group choice.
806 field_trial->SetForced();
807 return field_trial;
808 }
809
810 // static
AddObserver(Observer * observer)811 bool FieldTrialList::AddObserver(Observer* observer) {
812 return FieldTrialList::AddObserverInternal(observer,
813 /*include_low_anonymity=*/false);
814 }
815
816 // static
RemoveObserver(Observer * observer)817 void FieldTrialList::RemoveObserver(Observer* observer) {
818 FieldTrialList::RemoveObserverInternal(observer,
819 /*include_low_anonymity=*/false);
820 }
821
822 // static
NotifyFieldTrialGroupSelection(FieldTrial * field_trial)823 void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) {
824 if (!global_)
825 return;
826
827 std::vector<raw_ptr<Observer, VectorExperimental>> local_observers;
828 std::vector<raw_ptr<Observer, VectorExperimental>>
829 local_observers_including_low_anonymity;
830
831 {
832 AutoLock auto_lock(global_->lock_);
833 if (field_trial->group_reported_)
834 return;
835 field_trial->group_reported_ = true;
836
837 ++global_->num_ongoing_notify_field_trial_group_selection_calls_;
838
839 ActivateFieldTrialEntryWhileLocked(field_trial);
840
841 // Copy observers to a local variable to access outside the scope of the
842 // lock. Since removing observers concurrently with this method is
843 // disallowed, pointers should remain valid while observers are notified.
844 local_observers = global_->observers_;
845 local_observers_including_low_anonymity =
846 global_->observers_including_low_anonymity_;
847 }
848
849 if (!field_trial->is_low_anonymity_) {
850 for (Observer* observer : local_observers) {
851 observer->OnFieldTrialGroupFinalized(*field_trial,
852 field_trial->group_name_internal());
853 }
854 }
855
856 for (Observer* observer : local_observers_including_low_anonymity) {
857 observer->OnFieldTrialGroupFinalized(*field_trial,
858 field_trial->group_name_internal());
859 }
860
861 int previous_num_ongoing_notify_field_trial_group_selection_calls =
862 global_->num_ongoing_notify_field_trial_group_selection_calls_--;
863 DCHECK_GT(previous_num_ongoing_notify_field_trial_group_selection_calls, 0);
864 }
865
866 // static
GetFieldTrialCount()867 size_t FieldTrialList::GetFieldTrialCount() {
868 if (!global_)
869 return 0;
870 AutoLock auto_lock(global_->lock_);
871 return global_->registered_.size();
872 }
873
874 // static
GetRandomizedFieldTrialCount()875 size_t FieldTrialList::GetRandomizedFieldTrialCount() {
876 if (!global_)
877 return 0;
878 AutoLock auto_lock(global_->lock_);
879 return global_->num_registered_randomized_trials_;
880 }
881
882 // static
GetParamsFromSharedMemory(FieldTrial * field_trial,std::map<std::string,std::string> * params)883 bool FieldTrialList::GetParamsFromSharedMemory(
884 FieldTrial* field_trial,
885 std::map<std::string, std::string>* params) {
886 DCHECK(global_);
887 // If the field trial allocator is not set up yet, then there are several
888 // cases:
889 // - We are in the browser process and the allocator has not been set up
890 // yet. If we got here, then we couldn't find the params in
891 // FieldTrialParamAssociator, so it's definitely not here. Return false.
892 // - Using shared memory for field trials is not enabled. If we got here,
893 // then there's nothing in shared memory. Return false.
894 // - We are in the child process and the allocator has not been set up yet.
895 // If this is the case, then you are calling this too early. The field trial
896 // allocator should get set up very early in the lifecycle. Try to see if
897 // you can call it after it's been set up.
898 AutoLock auto_lock(global_->lock_);
899 if (!global_->field_trial_allocator_)
900 return false;
901
902 // If ref_ isn't set, then the field trial data can't be in shared memory.
903 if (!field_trial->ref_)
904 return false;
905
906 const FieldTrial::FieldTrialEntry* entry =
907 global_->field_trial_allocator_->GetAsObject<FieldTrial::FieldTrialEntry>(
908 field_trial->ref_);
909
910 size_t allocated_size =
911 global_->field_trial_allocator_->GetAllocSize(field_trial->ref_);
912 uint64_t actual_size =
913 sizeof(FieldTrial::FieldTrialEntry) + entry->pickle_size;
914 if (allocated_size < actual_size)
915 return false;
916
917 return entry->GetParams(params);
918 }
919
920 // static
ClearParamsFromSharedMemoryForTesting()921 void FieldTrialList::ClearParamsFromSharedMemoryForTesting() {
922 if (!global_)
923 return;
924
925 AutoLock auto_lock(global_->lock_);
926 if (!global_->field_trial_allocator_)
927 return;
928
929 // To clear the params, we iterate through every item in the allocator, copy
930 // just the trial and group name into a newly-allocated segment and then clear
931 // the existing item.
932 FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
933 FieldTrialAllocator::Iterator mem_iter(allocator);
934
935 // List of refs to eventually be made iterable. We can't make it in the loop,
936 // since it would go on forever.
937 std::vector<FieldTrial::FieldTrialRef> new_refs;
938
939 FieldTrial::FieldTrialRef prev_ref;
940 while ((prev_ref = mem_iter.GetNextOfType<FieldTrial::FieldTrialEntry>()) !=
941 FieldTrialAllocator::kReferenceNull) {
942 // Get the existing field trial entry in shared memory.
943 const FieldTrial::FieldTrialEntry* prev_entry =
944 allocator->GetAsObject<FieldTrial::FieldTrialEntry>(prev_ref);
945 std::string_view trial_name;
946 std::string_view group_name;
947 bool is_overridden;
948 if (!prev_entry->GetState(trial_name, group_name, is_overridden)) {
949 continue;
950 }
951
952 // Write a new entry, minus the params.
953 Pickle pickle;
954 pickle.WriteString(trial_name);
955 pickle.WriteString(group_name);
956 pickle.WriteBool(is_overridden);
957
958 if (prev_entry->pickle_size == pickle.size() &&
959 memcmp(prev_entry->GetPickledDataPtr(), pickle.data(), pickle.size()) ==
960 0) {
961 // If the new entry is going to be the exact same as the existing one,
962 // then simply keep the existing one to avoid taking extra space in the
963 // allocator. This should mean that this trial has no params.
964 std::map<std::string, std::string> params;
965 CHECK(prev_entry->GetParams(¶ms));
966 CHECK(params.empty());
967 continue;
968 }
969
970 size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size();
971 FieldTrial::FieldTrialEntry* new_entry =
972 allocator->New<FieldTrial::FieldTrialEntry>(total_size);
973 DCHECK(new_entry)
974 << "Failed to allocate a new entry, likely because the allocator is "
975 "full. Consider increasing kFieldTrialAllocationSize.";
976 subtle::NoBarrier_Store(&new_entry->activated,
977 subtle::NoBarrier_Load(&prev_entry->activated));
978 new_entry->pickle_size = pickle.size();
979
980 // TODO(lawrencewu): Modify base::Pickle to be able to write over a section
981 // in memory, so we can avoid this memcpy.
982 memcpy(new_entry->GetPickledDataPtr(), pickle.data(), pickle.size());
983
984 // Update the ref on the field trial and add it to the list to be made
985 // iterable.
986 FieldTrial::FieldTrialRef new_ref = allocator->GetAsReference(new_entry);
987 FieldTrial* trial = global_->PreLockedFind(trial_name);
988 trial->ref_ = new_ref;
989 new_refs.push_back(new_ref);
990
991 // Mark the existing entry as unused.
992 allocator->ChangeType(prev_ref, 0,
993 FieldTrial::FieldTrialEntry::kPersistentTypeId,
994 /*clear=*/false);
995 }
996
997 for (const auto& ref : new_refs) {
998 allocator->MakeIterable(ref);
999 }
1000 }
1001
1002 // static
DumpAllFieldTrialsToPersistentAllocator(PersistentMemoryAllocator * allocator)1003 void FieldTrialList::DumpAllFieldTrialsToPersistentAllocator(
1004 PersistentMemoryAllocator* allocator) {
1005 if (!global_)
1006 return;
1007 AutoLock auto_lock(global_->lock_);
1008 for (const auto& registered : global_->registered_) {
1009 AddToAllocatorWhileLocked(allocator, registered.second);
1010 }
1011 }
1012
1013 // static
1014 std::vector<const FieldTrial::FieldTrialEntry*>
GetAllFieldTrialsFromPersistentAllocator(PersistentMemoryAllocator const & allocator)1015 FieldTrialList::GetAllFieldTrialsFromPersistentAllocator(
1016 PersistentMemoryAllocator const& allocator) {
1017 std::vector<const FieldTrial::FieldTrialEntry*> entries;
1018 FieldTrialAllocator::Iterator iter(&allocator);
1019 const FieldTrial::FieldTrialEntry* entry;
1020 while ((entry = iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
1021 nullptr) {
1022 entries.push_back(entry);
1023 }
1024 return entries;
1025 }
1026
1027 // static
GetInstance()1028 FieldTrialList* FieldTrialList::GetInstance() {
1029 return global_;
1030 }
1031
1032 // static
ResetInstance()1033 FieldTrialList* FieldTrialList::ResetInstance() {
1034 FieldTrialList* instance = global_;
1035 instance->was_reset_ = true;
1036 global_ = nullptr;
1037 return instance;
1038 }
1039
1040 // static
BackupInstanceForTesting()1041 FieldTrialList* FieldTrialList::BackupInstanceForTesting() {
1042 FieldTrialList* instance = global_;
1043 global_ = nullptr;
1044 return instance;
1045 }
1046
1047 // static
RestoreInstanceForTesting(FieldTrialList * instance)1048 void FieldTrialList::RestoreInstanceForTesting(FieldTrialList* instance) {
1049 global_ = instance;
1050 }
1051
1052 #if BUILDFLAG(USE_BLINK)
1053
1054 // static
CreateTrialsFromSwitchValue(const std::string & switch_value)1055 SharedMemoryError FieldTrialList::CreateTrialsFromSwitchValue(
1056 const std::string& switch_value) {
1057 auto shm = shared_memory::ReadOnlySharedMemoryRegionFrom(switch_value);
1058 if (!shm.has_value()) {
1059 return shm.error();
1060 }
1061 if (!FieldTrialList::CreateTrialsFromSharedMemoryRegion(shm.value())) {
1062 return SharedMemoryError::kCreateTrialsFailed;
1063 }
1064 return SharedMemoryError::kNoError;
1065 }
1066
1067 #endif // BUILDFLAG(USE_BLINK)
1068
1069 // static
CreateTrialsFromSharedMemoryRegion(const ReadOnlySharedMemoryRegion & shm_region)1070 bool FieldTrialList::CreateTrialsFromSharedMemoryRegion(
1071 const ReadOnlySharedMemoryRegion& shm_region) {
1072 ReadOnlySharedMemoryMapping shm_mapping =
1073 shm_region.MapAt(0, kFieldTrialAllocationSize);
1074 if (!shm_mapping.IsValid())
1075 OnOutOfMemory(kFieldTrialAllocationSize);
1076
1077 return FieldTrialList::CreateTrialsFromSharedMemoryMapping(
1078 std::move(shm_mapping));
1079 }
1080
1081 // static
CreateTrialsFromSharedMemoryMapping(ReadOnlySharedMemoryMapping shm_mapping)1082 bool FieldTrialList::CreateTrialsFromSharedMemoryMapping(
1083 ReadOnlySharedMemoryMapping shm_mapping) {
1084 global_->field_trial_allocator_ =
1085 std::make_unique<ReadOnlySharedPersistentMemoryAllocator>(
1086 std::move(shm_mapping), 0, kAllocatorName);
1087 FieldTrialAllocator* shalloc = global_->field_trial_allocator_.get();
1088 FieldTrialAllocator::Iterator mem_iter(shalloc);
1089
1090 const FieldTrial::FieldTrialEntry* entry;
1091 while ((entry = mem_iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
1092 nullptr) {
1093 std::string_view trial_name;
1094 std::string_view group_name;
1095 bool is_overridden;
1096 if (!entry->GetState(trial_name, group_name, is_overridden)) {
1097 return false;
1098 }
1099 // TODO(crbug.com/1431156): Don't set is_low_anonymity=false, but instead
1100 // propagate the is_low_anonymity state to the child process.
1101 FieldTrial* trial = CreateFieldTrial(
1102 trial_name, group_name, /*is_low_anonymity=*/false, is_overridden);
1103 trial->ref_ = mem_iter.GetAsReference(entry);
1104 if (subtle::NoBarrier_Load(&entry->activated)) {
1105 // Mark the trial as "used" and notify observers, if any.
1106 // This is useful to ensure that field trials created in child
1107 // processes are properly reported in crash reports.
1108 trial->Activate();
1109 }
1110 }
1111 return true;
1112 }
1113
1114 // static
InstantiateFieldTrialAllocatorIfNeeded()1115 void FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded() {
1116 if (!global_)
1117 return;
1118
1119 AutoLock auto_lock(global_->lock_);
1120 // Create the allocator if not already created and add all existing trials.
1121 if (global_->field_trial_allocator_ != nullptr)
1122 return;
1123
1124 MappedReadOnlyRegion shm =
1125 ReadOnlySharedMemoryRegion::Create(kFieldTrialAllocationSize);
1126
1127 if (!shm.IsValid())
1128 OnOutOfMemory(kFieldTrialAllocationSize);
1129
1130 global_->field_trial_allocator_ =
1131 std::make_unique<WritableSharedPersistentMemoryAllocator>(
1132 std::move(shm.mapping), 0, kAllocatorName);
1133 global_->field_trial_allocator_->CreateTrackingHistograms(kAllocatorName);
1134
1135 // Add all existing field trials.
1136 for (const auto& registered : global_->registered_) {
1137 AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(),
1138 registered.second);
1139 }
1140
1141 // Add all existing features.
1142 FeatureList::GetInstance()->AddFeaturesToAllocator(
1143 global_->field_trial_allocator_.get());
1144
1145 global_->readonly_allocator_region_ = std::move(shm.region);
1146 }
1147
1148 // static
AddToAllocatorWhileLocked(PersistentMemoryAllocator * allocator,FieldTrial * field_trial)1149 void FieldTrialList::AddToAllocatorWhileLocked(
1150 PersistentMemoryAllocator* allocator,
1151 FieldTrial* field_trial) {
1152 // Don't do anything if the allocator hasn't been instantiated yet.
1153 if (allocator == nullptr)
1154 return;
1155
1156 // Or if the allocator is read only, which means we are in a child process and
1157 // shouldn't be writing to it.
1158 if (allocator->IsReadonly())
1159 return;
1160
1161 FieldTrial::PickleState trial_state;
1162 field_trial->GetStateWhileLocked(&trial_state);
1163
1164 // Or if we've already added it. We must check after GetState since it can
1165 // also add to the allocator.
1166 if (field_trial->ref_)
1167 return;
1168
1169 Pickle pickle;
1170 PickleFieldTrial(trial_state, &pickle);
1171
1172 size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size();
1173 FieldTrial::FieldTrialRef ref = allocator->Allocate(
1174 total_size, FieldTrial::FieldTrialEntry::kPersistentTypeId);
1175 if (ref == FieldTrialAllocator::kReferenceNull) {
1176 NOTREACHED();
1177 return;
1178 }
1179
1180 FieldTrial::FieldTrialEntry* entry =
1181 allocator->GetAsObject<FieldTrial::FieldTrialEntry>(ref);
1182 subtle::NoBarrier_Store(&entry->activated, trial_state.activated);
1183 entry->pickle_size = pickle.size();
1184
1185 // TODO(lawrencewu): Modify base::Pickle to be able to write over a section in
1186 // memory, so we can avoid this memcpy.
1187 memcpy(entry->GetPickledDataPtr(), pickle.data(), pickle.size());
1188
1189 allocator->MakeIterable(ref);
1190 field_trial->ref_ = ref;
1191 }
1192
1193 // static
ActivateFieldTrialEntryWhileLocked(FieldTrial * field_trial)1194 void FieldTrialList::ActivateFieldTrialEntryWhileLocked(
1195 FieldTrial* field_trial) {
1196 FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
1197
1198 // Check if we're in the child process and return early if so.
1199 if (!allocator || allocator->IsReadonly())
1200 return;
1201
1202 FieldTrial::FieldTrialRef ref = field_trial->ref_;
1203 if (ref == FieldTrialAllocator::kReferenceNull) {
1204 // It's fine to do this even if the allocator hasn't been instantiated
1205 // yet -- it'll just return early.
1206 AddToAllocatorWhileLocked(allocator, field_trial);
1207 } else {
1208 // It's also okay to do this even though the callee doesn't have a lock --
1209 // the only thing that happens on a stale read here is a slight performance
1210 // hit from the child re-synchronizing activation state.
1211 FieldTrial::FieldTrialEntry* entry =
1212 allocator->GetAsObject<FieldTrial::FieldTrialEntry>(ref);
1213 subtle::NoBarrier_Store(&entry->activated, 1);
1214 }
1215 }
1216
PreLockedFind(std::string_view name)1217 FieldTrial* FieldTrialList::PreLockedFind(std::string_view name) {
1218 auto it = registered_.find(name);
1219 if (registered_.end() == it)
1220 return nullptr;
1221 return it->second;
1222 }
1223
1224 // static
Register(FieldTrial * trial,bool is_randomized_trial)1225 void FieldTrialList::Register(FieldTrial* trial, bool is_randomized_trial) {
1226 DCHECK(global_);
1227
1228 AutoLock auto_lock(global_->lock_);
1229 CHECK(!global_->PreLockedFind(trial->trial_name())) << trial->trial_name();
1230 trial->AddRef();
1231 trial->SetTrialRegistered();
1232 global_->registered_[trial->trial_name()] = trial;
1233
1234 if (is_randomized_trial)
1235 ++global_->num_registered_randomized_trials_;
1236 }
1237
1238 // static
GetRegisteredTrials()1239 FieldTrialList::RegistrationMap FieldTrialList::GetRegisteredTrials() {
1240 RegistrationMap output;
1241 if (global_) {
1242 AutoLock auto_lock(global_->lock_);
1243 output = global_->registered_;
1244 }
1245 return output;
1246 }
1247
1248 // static
CreateTrialsFromFieldTrialStatesInternal(const std::vector<FieldTrial::State> & entries)1249 bool FieldTrialList::CreateTrialsFromFieldTrialStatesInternal(
1250 const std::vector<FieldTrial::State>& entries) {
1251 DCHECK(global_);
1252
1253 for (const auto& entry : entries) {
1254 FieldTrial* trial =
1255 CreateFieldTrial(entry.trial_name, entry.group_name,
1256 /*is_low_anonymity=*/false, entry.is_overridden);
1257 if (!trial)
1258 return false;
1259 if (entry.activated) {
1260 // Mark the trial as "used" and notify observers, if any.
1261 // This is useful to ensure that field trials created in child
1262 // processes are properly reported in crash reports.
1263 trial->Activate();
1264 }
1265 }
1266 return true;
1267 }
1268
1269 // static
GetActiveFieldTrialGroupsInternal(FieldTrial::ActiveGroups * active_groups,bool include_low_anonymity)1270 void FieldTrialList::GetActiveFieldTrialGroupsInternal(
1271 FieldTrial::ActiveGroups* active_groups,
1272 bool include_low_anonymity) {
1273 DCHECK(active_groups->empty());
1274 if (!global_) {
1275 return;
1276 }
1277 AutoLock auto_lock(global_->lock_);
1278
1279 for (const auto& registered : global_->registered_) {
1280 const FieldTrial& trial = *registered.second;
1281 FieldTrial::ActiveGroup active_group;
1282 if ((include_low_anonymity || !trial.is_low_anonymity_) &&
1283 trial.GetActiveGroup(&active_group)) {
1284 active_groups->push_back(active_group);
1285 }
1286 }
1287 }
1288
1289 // static
AddObserverInternal(Observer * observer,bool include_low_anonymity)1290 bool FieldTrialList::AddObserverInternal(Observer* observer,
1291 bool include_low_anonymity) {
1292 if (!global_) {
1293 return false;
1294 }
1295 AutoLock auto_lock(global_->lock_);
1296 if (include_low_anonymity) {
1297 global_->observers_including_low_anonymity_.push_back(observer);
1298 } else {
1299 global_->observers_.push_back(observer);
1300 }
1301 return true;
1302 }
1303
1304 // static
RemoveObserverInternal(Observer * observer,bool include_low_anonymity)1305 void FieldTrialList::RemoveObserverInternal(Observer* observer,
1306 bool include_low_anonymity) {
1307 if (!global_) {
1308 return;
1309 }
1310 AutoLock auto_lock(global_->lock_);
1311 std::erase(include_low_anonymity ? global_->observers_including_low_anonymity_
1312 : global_->observers_,
1313 observer);
1314 DCHECK_EQ(global_->num_ongoing_notify_field_trial_group_selection_calls_, 0)
1315 << "Cannot call RemoveObserver while accessing FieldTrial::group_name().";
1316 }
1317
1318 } // namespace base
1319