Skip to content

Commit 887e95d

Browse files
Fix a bug in edition defaults calculation.
Since the fixed/overridable split can occur whenever a feature is introduced or removed, we need to include those editions in the resulting compiled defaults. This does bug only affects edition 2024 and later, where features may be removed or introduced in isolation. PiperOrigin-RevId: 638903990
1 parent d2edc49 commit 887e95d

File tree

3 files changed

+100
-4
lines changed

3 files changed

+100
-4
lines changed

src/google/protobuf/feature_resolver.cc

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,38 @@ absl::Status ValidateExtension(const Descriptor& feature_set,
189189
return absl::OkStatus();
190190
}
191191

192+
void MaybeInsertEdition(Edition edition, Edition maximum_edition,
193+
absl::btree_set<Edition>& editions) {
194+
if (edition <= maximum_edition) {
195+
editions.insert(edition);
196+
}
197+
}
198+
199+
// This collects all of the editions that are relevant to any features defined
200+
// in a message descriptor. We only need to consider editions where something
201+
// has changed.
192202
void CollectEditions(const Descriptor& descriptor, Edition maximum_edition,
193203
absl::btree_set<Edition>& editions) {
194204
for (int i = 0; i < descriptor.field_count(); ++i) {
195-
for (const auto& def : descriptor.field(i)->options().edition_defaults()) {
196-
if (maximum_edition < def.edition()) continue;
205+
const FieldOptions& options = descriptor.field(i)->options();
206+
// Editions where a new feature is introduced should be captured.
207+
MaybeInsertEdition(options.feature_support().edition_introduced(),
208+
maximum_edition, editions);
209+
210+
// Editions where a feature is removed should be captured.
211+
if (options.feature_support().has_edition_removed()) {
212+
MaybeInsertEdition(options.feature_support().edition_removed(),
213+
maximum_edition, editions);
214+
}
215+
216+
// Any edition where a default value changes should be captured.
217+
for (const auto& def : options.edition_defaults()) {
197218
// TODO Remove this once all features use EDITION_LEGACY.
198219
if (def.edition() == Edition::EDITION_LEGACY) {
199220
editions.insert(Edition::EDITION_PROTO2);
200221
continue;
201222
}
202-
editions.insert(def.edition());
223+
MaybeInsertEdition(def.edition(), maximum_edition, editions);
203224
}
204225
}
205226
}

src/google/protobuf/feature_resolver_test.cc

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,81 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumTooEarly) {
13111311
HasError(HasSubstr("edition 1_TEST_ONLY is earlier than the oldest")));
13121312
}
13131313

1314+
TEST_F(FeatureResolverPoolTest, CompileDefaultsRemovedOnly) {
1315+
const FileDescriptor* file = ParseSchema(R"schema(
1316+
syntax = "proto2";
1317+
package test;
1318+
import "google/protobuf/descriptor.proto";
1319+
1320+
extend google.protobuf.FeatureSet {
1321+
optional Foo bar = 9999;
1322+
}
1323+
enum Bar {
1324+
TEST_ENUM_FEATURE_UNKNOWN = 0;
1325+
VALUE1 = 1;
1326+
VALUE2 = 2;
1327+
}
1328+
message Foo {
1329+
optional Bar file_feature = 1 [
1330+
targets = TARGET_TYPE_FIELD,
1331+
feature_support.edition_introduced = EDITION_2023,
1332+
feature_support.edition_removed = EDITION_99998_TEST_ONLY,
1333+
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
1334+
];
1335+
}
1336+
)schema");
1337+
ASSERT_NE(file, nullptr);
1338+
1339+
const FieldDescriptor* ext = file->extension(0);
1340+
auto compiled_defaults = FeatureResolver::CompileDefaults(
1341+
feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
1342+
ASSERT_OK(compiled_defaults);
1343+
const auto& defaults = *compiled_defaults->defaults().rbegin();
1344+
EXPECT_THAT(defaults.edition(), EDITION_99998_TEST_ONLY);
1345+
EXPECT_THAT(defaults.fixed_features().GetExtension(pb::test).file_feature(),
1346+
pb::VALUE1);
1347+
EXPECT_FALSE(defaults.overridable_features()
1348+
.GetExtension(pb::test)
1349+
.has_file_feature());
1350+
}
1351+
1352+
TEST_F(FeatureResolverPoolTest, CompileDefaultsIntroducedOnly) {
1353+
const FileDescriptor* file = ParseSchema(R"schema(
1354+
syntax = "proto2";
1355+
package test;
1356+
import "google/protobuf/descriptor.proto";
1357+
1358+
extend google.protobuf.FeatureSet {
1359+
optional Foo bar = 9999;
1360+
}
1361+
enum Bar {
1362+
TEST_ENUM_FEATURE_UNKNOWN = 0;
1363+
VALUE1 = 1;
1364+
VALUE2 = 2;
1365+
}
1366+
message Foo {
1367+
optional Bar file_feature = 1 [
1368+
targets = TARGET_TYPE_FIELD,
1369+
feature_support.edition_introduced = EDITION_99998_TEST_ONLY,
1370+
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
1371+
];
1372+
}
1373+
)schema");
1374+
ASSERT_NE(file, nullptr);
1375+
1376+
const FieldDescriptor* ext = file->extension(0);
1377+
auto compiled_defaults = FeatureResolver::CompileDefaults(
1378+
feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
1379+
ASSERT_OK(compiled_defaults);
1380+
const auto& defaults = *compiled_defaults->defaults().rbegin();
1381+
EXPECT_THAT(defaults.edition(), EDITION_99998_TEST_ONLY);
1382+
EXPECT_THAT(
1383+
defaults.overridable_features().GetExtension(pb::test).file_feature(),
1384+
pb::VALUE1);
1385+
EXPECT_FALSE(
1386+
defaults.fixed_features().GetExtension(pb::test).has_file_feature());
1387+
}
1388+
13141389
TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
13151390
const FileDescriptor* file = ParseSchema(R"schema(
13161391
syntax = "proto2";

src/google/protobuf/unittest_features.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ message TestFeatures {
193193
targets = TARGET_TYPE_FILE,
194194
targets = TARGET_TYPE_FIELD,
195195
feature_support = {
196-
edition_introduced: EDITION_PROTO2
196+
edition_introduced: EDITION_PROTO3
197197
edition_removed: EDITION_2023
198198
},
199199
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" },

0 commit comments

Comments
 (0)