Commit 30f7575a authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

Disallow FeatureList overrides after early setup

This changes BrowserTestBase::SetUp to disallow FeatureList overrides
for the remaining duration of the test. Catches incorrect usage of
ScopedFeatureList which can cause data races.

Bug: 846380
Change-Id: Ie76d59e191edc3ac05c96131e83e5aa0b73dfda7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1842111
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarAlexei Svitkine <asvitkine@chromium.org>
Reviewed-by: default avatardanakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#705389}
parent e6873af0
...@@ -29,6 +29,17 @@ FeatureList* g_feature_list_instance = nullptr; ...@@ -29,6 +29,17 @@ FeatureList* g_feature_list_instance = nullptr;
// Tracks whether the FeatureList instance was initialized via an accessor. // Tracks whether the FeatureList instance was initialized via an accessor.
bool g_initialized_from_accessor = false; bool g_initialized_from_accessor = false;
#if DCHECK_IS_ON()
const char* g_reason_overrides_disallowed = nullptr;
void DCheckOverridesAllowed() {
const bool feature_overrides_allowed = !g_reason_overrides_disallowed;
DCHECK(feature_overrides_allowed) << g_reason_overrides_disallowed;
}
#else
void DCheckOverridesAllowed() {}
#endif
// An allocator entry for a feature in shared memory. The FeatureEntry is // An allocator entry for a feature in shared memory. The FeatureEntry is
// followed by a base::Pickle object that contains the feature and trial name. // followed by a base::Pickle object that contains the feature and trial name.
struct FeatureEntry { struct FeatureEntry {
...@@ -86,6 +97,23 @@ FeatureList::FeatureList() = default; ...@@ -86,6 +97,23 @@ FeatureList::FeatureList() = default;
FeatureList::~FeatureList() = default; FeatureList::~FeatureList() = default;
FeatureList::ScopedDisallowOverrides::ScopedDisallowOverrides(
const char* reason)
#if DCHECK_IS_ON()
: previous_reason_(g_reason_overrides_disallowed) {
g_reason_overrides_disallowed = reason;
}
#else
{
}
#endif
FeatureList::ScopedDisallowOverrides::~ScopedDisallowOverrides() {
#if DCHECK_IS_ON()
g_reason_overrides_disallowed = previous_reason_;
#endif
}
void FeatureList::InitializeFromCommandLine( void FeatureList::InitializeFromCommandLine(
const std::string& enable_features, const std::string& enable_features,
const std::string& disable_features) { const std::string& disable_features) {
...@@ -385,6 +413,7 @@ void FeatureList::RegisterOverride(StringPiece feature_name, ...@@ -385,6 +413,7 @@ void FeatureList::RegisterOverride(StringPiece feature_name,
OverrideState overridden_state, OverrideState overridden_state,
FieldTrial* field_trial) { FieldTrial* field_trial) {
DCHECK(!initialized_); DCHECK(!initialized_);
DCheckOverridesAllowed();
if (field_trial) { if (field_trial) {
DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name())) DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name()))
<< field_trial->trial_name(); << field_trial->trial_name();
......
...@@ -100,6 +100,21 @@ class BASE_EXPORT FeatureList { ...@@ -100,6 +100,21 @@ class BASE_EXPORT FeatureList {
FeatureList(); FeatureList();
~FeatureList(); ~FeatureList();
// Used by common test fixture classes to prevent abuse of ScopedFeatureList
// after multiple threads have started.
class BASE_EXPORT ScopedDisallowOverrides {
public:
explicit ScopedDisallowOverrides(const char* reason);
~ScopedDisallowOverrides();
private:
#if DCHECK_IS_ON()
const char* const previous_reason_;
#endif
DISALLOW_COPY_AND_ASSIGN(ScopedDisallowOverrides);
};
// Specifies whether a feature override enables or disables the feature. // Specifies whether a feature override enables or disables the feature.
enum OverrideState { enum OverrideState {
OVERRIDE_USE_DEFAULT, OVERRIDE_USE_DEFAULT,
......
...@@ -26,13 +26,19 @@ namespace test { ...@@ -26,13 +26,19 @@ namespace test {
// list and initialize it anew, destroy an existing scoped list and init // list and initialize it anew, destroy an existing scoped list and init
// a new one. // a new one.
// //
// If multiple instances of this class are used in a nested fashion, they
// should be destroyed in the opposite order of their Init*() methods being
// called.
//
// ScopedFeatureList needs to be initialized (via one of Init*() methods) // ScopedFeatureList needs to be initialized (via one of Init*() methods)
// before running code that inspects the state of features, such as in the // before running code that inspects the state of features, such as in the
// constructor of the test harness. // constructor of the test harness.
// //
// If multiple instances of this class are used in a nested fashion, they // WARNING: To be clear, in multithreaded test environments (such as browser
// should be destroyed in the opposite order of their Init*() methods being // tests) there may background threads using FeatureList before the test body is
// called. // even entered. In these cases it is imperative that ScopedFeatureList be
// initialized BEFORE those threads are started, hence the recommendation to do
// initialization in the test harness's constructor.
class ScopedFeatureList final { class ScopedFeatureList final {
public: public:
ScopedFeatureList(); ScopedFeatureList();
......
...@@ -20,13 +20,14 @@ ...@@ -20,13 +20,14 @@
class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest { class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest {
public: public:
SpellCheckHostChromeImplWinBrowserTest() {
feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
}
void SetUpOnMainThread() override { void SetUpOnMainThread() override {
content::BrowserContext* context = browser()->profile(); content::BrowserContext* context = browser()->profile();
renderer_.reset(new content::MockRenderProcessHost(context)); renderer_.reset(new content::MockRenderProcessHost(context));
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
SpellCheckHostChromeImpl::Create( SpellCheckHostChromeImpl::Create(
renderer_->GetID(), spell_check_host_.BindNewPipeAndPassReceiver()); renderer_->GetID(), spell_check_host_.BindNewPipeAndPassReceiver());
} }
...@@ -58,6 +59,7 @@ class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest { ...@@ -58,6 +59,7 @@ class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest {
} }
protected: protected:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<content::MockRenderProcessHost> renderer_; std::unique_ptr<content::MockRenderProcessHost> renderer_;
mojo::Remote<spellcheck::mojom::SpellCheckHost> spell_check_host_; mojo::Remote<spellcheck::mojom::SpellCheckHost> spell_check_host_;
...@@ -72,9 +74,6 @@ IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest, ...@@ -72,9 +74,6 @@ IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest,
if (base::win::GetVersion() < base::win::Version::WIN8) if (base::win::GetVersion() < base::win::Version::WIN8)
return; return;
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
spellcheck_platform::SetLanguage( spellcheck_platform::SetLanguage(
"en-US", base::BindOnce(&SpellCheckHostChromeImplWinBrowserTest:: "en-US", base::BindOnce(&SpellCheckHostChromeImplWinBrowserTest::
SetLanguageCompletionCallback, SetLanguageCompletionCallback,
......
...@@ -427,6 +427,12 @@ void BrowserTestBase::SetUp() { ...@@ -427,6 +427,12 @@ void BrowserTestBase::SetUp() {
InitializeBrowserMemoryInstrumentationClient(); InitializeBrowserMemoryInstrumentationClient();
} }
// All FeatureList overrides should have been registered prior to browser test
// SetUp().
base::FeatureList::ScopedDisallowOverrides disallow_feature_overrides(
"FeatureList overrides must happen in the test constructor, before "
"BrowserTestBase::SetUp() has run.");
auto discardable_shared_memory_manager = auto discardable_shared_memory_manager =
std::make_unique<discardable_memory::DiscardableSharedMemoryManager>(); std::make_unique<discardable_memory::DiscardableSharedMemoryManager>();
auto service_manager_env = std::make_unique<ServiceManagerEnvironment>( auto service_manager_env = std::make_unique<ServiceManagerEnvironment>(
...@@ -581,6 +587,14 @@ std::string GetDefaultTraceFilaneme() { ...@@ -581,6 +587,14 @@ std::string GetDefaultTraceFilaneme() {
} // namespace } // namespace
void BrowserTestBase::ProxyRunTestOnMainThreadLoop() { void BrowserTestBase::ProxyRunTestOnMainThreadLoop() {
#if !defined(OS_ANDROID)
// All FeatureList overrides should have been registered prior to browser test
// SetUp(). Note that on Android, this scoper lives in SetUp() above.
base::FeatureList::ScopedDisallowOverrides disallow_feature_overrides(
"FeatureList overrides must happen in the test constructor, before "
"BrowserTestBase::SetUp() has run.");
#endif
// Install a RunLoop timeout if none is present but do not override tests that // Install a RunLoop timeout if none is present but do not override tests that
// set a ScopedRunTimeoutForTest from their fixture's constructor (which // set a ScopedRunTimeoutForTest from their fixture's constructor (which
// happens as part of setting up the test factory in gtest while // happens as part of setting up the test factory in gtest while
......
...@@ -32,21 +32,12 @@ class BrowserTestBase : public testing::Test { ...@@ -32,21 +32,12 @@ class BrowserTestBase : public testing::Test {
BrowserTestBase(); BrowserTestBase();
~BrowserTestBase() override; ~BrowserTestBase() override;
// Configures everything for an in process browser test (i.e. feature list, // Configures everything for an in process browser test (e.g. thread pool,
// thread pool, etc.) by invoking ContentMain (or manually on OS_ANDROID). As // etc.) by invoking ContentMain (or manually on OS_ANDROID). As such all
// such all single-threaded initialization (such as // single-threaded initialization must be done before this step.
// base::test::ScopedFeatureList) must be done before this step, i.e.:
// class MyFixture : public content::BrowserTestBase {
// public:
// void SetUp() override {
// scoped_feature_list_.InitWithFeatures(features::kMyFeature);
// content::BrowserTestBase::SetUp();
// }
// //
// private: // ContentMain then ends up invoking RunTestOnMainThreadLoop with browser
// base::test::ScopedFeatureList scoped_feature_list_; // threads already running.
// };
// ContentMain then ends up invoking RunTestOnMainThreadLoop.
void SetUp() override; void SetUp() override;
// Restores state configured in SetUp. // Restores state configured in SetUp.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment