[v8] Pass through prefixed feature flags to V8
V8 has its own command line flag mechanism, and V8-relevant base::Feature flags are defined in gin and passed through to V8 during gin initialization. This has the consequence that V8 flags need to be defined twice to be controllable via Chromium's variations mechanism -- once in V8 for their functionality, and once in Blink/gin just for pass-through. This adds overhead when wanting to A/B test V8 flags, and can cause issues if an enabled V8 flag needs to be disabled via kill switch (namely, we have to make sure that there exists a base::Feature equivalent of the flag that we can disable). It also causes some confusion about where the canonical default feature state exists (in Blink or V8). This CL introduces a generic `V8Flags_` prefix for features in the FeatureList. The gin initializer walks all feature overrides, and any `V8Flags_foo` feature that is overridden to enabled is passed through to V8 as a `--foo` flag (and disabled features are passed through as `--no-foo`). This will allow us to, effectively, expose all V8 flags as a pseudo base::Feature, without needing Chromium-side changes to expose such flags to Finch. Change-Id: Id88beafb8688d9f046706b0a67e23ad94d08a7a9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5873788 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Nico Weber <thakis@chromium.org> Auto-Submit: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#1361715}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
0d03831405
commit
f7f53b43e9
base
gin
tools/v8_context_snapshot
@ -294,6 +294,7 @@ component("base") {
|
||||
"export_template.h",
|
||||
"feature_list.cc",
|
||||
"feature_list.h",
|
||||
"feature_visitor.h",
|
||||
"features.cc",
|
||||
"features.h",
|
||||
"file_version_info.h",
|
||||
@ -1002,10 +1003,6 @@ component("base") {
|
||||
]
|
||||
}
|
||||
|
||||
if (is_chromeos_ash) {
|
||||
sources += [ "feature_visitor.h" ]
|
||||
}
|
||||
|
||||
if (is_linux || is_chromeos || is_android) {
|
||||
sources += [
|
||||
"files/file_path_watcher_inotify.cc",
|
||||
|
@ -2,24 +2,27 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/location.h"
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "base/feature_list.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "base/base_switches.h"
|
||||
#include "base/check_is_test.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/containers/span.h"
|
||||
#include "base/debug/crash_logging.h"
|
||||
#include "base/debug/dump_without_crashing.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/feature_visitor.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
@ -37,10 +40,6 @@
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
#include "base/feature_visitor.h"
|
||||
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace {
|
||||
@ -687,14 +686,39 @@ void FeatureList::AddEarlyAllowedFeatureForTesting(std::string feature_name) {
|
||||
allowed_feature_names_.insert(std::move(feature_name));
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
// static
|
||||
void FeatureList::VisitFeaturesAndParams(FeatureVisitor& visitor) {
|
||||
CHECK(g_feature_list_instance);
|
||||
void FeatureList::VisitFeaturesAndParams(FeatureVisitor& visitor,
|
||||
std::string_view filter_prefix) {
|
||||
// If there is no feature list, there are no overrides. This should only happen
|
||||
// in tests.
|
||||
// TODO(leszeks): Add a CHECK_IS_TEST() to verify the above.
|
||||
if (!g_feature_list_instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
FieldTrialParamAssociator* params_associator =
|
||||
FieldTrialParamAssociator::GetInstance();
|
||||
|
||||
for (auto& feature_override : g_feature_list_instance->overrides_) {
|
||||
using FeatureOverride = std::pair<std::string, OverrideEntry>;
|
||||
base::span<FeatureOverride> filtered_overrides(
|
||||
g_feature_list_instance->overrides_);
|
||||
if (!filter_prefix.empty()) {
|
||||
// If there is a filter prefix, then change the begin/end range to be the
|
||||
// range where the values are prefixed with the given prefix (overrides are
|
||||
// lexically sorted, so this will be a continuous range). This is
|
||||
// implemented as a binary search of the upper and lower bounds of the
|
||||
// override iterator, projecting each iterator value to just the
|
||||
// key, trimmed to the length of the prefix.
|
||||
DCHECK(std::ranges::is_sorted(
|
||||
filtered_overrides, std::less<>(),
|
||||
[](const FeatureOverride& entry) { return entry.first; }));
|
||||
filtered_overrides = std::ranges::equal_range(
|
||||
filtered_overrides, filter_prefix, std::less<>(),
|
||||
[filter_prefix](const FeatureOverride& entry) {
|
||||
return std::string_view(entry.first).substr(0, filter_prefix.size());
|
||||
});
|
||||
}
|
||||
for (const FeatureOverride& feature_override : filtered_overrides) {
|
||||
FieldTrial* field_trial = feature_override.second.field_trial;
|
||||
|
||||
std::string trial_name;
|
||||
@ -712,7 +736,6 @@ void FeatureList::VisitFeaturesAndParams(FeatureVisitor& visitor) {
|
||||
group_name);
|
||||
}
|
||||
}
|
||||
#endif // BULDFLAG(IS_CHROMEOS_ASH)
|
||||
|
||||
void FeatureList::FinalizeInitialization() {
|
||||
DCHECK(!initialized_);
|
||||
|
@ -26,17 +26,13 @@
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
class FieldTrial;
|
||||
class FieldTrialList;
|
||||
class PersistentMemoryAllocator;
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
class FeatureVisitor;
|
||||
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
|
||||
// Specifies whether a given feature is enabled or disabled by default.
|
||||
// NOTE: The actual runtime state may be different, due to a field trial or a
|
||||
@ -579,15 +575,15 @@ class BASE_EXPORT FeatureList {
|
||||
// only be called on a FeatureList that was set with SetEarlyAccessInstance().
|
||||
void AddEarlyAllowedFeatureForTesting(std::string feature_name);
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
// Allows a visitor to record override state, parameters, and field trial
|
||||
// associated with each feature.
|
||||
// associated with each feature. Optionally, provide a prefix which filters
|
||||
// the visited features.
|
||||
//
|
||||
// NOTE: This is intended only for the special case of needing to get all
|
||||
// overrides. This use case is specific to CrOS-Ash. Most users should call
|
||||
// IsEnabled() to query a feature's state.
|
||||
static void VisitFeaturesAndParams(FeatureVisitor& visitor);
|
||||
#endif // BULDFLAG(IS_CHROMEOS_ASH)
|
||||
// overrides. This use case is specific to CrOS-Ash and V8. Most users should
|
||||
// call IsEnabled() to query a feature's state.
|
||||
static void VisitFeaturesAndParams(FeatureVisitor& visitor,
|
||||
std::string_view filter_prefix = "");
|
||||
|
||||
private:
|
||||
FRIEND_TEST_ALL_PREFIXES(FeatureListTest, CheckFeatureIdentity);
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/feature_list_buildflags.h"
|
||||
#include "base/feature_visitor.h"
|
||||
#include "base/format_macros.h"
|
||||
#include "base/memory/read_only_shared_memory_region.h"
|
||||
#include "base/metrics/field_trial.h"
|
||||
@ -32,10 +33,6 @@
|
||||
#include "build/chromeos_buildflags.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
#include "base/feature_visitor.h"
|
||||
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace {
|
||||
@ -851,7 +848,6 @@ TEST(FeatureListAccessorTest, InitFromCommandLineWithFeatureParams) {
|
||||
}
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
// Test only class to verify correctness of
|
||||
// FeatureList::VisitFeaturesAndParams().
|
||||
class TestFeatureVisitor : public FeatureVisitor {
|
||||
@ -979,6 +975,35 @@ TEST(TestFeatureVisitor, FeatureHasParams) {
|
||||
|
||||
EXPECT_EQ(actual_feature_state, expected_feature_state);
|
||||
}
|
||||
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
|
||||
TEST(TestFeatureVisitor, FeatureWithPrefix) {
|
||||
base::test::ScopedFeatureList outer_scope;
|
||||
outer_scope.InitWithEmptyFeatureAndFieldTrialLists();
|
||||
|
||||
base::test::ScopedFeatureList initialized_feature_list;
|
||||
|
||||
initialized_feature_list.InitFromCommandLine(
|
||||
/*enable_features=*/
|
||||
"AFeature,AnotherFeature,TestFeature,TestFeature2,PrefixedFeature,"
|
||||
"PrefixedFeature2",
|
||||
/*disable_features=*/"");
|
||||
|
||||
TestFeatureVisitor visitor;
|
||||
base::FeatureList::VisitFeaturesAndParams(visitor, "Prefixed");
|
||||
std::multiset<TestFeatureVisitor::VisitedFeatureState> actual_feature_state =
|
||||
visitor.feature_state();
|
||||
|
||||
std::multiset<TestFeatureVisitor::VisitedFeatureState>
|
||||
expected_feature_state = {
|
||||
{"PrefixedFeature",
|
||||
FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
|
||||
FieldTrialParams{}, "", ""},
|
||||
{"PrefixedFeature2",
|
||||
FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
|
||||
FieldTrialParams{}, "", ""},
|
||||
};
|
||||
|
||||
EXPECT_EQ(actual_feature_state, expected_feature_state);
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
@ -9,21 +9,21 @@
|
||||
#include <string>
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
|
||||
static_assert(BUILDFLAG(IS_CHROMEOS_ASH), "For ChromeOS ash-chrome only");
|
||||
|
||||
namespace variations::cros_early_boot::evaluate_seed {
|
||||
class EarlyBootFeatureVisitor;
|
||||
}
|
||||
|
||||
namespace gin {
|
||||
class V8FeatureVisitor;
|
||||
}
|
||||
|
||||
namespace base {
|
||||
|
||||
class TestFeatureVisitor;
|
||||
|
||||
// An interface for EarlyBootFeatureVisitor that provides a method to
|
||||
// iterate over a feature's name, override state, parameters, and associated
|
||||
// field trial.
|
||||
// An interface for FeatureList that provides a method to iterate over a
|
||||
// feature's name, override state, parameters, and associated field trial.
|
||||
//
|
||||
// NOTE: This is intended only for the special case of needing to get all
|
||||
// feature overrides. Most users should call FeatureList::IsEnabled() to query
|
||||
@ -45,6 +45,7 @@ class FeatureVisitor {
|
||||
|
||||
private:
|
||||
friend variations::cros_early_boot::evaluate_seed::EarlyBootFeatureVisitor;
|
||||
friend gin::V8FeatureVisitor;
|
||||
friend TestFeatureVisitor;
|
||||
|
||||
// The constructor is private so only friend classes can inherit from this
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "base/debug/alias.h"
|
||||
#include "base/debug/crash_logging.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/feature_visitor.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
@ -218,10 +219,77 @@ void SetV8FlagsIfOverridden(const base::Feature& feature,
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::string_view kV8FlagFeaturePrefix = "V8Flag_";
|
||||
|
||||
} // namespace
|
||||
|
||||
class V8FeatureVisitor : public base::FeatureVisitor {
|
||||
public:
|
||||
void Visit(const std::string& feature_name,
|
||||
base::FeatureList::OverrideState override_state,
|
||||
const std::map<std::string, std::string>& params,
|
||||
const std::string& trial_name,
|
||||
const std::string& group_name) override {
|
||||
std::string_view feature_name_view(feature_name);
|
||||
|
||||
// VisitFeaturesAndParams is called with kV8FlagFeaturePrefix as a filter
|
||||
// prefix, so we expect all feature names to start with "V8Flag_". Strip
|
||||
// this prefix off to get the corresponding V8 flag name.
|
||||
DCHECK(feature_name_view.starts_with(kV8FlagFeaturePrefix));
|
||||
std::string_view flag_name =
|
||||
feature_name_view.substr(kV8FlagFeaturePrefix.size());
|
||||
|
||||
switch (override_state) {
|
||||
case base::FeatureList::OverrideState::OVERRIDE_USE_DEFAULT:
|
||||
return;
|
||||
|
||||
case base::FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE:
|
||||
SetV8FlagsFormatted("--no-%s", flag_name);
|
||||
// Do not set parameters for disabled features.
|
||||
break;
|
||||
|
||||
case base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE:
|
||||
SetV8FlagsFormatted("--%s", flag_name);
|
||||
for (const auto& [param_name, param_value] : params) {
|
||||
SetV8FlagsFormatted("--%s=%s", param_name.c_str(),
|
||||
param_value.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
void SetFlags(IsolateHolder::ScriptMode mode,
|
||||
const std::string js_command_line_flags) {
|
||||
// We assume that all feature flag defaults correspond to the default
|
||||
// values of the corresponding V8 flags.
|
||||
// Chromium features prefixed with "V8Flag_" are forwarded to V8 as V8 flags,
|
||||
// with the "V8Flag_" prefix stripped off. For example, an enabled feature
|
||||
// "V8Flag_foo_bar" will be passed to V8 as the flag `--foo_bar`. Similarly,
|
||||
// if that feature is explicitly disabled, it will be passed to V8 as
|
||||
// `--no-foo_bar`. No Chromium-side declaration of a V8Flag_foo_bar feature
|
||||
// is necessary, the matching is done on strings.
|
||||
//
|
||||
// Parameters attached to features will also be passed through, with the same
|
||||
// name as the parameter and the value passed by string, to be decoded by V8's
|
||||
// flag parsing.
|
||||
//
|
||||
// Thus, running Chromium with:
|
||||
//
|
||||
// --enable-features=V8Flag_foo,V8Flag_bar:bar_param/20
|
||||
// --disable-features=V8Flag_baz
|
||||
//
|
||||
// will be converted, on V8 initialization, to V8 flags:
|
||||
//
|
||||
// --foo --bar --bar_param=20 --no-baz
|
||||
V8FeatureVisitor feature_visitor;
|
||||
base::FeatureList::VisitFeaturesAndParams(feature_visitor,
|
||||
kV8FlagFeaturePrefix);
|
||||
|
||||
// Otherwise, feature flags explicitly defined in Chromium are translated
|
||||
// to V8 flags as follows. We ignore feature flag default values, instead
|
||||
// using the corresponding V8 flags default values if there is no explicit
|
||||
// feature override.
|
||||
SetV8FlagsIfOverridden(features::kV8CompactCodeSpaceWithStack,
|
||||
"--compact-code-space-with-stack",
|
||||
"--no-compact-code-space-with-stack");
|
||||
|
@ -35,6 +35,10 @@ class SnapshotPlatform final : public blink::Platform {
|
||||
int main(int argc, char** argv) {
|
||||
base::AtExitManager at_exit;
|
||||
|
||||
// Initialize an empty feature list for gin startup.
|
||||
auto early_access_feature_list = std::make_unique<base::FeatureList>();
|
||||
base::FeatureList::SetInstance(std::move(early_access_feature_list));
|
||||
|
||||
const bool kRemoveRecognizedFlags = true;
|
||||
v8::V8::SetFlagsFromCommandLine(&argc, argv, kRemoveRecognizedFlags);
|
||||
base::CommandLine::Init(argc, argv);
|
||||
|
Reference in New Issue
Block a user