1 // Copyright 2019 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.
5 #include "chrome/browser/unexpire_flags.h"
7 #include "base/containers/contains.h"
8 #include "base/containers/flat_map.h"
9 #include "base/no_destructor.h"
10 #include "chrome/browser/expired_flags_list.h"
11 #include "chrome/browser/unexpire_flags_gen.h"
12 #include "chrome/common/chrome_version.h"
13 #include "components/flags_ui/flags_storage.h"
19 using FlagNameToExpirationMap = base::flat_map<std::string, int>;
21 static FlagNameToExpirationMap* GetFlagExpirationOverrideMap() {
22 static base::NoDestructor<FlagNameToExpirationMap> map;
26 int ExpirationMilestoneForFlag(const char* flag) {
27 if (base::Contains(*GetFlagExpirationOverrideMap(), flag)) {
28 return GetFlagExpirationOverrideMap()->at(flag);
31 for (int i = 0; kExpiredFlags[i].name; ++i) {
32 const ExpiredFlag* f = &kExpiredFlags[i];
33 if (strcmp(f->name, flag)) {
37 // To keep the size of the expired flags list down,
38 // //tools/flags/generate_expired_flags.py doesn't emit flags with expiry
39 // mstone -1; it makes no sense for these flags to be in the expiry list
40 // anyway. However, if a bug did cause that to happen, and this function
41 // didn't handle that case, all flags with expiration -1 would immediately
42 // expire, which would be very bad. As such there's an extra error-check
43 // here: a DCHECK to catch bugs in the script, and a regular if to ensure we
44 // never expire flags that should never expire.
45 DCHECK_NE(f->mstone, -1);
51 // This function is a nasty hack - normally, the logic to turn flags into
52 // feature names happens inside flags_ui::FlagsState, but this function is used
53 // from the setup code of FlagsState, so it can't rely on FlagsState having been
54 // set up. As such, we look into the backing FlagsStorage and hardcode how
55 // enabled flags look inside that storage.
56 std::set<int> UnexpiredMilestonesFromStorage(
57 const flags_ui::FlagsStorage* storage) {
58 std::set<int> unexpired;
59 for (const auto& f : storage->GetFlags()) {
61 if (sscanf(f.c_str(), "temporary-unexpire-flags-m%d@1", &mstone) == 1) {
62 unexpired.insert(mstone);
70 bool IsFlagExpired(const flags_ui::FlagsStorage* storage,
71 const char* internal_name) {
74 int mstone = ExpirationMilestoneForFlag(internal_name);
79 // This is extremely horrible:
81 // In order to know if a flag is expired or not, normally this function
82 // queries the state of base::FeatureList to check whether the unexpire
83 // feature for that milestone is enabled. However, when *creating* the initial
84 // base::FeatureList instance, these features won't be initialized yet, which
85 // leads to this issue:
87 // * Assume a flag "foo-bar" for feature FooBar that expires in M83.
88 // * Also, assume that temporary-unexpire-flags-m83 is enabled.
90 // If both of those are true, then if IsFlagExpired("foo-bar") is called
91 // *during* initial feature list setup, it will return true rather than false,
92 // which will cause FooBar to be set to its default rather than the
93 // non-default value that the flag may be to. This happens because the
94 // TemporaryUnexpireFlagsM83 feature hasn't been initialized yet, so it gets
95 // treated as its default state (disabled).
97 // To deal with that and make this function behave more correctly during
98 // FeatureList initialization, also consult the backing FlagsStorage from the
99 // FlagsState and look at the temporary-unexpire-flags-m$M flags directly, as
100 // well as looking at their features.
102 // This still has a problem: during browser startup, if the unexpire feature
103 // will be configured by some other mechanism (group policy, etc), that
104 // feature's value won't apply in time here and the bug described will happen.
105 // In fact, that is a design behavior of the feature system, since flag
106 // unexpiry happens during FeatureList initialization.
107 // TODO(ellyjones): what might we do about that?
108 std::set<int> unexpired_milestones = UnexpiredMilestonesFromStorage(storage);
109 if (base::Contains(unexpired_milestones, mstone)) {
113 // Otherwise, the flag is expired if its expiration mstone is less than the
114 // mstone of this copy of Chromium.
115 return mstone < CHROME_VERSION_MAJOR;
120 void SetFlagExpiration(const std::string& name, int mstone) {
121 GetFlagExpirationOverrideMap()->insert_or_assign(name, mstone);
124 } // namespace testing