Split up default feature sets provided to plugins and runtimes.

The new fields fixed_features and overridable_features can be simply merged to recover the old aggregate defaults.  By splitting them though, plugins and runtimes get some extra information about lifetimes for enforcement.

PiperOrigin-RevId: 625084569
pull/16473/head
Mike Kruskal 8 months ago committed by Copybara-Service
parent 0a497f9148
commit b8ec9588c5
  1. 33
      src/google/protobuf/compiler/code_generator_unittest.cc
  2. 99
      src/google/protobuf/compiler/command_line_interface_unittest.cc
  3. 2
      src/google/protobuf/cpp_edition_defaults.h
  4. 42
      src/google/protobuf/feature_resolver.cc
  5. 190
      src/google/protobuf/feature_resolver_test.cc
  6. 17
      src/google/protobuf/unittest_features.proto

@ -271,7 +271,6 @@ TEST_F(CodeGeneratorTest, BuildFeatureSetDefaults) {
EXPECT_THAT(generator.BuildFeatureSetDefaults(),
IsOkAndHolds(EqualsProto(R"pb(
defaults {
edition: EDITION_PROTO2
features {
field_presence: EXPLICIT
enum_type: CLOSED
@ -280,9 +279,18 @@ TEST_F(CodeGeneratorTest, BuildFeatureSetDefaults) {
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
edition: EDITION_PROTO2
overridable_features {}
fixed_features {
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
utf8_validation: NONE
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
}
defaults {
edition: EDITION_PROTO3
features {
field_presence: IMPLICIT
enum_type: OPEN
@ -291,9 +299,18 @@ TEST_F(CodeGeneratorTest, BuildFeatureSetDefaults) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_PROTO3
overridable_features {}
fixed_features {
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
}
defaults {
edition: EDITION_2023
features {
field_presence: EXPLICIT
enum_type: OPEN
@ -302,6 +319,16 @@ TEST_F(CodeGeneratorTest, BuildFeatureSetDefaults) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_2023
overridable_features {
field_presence: EXPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
fixed_features {}
}
minimum_edition: EDITION_99997_TEST_ONLY
maximum_edition: EDITION_99999_TEST_ONLY

@ -1865,7 +1865,6 @@ TEST_F(CommandLineInterfaceTest, EditionDefaults) {
FeatureSetDefaults defaults = ReadEditionDefaults("defaults");
EXPECT_THAT(defaults, EqualsProto(R"pb(
defaults {
edition: EDITION_PROTO2
features {
field_presence: EXPLICIT
enum_type: CLOSED
@ -1874,9 +1873,18 @@ TEST_F(CommandLineInterfaceTest, EditionDefaults) {
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
edition: EDITION_PROTO2
overridable_features {}
fixed_features {
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
utf8_validation: NONE
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
}
defaults {
edition: EDITION_PROTO3
features {
field_presence: IMPLICIT
enum_type: OPEN
@ -1885,9 +1893,18 @@ TEST_F(CommandLineInterfaceTest, EditionDefaults) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_PROTO3
overridable_features {}
fixed_features {
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
}
defaults {
edition: EDITION_2023
features {
field_presence: EXPLICIT
enum_type: OPEN
@ -1896,6 +1913,16 @@ TEST_F(CommandLineInterfaceTest, EditionDefaults) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_2023
overridable_features {
field_presence: EXPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
fixed_features {}
}
minimum_edition: EDITION_PROTO2
maximum_edition: EDITION_2023
@ -1914,7 +1941,6 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMaximum) {
FeatureSetDefaults defaults = ReadEditionDefaults("defaults");
EXPECT_THAT(defaults, EqualsProto(R"pb(
defaults {
edition: EDITION_PROTO2
features {
field_presence: EXPLICIT
enum_type: CLOSED
@ -1923,9 +1949,18 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMaximum) {
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
edition: EDITION_PROTO2
overridable_features {}
fixed_features {
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
utf8_validation: NONE
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
}
defaults {
edition: EDITION_PROTO3
features {
field_presence: IMPLICIT
enum_type: OPEN
@ -1934,9 +1969,18 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMaximum) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_PROTO3
overridable_features {}
fixed_features {
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
}
defaults {
edition: EDITION_2023
features {
field_presence: EXPLICIT
enum_type: OPEN
@ -1945,6 +1989,16 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMaximum) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_2023
overridable_features {
field_presence: EXPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
fixed_features {}
}
minimum_edition: EDITION_PROTO2
maximum_edition: EDITION_99997_TEST_ONLY
@ -1964,7 +2018,6 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMinimum) {
FeatureSetDefaults defaults = ReadEditionDefaults("defaults");
EXPECT_THAT(defaults, EqualsProto(R"pb(
defaults {
edition: EDITION_PROTO2
features {
field_presence: EXPLICIT
enum_type: CLOSED
@ -1973,9 +2026,18 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMinimum) {
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
edition: EDITION_PROTO2
overridable_features {}
fixed_features {
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
utf8_validation: NONE
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
}
}
defaults {
edition: EDITION_PROTO3
features {
field_presence: IMPLICIT
enum_type: OPEN
@ -1984,9 +2046,18 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMinimum) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_PROTO3
overridable_features {}
fixed_features {
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
}
defaults {
edition: EDITION_2023
features {
field_presence: EXPLICIT
enum_type: OPEN
@ -1995,6 +2066,16 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMinimum) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
edition: EDITION_2023
overridable_features {
field_presence: EXPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
}
fixed_features {}
}
minimum_edition: EDITION_99997_TEST_ONLY
maximum_edition: EDITION_99999_TEST_ONLY

@ -5,7 +5,7 @@
// the C++ runtime. This is used for feature resolution under Editions.
// NOLINTBEGIN
// clang-format off
#define PROTOBUF_INTERNAL_CPP_EDITION_DEFAULTS "\n\030\022\023\010\001\020\002\030\002 \003(\0010\002\302>\004\010\001\020\003\030\346\007\n\030\022\023\010\002\020\001\030\001 \002(\0010\001\302>\004\010\000\020\003\030\347\007\n\030\022\023\010\001\020\001\030\001 \002(\0010\001\302>\004\010\000\020\003\030\350\007\n\030\022\023\010\001\020\001\030\001 \002(\0010\001\302>\004\010\000\020\001\030\351\007 \346\007(\351\007"
#define PROTOBUF_INTERNAL_CPP_EDITION_DEFAULTS "\n2\022\023\010\001\020\002\030\002 \003(\0010\002\302>\004\010\001\020\003\030\346\007\"\003\302>\000*\023\010\001\020\002\030\002 \003(\0010\002\302>\004\010\001\020\003\n2\022\023\010\002\020\001\030\001 \002(\0010\001\302>\004\010\000\020\003\030\347\007\"\003\302>\000*\023\010\002\020\001\030\001 \002(\0010\001\302>\004\010\000\020\003\n2\022\023\010\001\020\001\030\001 \002(\0010\001\302>\004\010\000\020\003\030\350\007\"\023\010\001\020\001\030\001 \002(\0010\001\302>\004\010\000\020\003*\003\302>\000\n2\022\023\010\001\020\001\030\001 \002(\0010\001\302>\004\010\000\020\001\030\351\007\"\023\010\001\020\001\030\001 \002(\0010\001\302>\004\010\000\020\001*\003\302>\000 \346\007(\351\007"
// clang-format on
// NOLINTEND

@ -204,8 +204,10 @@ void CollectEditions(const Descriptor& descriptor, Edition maximum_edition,
}
}
absl::Status FillDefaults(Edition edition, Message& msg) {
const Descriptor& descriptor = *msg.GetDescriptor();
absl::Status FillDefaults(Edition edition, Message& fixed,
Message& overridable) {
const Descriptor& descriptor = *fixed.GetDescriptor();
ABSL_CHECK(&descriptor == overridable.GetDescriptor());
auto comparator = [](const FieldOptions::EditionDefault& a,
const FieldOptions::EditionDefault& b) {
@ -216,8 +218,17 @@ absl::Status FillDefaults(Edition edition, Message& msg) {
for (int i = 0; i < descriptor.field_count(); ++i) {
const FieldDescriptor& field = *descriptor.field(i);
Message* msg = &overridable;
if (field.options().has_feature_support()) {
if ((field.options().feature_support().has_edition_introduced() &&
edition < field.options().feature_support().edition_introduced()) ||
(field.options().feature_support().has_edition_removed() &&
edition >= field.options().feature_support().edition_removed())) {
msg = &fixed;
}
}
msg.GetReflection()->ClearField(&msg, &field);
msg->GetReflection()->ClearField(msg, &field);
ABSL_CHECK(!field.is_repeated());
ABSL_CHECK(field.cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE);
@ -233,7 +244,7 @@ absl::Status FillDefaults(Edition edition, Message& msg) {
}
const std::string& def = std::prev(first_nonmatch)->value();
if (!TextFormat::ParseFieldValueFromString(def, &field, &msg)) {
if (!TextFormat::ParseFieldValueFromString(def, &field, msg)) {
return Error("Parsing error in edition_defaults for feature field ",
field.full_name(), ". Could not parse: ", def);
}
@ -350,18 +361,31 @@ absl::StatusOr<FeatureSetDefaults> FeatureResolver::CompileDefaults(
defaults.set_maximum_edition(maximum_edition);
auto message_factory = absl::make_unique<DynamicMessageFactory>();
for (const auto& edition : editions) {
auto defaults_dynamic =
auto fixed_defaults_dynamic =
absl::WrapUnique(message_factory->GetPrototype(feature_set)->New());
RETURN_IF_ERROR(FillDefaults(edition, *defaults_dynamic));
auto overridable_defaults_dynamic =
absl::WrapUnique(message_factory->GetPrototype(feature_set)->New());
RETURN_IF_ERROR(FillDefaults(edition, *fixed_defaults_dynamic,
*overridable_defaults_dynamic));
for (const auto* extension : extensions) {
RETURN_IF_ERROR(FillDefaults(
edition, *defaults_dynamic->GetReflection()->MutableMessage(
defaults_dynamic.get(), extension)));
edition,
*fixed_defaults_dynamic->GetReflection()->MutableMessage(
fixed_defaults_dynamic.get(), extension),
*overridable_defaults_dynamic->GetReflection()->MutableMessage(
overridable_defaults_dynamic.get(), extension)));
}
auto* edition_defaults = defaults.mutable_defaults()->Add();
edition_defaults->set_edition(edition);
edition_defaults->mutable_fixed_features()->MergeFromString(
fixed_defaults_dynamic->SerializeAsString());
edition_defaults->mutable_overridable_features()->MergeFromString(
overridable_defaults_dynamic->SerializeAsString());
// TODO Remove this once `features` is deprecated.
edition_defaults->mutable_features()->MergeFromString(
fixed_defaults_dynamic->SerializeAsString());
edition_defaults->mutable_features()->MergeFromString(
defaults_dynamic->SerializeAsString());
overridable_defaults_dynamic->SerializeAsString());
}
return defaults;
}

@ -248,6 +248,148 @@ TEST(FeatureResolverTest, DefaultsMiddleEdition) {
EXPECT_TRUE(ext.bool_field_feature());
}
TEST(FeatureResolverTest, CompileDefaultsFixedFutureFeature) {
absl::StatusOr<FeatureSetDefaults> defaults =
FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
{GetExtension(pb::test)}, EDITION_PROTO2,
EDITION_2023);
ASSERT_OK(defaults);
ASSERT_EQ(defaults->defaults_size(), 3);
const auto& edition_defaults = defaults->defaults(2);
ASSERT_EQ(edition_defaults.edition(), EDITION_2023);
EXPECT_TRUE(
edition_defaults.features().GetExtension(pb::test).has_future_feature());
EXPECT_EQ(edition_defaults.features().GetExtension(pb::test).future_feature(),
pb::VALUE1);
EXPECT_TRUE(edition_defaults.fixed_features()
.GetExtension(pb::test)
.has_future_feature());
EXPECT_EQ(
edition_defaults.fixed_features().GetExtension(pb::test).future_feature(),
pb::VALUE1);
EXPECT_FALSE(edition_defaults.overridable_features()
.GetExtension(pb::test)
.has_future_feature());
}
TEST(FeatureResolverTest, CompileDefaultsFixedRemovedFeature) {
absl::StatusOr<FeatureSetDefaults> defaults =
FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
{GetExtension(pb::test)}, EDITION_PROTO2,
EDITION_2024);
ASSERT_OK(defaults);
ASSERT_EQ(defaults->defaults_size(), 4);
const auto& edition_defaults = defaults->defaults(3);
ASSERT_EQ(edition_defaults.edition(), EDITION_2024);
EXPECT_TRUE(
edition_defaults.features().GetExtension(pb::test).has_removed_feature());
EXPECT_EQ(
edition_defaults.features().GetExtension(pb::test).removed_feature(),
pb::VALUE3);
EXPECT_TRUE(edition_defaults.fixed_features()
.GetExtension(pb::test)
.has_removed_feature());
EXPECT_EQ(edition_defaults.fixed_features()
.GetExtension(pb::test)
.removed_feature(),
pb::VALUE3);
EXPECT_FALSE(edition_defaults.overridable_features()
.GetExtension(pb::test)
.has_removed_feature());
}
TEST(FeatureResolverTest, CompileDefaultsOverridableNoSupportWindow) {
absl::StatusOr<FeatureSetDefaults> defaults =
FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
{GetExtension(pb::test)}, EDITION_PROTO2,
EDITION_2023);
ASSERT_OK(defaults);
ASSERT_EQ(defaults->defaults_size(), 3);
const auto& edition_defaults = defaults->defaults(2);
ASSERT_EQ(edition_defaults.edition(), EDITION_2023);
EXPECT_TRUE(edition_defaults.features()
.GetExtension(pb::test)
.has_no_support_window_feature());
EXPECT_EQ(edition_defaults.features()
.GetExtension(pb::test)
.no_support_window_feature(),
pb::VALUE2);
EXPECT_FALSE(edition_defaults.fixed_features()
.GetExtension(pb::test)
.has_no_support_window_feature());
EXPECT_TRUE(edition_defaults.overridable_features()
.GetExtension(pb::test)
.has_no_support_window_feature());
EXPECT_EQ(edition_defaults.overridable_features()
.GetExtension(pb::test)
.no_support_window_feature(),
pb::VALUE2);
}
TEST(FeatureResolverTest, CompileDefaultsOverridableEmptySupportWindow) {
absl::StatusOr<FeatureSetDefaults> defaults =
FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
{GetExtension(pb::test)}, EDITION_PROTO2,
EDITION_2023);
ASSERT_OK(defaults);
ASSERT_EQ(defaults->defaults_size(), 3);
const auto& edition_defaults = defaults->defaults(2);
ASSERT_EQ(edition_defaults.edition(), EDITION_2023);
EXPECT_TRUE(edition_defaults.features()
.GetExtension(pb::test)
.has_empty_support_window_feature());
EXPECT_EQ(edition_defaults.features()
.GetExtension(pb::test)
.empty_support_window_feature(),
pb::VALUE2);
EXPECT_FALSE(edition_defaults.fixed_features()
.GetExtension(pb::test)
.has_empty_support_window_feature());
EXPECT_TRUE(edition_defaults.overridable_features()
.GetExtension(pb::test)
.has_empty_support_window_feature());
EXPECT_EQ(edition_defaults.overridable_features()
.GetExtension(pb::test)
.empty_support_window_feature(),
pb::VALUE2);
}
TEST(FeatureResolverTest, CompileDefaultsOverridable) {
absl::StatusOr<FeatureSetDefaults> defaults =
FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
{GetExtension(pb::test)}, EDITION_PROTO2,
EDITION_2023);
ASSERT_OK(defaults);
ASSERT_EQ(defaults->defaults_size(), 3);
const auto& edition_defaults = defaults->defaults(2);
ASSERT_EQ(edition_defaults.edition(), EDITION_2023);
EXPECT_TRUE(
edition_defaults.features().GetExtension(pb::test).has_removed_feature());
EXPECT_EQ(
edition_defaults.features().GetExtension(pb::test).removed_feature(),
pb::VALUE2);
EXPECT_FALSE(edition_defaults.fixed_features()
.GetExtension(pb::test)
.has_removed_feature());
EXPECT_TRUE(edition_defaults.overridable_features()
.GetExtension(pb::test)
.has_removed_feature());
EXPECT_EQ(edition_defaults.overridable_features()
.GetExtension(pb::test)
.removed_feature(),
pb::VALUE2);
}
TEST(FeatureResolverTest, CreateFromUnsortedDefaults) {
auto valid_defaults = FeatureResolver::CompileDefaults(
FeatureSet::descriptor(), {}, EDITION_PROTO2, EDITION_2023);
@ -1231,6 +1373,18 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
json_format: LEGACY_BEST_EFFORT
[pb.test] { file_feature: VALUE1 }
}
overridable_features {
[pb.test] {}
}
fixed_features {
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
utf8_validation: NONE
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
[pb.test] { file_feature: VALUE1 }
}
}
defaults {
edition: EDITION_PROTO3
@ -1243,6 +1397,18 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
json_format: ALLOW
[pb.test] { file_feature: VALUE1 }
}
overridable_features {
[pb.test] {}
}
fixed_features {
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
[pb.test] { file_feature: VALUE1 }
}
}
defaults {
edition: EDITION_2023
@ -1255,6 +1421,18 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
json_format: ALLOW
[pb.test] { file_feature: VALUE2 }
}
overridable_features {
field_presence: EXPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
[pb.test] { file_feature: VALUE2 }
}
fixed_features {
[pb.test] {}
}
}
defaults {
edition: EDITION_99998_TEST_ONLY
@ -1267,6 +1445,18 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
json_format: ALLOW
[pb.test] { file_feature: VALUE3 }
}
overridable_features {
field_presence: EXPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
utf8_validation: VERIFY
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
[pb.test] { file_feature: VALUE3 }
}
fixed_features {
[pb.test] {}
}
}
)pb"));
}

@ -192,4 +192,21 @@ message TestFeatures {
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" },
edition_defaults = { edition: EDITION_2023, value: "VALUE2" }
];
optional EnumFeature no_support_window_feature = 20 [
retention = RETENTION_RUNTIME,
targets = TARGET_TYPE_FILE,
targets = TARGET_TYPE_FIELD,
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" },
edition_defaults = { edition: EDITION_2023, value: "VALUE2" }
];
optional EnumFeature empty_support_window_feature = 21 [
retention = RETENTION_RUNTIME,
targets = TARGET_TYPE_FILE,
targets = TARGET_TYPE_FIELD,
feature_support = {},
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" },
edition_defaults = { edition: EDITION_2023, value: "VALUE2" }
];
}

Loading…
Cancel
Save