| // Copyright 2019 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/unexpire_flags.h" | 
 |  | 
 | #include "base/containers/flat_map.h" | 
 | #include "base/no_destructor.h" | 
 | #include "chrome/browser/expired_flags_list.h" | 
 | #include "chrome/browser/unexpire_flags_gen.h" | 
 | #include "chrome/common/chrome_version.h" | 
 | #include "components/flags_ui/flags_storage.h" | 
 |  | 
 | namespace flags { | 
 |  | 
 | namespace { | 
 |  | 
 | using FlagNameToExpirationMap = base::flat_map<std::string, int>; | 
 |  | 
 | static FlagNameToExpirationMap* GetFlagExpirationOverrideMap() { | 
 |   static base::NoDestructor<FlagNameToExpirationMap> map; | 
 |   return map.get(); | 
 | } | 
 |  | 
 | int ExpirationMilestoneForFlag(const char* flag) { | 
 |   if (base::Contains(*GetFlagExpirationOverrideMap(), flag)) | 
 |     return GetFlagExpirationOverrideMap()->at(flag); | 
 |   for (int i = 0; kExpiredFlags[i].name; ++i) { | 
 |     const ExpiredFlag* f = &kExpiredFlags[i]; | 
 |     if (strcmp(f->name, flag)) | 
 |       continue; | 
 |  | 
 |     // To keep the size of the expired flags list down, | 
 |     // //tools/flags/generate_expired_flags.py doesn't emit flags with expiry | 
 |     // mstone -1; it makes no sense for these flags to be in the expiry list | 
 |     // anyway. However, if a bug did cause that to happen, and this function | 
 |     // didn't handle that case, all flags with expiration -1 would immediately | 
 |     // expire, which would be very bad. As such there's an extra error-check | 
 |     // here: a DCHECK to catch bugs in the script, and a regular if to ensure we | 
 |     // never expire flags that should never expire. | 
 |     DCHECK_NE(f->mstone, -1); | 
 |     return f->mstone; | 
 |   } | 
 |   return -1; | 
 | } | 
 |  | 
 | // This function is a nasty hack - normally, the logic to turn flags into | 
 | // feature names happens inside flags_ui::FlagsState, but this function is used | 
 | // from the setup code of FlagsState, so it can't rely on FlagsState having been | 
 | // set up. As such, we look into the backing FlagsStorage and hardcode how | 
 | // enabled flags look inside that storage. | 
 | std::set<int> UnexpiredMilestonesFromStorage( | 
 |     const flags_ui::FlagsStorage* storage) { | 
 |   std::set<int> unexpired; | 
 |   for (const auto& f : storage->GetFlags()) { | 
 |     int mstone; | 
 |     if (sscanf(f.c_str(), "temporary-unexpire-flags-m%d@1", &mstone) == 1) | 
 |       unexpired.insert(mstone); | 
 |   } | 
 |   return unexpired; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | bool IsFlagExpired(const flags_ui::FlagsStorage* storage, | 
 |                    const char* internal_name) { | 
 |   DCHECK(storage); | 
 |   constexpr int kChromeVersion[] = {CHROME_VERSION}; | 
 |   constexpr int kChromeVersionMajor = kChromeVersion[0]; | 
 |  | 
 |   int mstone = ExpirationMilestoneForFlag(internal_name); | 
 |  | 
 |   if (mstone == -1) | 
 |     return false; | 
 |  | 
 |   // This is extremely horrible: | 
 |   // | 
 |   // In order to know if a flag is expired or not, normally this function | 
 |   // queries the state of base::FeatureList to check whether the unexpire | 
 |   // feature for that milestone is enabled. However, when *creating* the initial | 
 |   // base::FeatureList instance, these features won't be initialized yet, which | 
 |   // leads to this issue: | 
 |   // | 
 |   // * Assume a flag "foo-bar" for feature FooBar that expires in M83. | 
 |   // * Also, assume that temporary-unexpire-flags-m83 is enabled. | 
 |   // | 
 |   // If both of those are true, then if IsFlagExpired("foo-bar") is called | 
 |   // *during* initial feature list setup, it will return true rather than false, | 
 |   // which will cause FooBar to be set to its default rather than the | 
 |   // non-default value that the flag may be to. This happens because the | 
 |   // TemporaryUnexpireFlagsM83 feature hasn't been initialized yet, so it gets | 
 |   // treated as its default state (disabled). | 
 |   // | 
 |   // To deal with that and make this function behave more correctly during | 
 |   // FeatureList initialization, also consult the backing FlagsStorage from the | 
 |   // FlagsState and look at the temporary-unexpire-flags-m$M flags directly, as | 
 |   // well as looking at their features. | 
 |   // | 
 |   // This still has a problem: during browser startup, if the unexpire feature | 
 |   // will be configured by some other mechanism (group policy, etc), that | 
 |   // feature's value won't apply in time here and the bug described will happen. | 
 |   // TODO(ellyjones): Figure out how to fix that. | 
 |   std::set<int> unexpired_milestones = UnexpiredMilestonesFromStorage(storage); | 
 |   if (base::Contains(unexpired_milestones, mstone)) | 
 |     return false; | 
 |  | 
 |   const base::Feature* expiry_feature = GetUnexpireFeatureForMilestone(mstone); | 
 |  | 
 |   // If there's an unexpiry feature, and the unexpiry feature is *disabled*, | 
 |   // then the flag is expired. The double-negative is very unfortunate. | 
 |   if (expiry_feature) | 
 |     return !base::FeatureList::IsEnabled(*expiry_feature); | 
 |   return mstone < kChromeVersionMajor; | 
 | } | 
 |  | 
 | namespace testing { | 
 |  | 
 | void SetFlagExpiration(const std::string& name, int mstone) { | 
 |   GetFlagExpirationOverrideMap()->insert_or_assign(name, mstone); | 
 | } | 
 |  | 
 | }  // namespace testing | 
 |  | 
 | }  // namespace flags |