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