Skip to content

Commit 34c1110

Browse files
karenwuzcopybara-github
authored andcommitted
Validate Feature Support on Custom Options
PiperOrigin-RevId: 864384082
1 parent 588fa9d commit 34c1110

File tree

9 files changed

+422
-86
lines changed

9 files changed

+422
-86
lines changed

src/google/protobuf/compiler/command_line_interface_unittest.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,36 @@ TEST_F(CommandLineInterfaceTest, ValidateFeatureSupportValid) {
16521652
ExpectNoErrors();
16531653
}
16541654

1655+
TEST_F(CommandLineInterfaceTest, ValidateFeatureSupportLifetimesOptionRemoved) {
1656+
CreateTempFile("google/protobuf/descriptor.proto",
1657+
google::protobuf::DescriptorProto::descriptor()->file()->DebugString());
1658+
CreateTempFile("foo.proto",
1659+
R"schema(
1660+
edition = "2024";
1661+
import "google/protobuf/descriptor.proto";
1662+
1663+
option features.field_presence = IMPLICIT;
1664+
1665+
extend google.protobuf.MessageOptions {
1666+
bool removed_option = 7733026 [feature_support = {
1667+
edition_removed: EDITION_2023
1668+
removal_error: "removed_option removal error"}];
1669+
}
1670+
message Foo {
1671+
option (removed_option) = true;
1672+
int32 bar = 1 [
1673+
feature_support = {
1674+
edition_removed: EDITION_2023
1675+
removal_error: "Custom removal error"
1676+
}
1677+
];
1678+
})schema");
1679+
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
1680+
ExpectErrorSubstring(
1681+
"removed_option has been removed in edition 2023: removed_option removal "
1682+
"error");
1683+
}
1684+
16551685
TEST_F(CommandLineInterfaceTest, FeatureValidationError) {
16561686
CreateTempFile("foo.proto",
16571687
R"schema(

src/google/protobuf/descriptor.cc

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,6 @@ class DescriptorPool::DeferredValidation {
16081608
struct LifetimesInfo {
16091609
const Message* option_to_validate; // features or options to validate
16101610
const Message* proto;
1611-
bool is_feature;
16121611
absl::string_view full_name;
16131612
absl::string_view filename;
16141613
};
@@ -1637,20 +1636,16 @@ class DescriptorPool::DeferredValidation {
16371636
return true;
16381637
}
16391638

1640-
static absl::string_view feature_set_name = "google.protobuf.FeatureSet";
16411639
bool has_errors = false;
16421640
for (const auto& it : lifetimes_info_map_) {
16431641
const FileDescriptor* file = it.first;
16441642

16451643
for (const auto& info : it.second) {
1646-
const Descriptor* feature_set_descriptor = nullptr;
1647-
// TODO: Support custom options
1648-
if (info.is_feature) {
1649-
feature_set_descriptor =
1650-
pool_->FindMessageTypeByName(feature_set_name);
1651-
}
1644+
const Descriptor* option_descriptor = nullptr;
1645+
option_descriptor = pool_->FindMessageTypeByName(
1646+
info.option_to_validate->GetTypeName());
16521647
auto results = FeatureResolver::ValidateFeatureLifetimes(
1653-
file->edition(), *info.option_to_validate, feature_set_descriptor);
1648+
file->edition(), *info.option_to_validate, option_descriptor);
16541649
for (const auto& error : results.errors) {
16551650
has_errors = true;
16561651
if (error_collector_ == nullptr) {
@@ -6690,15 +6685,13 @@ FileDescriptor* DescriptorBuilder::BuildFileImpl(
66906685
*result, proto, [&](const auto& descriptor, const auto& desc_proto) {
66916686
if (!IsDefaultInstance(*descriptor.proto_features_)) {
66926687
deferred_validation_.ValidateFeatureLifetimes(
6693-
GetFile(descriptor),
6694-
{descriptor.proto_features_, &desc_proto, /*is_feature=*/true,
6695-
GetFullName(descriptor), proto.name()});
6688+
GetFile(descriptor), {descriptor.proto_features_, &desc_proto,
6689+
GetFullName(descriptor), proto.name()});
66966690
}
66976691
if (!IsDefaultInstance(*descriptor.options_)) {
66986692
deferred_validation_.ValidateFeatureLifetimes(
6699-
GetFile(descriptor),
6700-
{descriptor.options_, &desc_proto,
6701-
/*is_feature=*/false, GetFullName(descriptor), proto.name()});
6693+
GetFile(descriptor), {descriptor.options_, &desc_proto,
6694+
GetFullName(descriptor), proto.name()});
67026695
}
67036696
});
67046697
}

src/google/protobuf/descriptor_test_utils.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ void ValidationErrorTest::ParseAndBuildFileWithErrorSubstr(
135135
EXPECT_THAT(error_collector.text_, HasSubstr(expected_errors));
136136
}
137137

138+
void ValidationErrorTest::ParseAndBuildFileWithWarningSubstr(
139+
absl::string_view file_name, absl::string_view file_text,
140+
absl::string_view expected_warning) {
141+
MockErrorCollector error_collector;
142+
EXPECT_TRUE(pool_.BuildFileCollectingErrors(ParseFile(file_name, file_text),
143+
&error_collector));
144+
EXPECT_THAT(error_collector.warning_text_, HasSubstr(expected_warning));
145+
}
146+
138147
// Parse file_text as a FileDescriptorProto in text format and add it
139148
// to the DescriptorPool. Expect errors to be produced which match the
140149
// given warning text.

src/google/protobuf/descriptor_test_utils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ class ValidationErrorTest : public testing::Test {
9797
absl::string_view file_text,
9898
absl::string_view expected_errors);
9999

100+
void ParseAndBuildFileWithWarningSubstr(absl::string_view file_name,
101+
absl::string_view file_text,
102+
absl::string_view expected_warning);
103+
100104
// Parse file_text as a FileDescriptorProto in text format and add it
101105
// to the DescriptorPool. Expect errors to be produced which match the
102106
// given warning text.

src/google/protobuf/descriptor_unittest.cc

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4052,7 +4052,7 @@ TEST_P(AllowUnknownDependenciesTest, MissingTypeAggregateOption) {
40524052
EXPECT_EQ(file->options().uninterpreted_option_size(), 1);
40534053
}
40544054

4055-
TEST_P(AllowUnknownDependenciesTest, MissinNestedTypeAggregateOption) {
4055+
TEST_P(AllowUnknownDependenciesTest, MissingNestedTypeAggregateOption) {
40564056
// Test that we can use an aggregate custom option with a missing type.
40574057

40584058
FileDescriptorProto file_proto;
@@ -4112,15 +4112,15 @@ TEST_P(AllowUnknownDependenciesTest, MissinNestedTypeAggregateOption) {
41124112
const Descriptor* message = file->FindMessageTypeByName("MessageMissing");
41134113
ASSERT_NE(message, nullptr);
41144114
std::vector<const FieldDescriptor*> fields;
4115-
file->options().GetReflection()->ListFields(message->options(), &fields);
4115+
message->options().GetReflection()->ListFields(message->options(), &fields);
41164116
EXPECT_EQ(fields.size(), 1);
41174117
EXPECT_EQ(message->options().unknown_fields().field_count(), 0);
41184118
EXPECT_EQ(message->options().uninterpreted_option_size(), 1);
41194119

41204120
// Aggregate options without missing types should be resolved.
41214121
message = file->FindMessageTypeByName("MessageValid");
41224122
ASSERT_NE(message, nullptr);
4123-
file->options().GetReflection()->ListFields(message->options(), &fields);
4123+
message->options().GetReflection()->ListFields(message->options(), &fields);
41244124
EXPECT_EQ(fields.size(), 0);
41254125
EXPECT_EQ(message->options().unknown_fields().field_count(), 1);
41264126
EXPECT_EQ(message->options().uninterpreted_option_size(), 0);
@@ -12907,6 +12907,61 @@ TEST_F(FeaturesTest, ExistingUnstableFeatureDefault) {
1290712907
pb::UNSTABLE3);
1290812908
}
1290912909

12910+
TEST_F(FeaturesTest, FeatureLifetimesOptionRemoved) {
12911+
BuildDescriptorMessagesInTestPool();
12912+
ParseAndBuildFileWithErrorSubstr(
12913+
"featurelifetimes.proto", R"schema(
12914+
edition = "2024";
12915+
package featurelifetimes;
12916+
import "google/protobuf/descriptor.proto";
12917+
12918+
extend google.protobuf.MessageOptions {
12919+
bool removed_option = 7733026 [feature_support = {
12920+
edition_removed: EDITION_2023
12921+
removal_error: "removed_option removal error"
12922+
}];
12923+
}
12924+
message SomeMessage {
12925+
option (removed_option) = true;
12926+
}
12927+
)schema",
12928+
"featurelifetimes.removed_option has been removed in edition 2023: "
12929+
"removed_option removal error");
12930+
}
12931+
12932+
TEST_F(FeaturesTest, FeatureLifetimesOptionDeprecated) {
12933+
BuildDescriptorMessagesInTestPool();
12934+
12935+
ParseAndBuildFile("featurelifetimes.proto", R"schema(
12936+
edition = "2024";
12937+
package featurelifetimes;
12938+
import "google/protobuf/descriptor.proto";
12939+
12940+
extend google.protobuf.MessageOptions {
12941+
bool deprecated_option = 7733026 [feature_support = {
12942+
edition_deprecated: EDITION_2023
12943+
deprecation_warning: "deprecated_option deprecation warning"
12944+
}];
12945+
}
12946+
)schema");
12947+
12948+
pool_.AddDirectInputFile("some_message.proto");
12949+
ParseAndBuildFileWithWarningSubstr(
12950+
"some_message.proto",
12951+
R"schema(
12952+
edition = "2024";
12953+
package some_message;
12954+
import "google/protobuf/descriptor.proto";
12955+
import "featurelifetimes.proto";
12956+
12957+
message SomeMessage {
12958+
option (featurelifetimes.deprecated_option) = true;
12959+
}
12960+
)schema",
12961+
"featurelifetimes.deprecated_option has been deprecated in edition 2023: "
12962+
"deprecated_option deprecation warning");
12963+
}
12964+
1291012965
// Test that the result of FileDescriptor::DebugString() can be used to create
1291112966
// the original descriptors.
1291212967
class FeaturesDebugStringTest

src/google/protobuf/feature_resolver.cc

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -340,50 +340,62 @@ void ValidateSingleFeatureLifetimes(
340340
results.errors.emplace_back(std::move(error_message));
341341
} else if (feature_support.has_edition_deprecated() &&
342342
edition >= feature_support.edition_deprecated()) {
343-
std::string error_message = absl::Substitute(
343+
std::string warning_message = absl::Substitute(
344344
"$0 has been deprecated in edition "
345345
"$1: $2",
346346
full_name, feature_support.edition_deprecated(),
347347
feature_support.deprecation_warning());
348-
results.warnings.emplace_back(std::move(error_message));
348+
results.warnings.emplace_back(std::move(warning_message));
349349
}
350350
}
351351

352352
void ValidateFeatureLifetimesImpl(Edition edition, const Message& message,
353-
FeatureResolver::ValidationResults& results,
354-
bool is_feature) {
353+
FeatureResolver::ValidationResults& results) {
355354
std::vector<const FieldDescriptor*> fields;
356355
message.GetReflection()->ListFields(message, &fields);
357356
for (const FieldDescriptor* field : fields) {
358-
// TODO: Support repeated option enum values and custom options
359-
// feature support
360-
if (is_feature) {
361-
// Recurse into message extension.
362-
if (field->is_extension() &&
363-
field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
357+
const Reflection* reflector = message.GetReflection();
358+
// Recurse into all Messages to be validated
359+
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
360+
// Recursing into repeated Messages
361+
if (field->is_repeated()) {
362+
for (int index = 0; index < reflector->FieldSize(message, field);
363+
index++) {
364+
ValidateFeatureLifetimesImpl(
365+
edition, reflector->GetRepeatedMessage(message, field, index),
366+
results);
367+
}
368+
} else {
364369
ValidateFeatureLifetimesImpl(
365-
edition, message.GetReflection()->GetMessage(message, field),
366-
results, is_feature);
367-
continue;
370+
edition, reflector->GetMessage(message, field), results);
368371
}
369-
370-
if (field->enum_type() != nullptr) {
371-
int number = message.GetReflection()->GetEnumValue(message, field);
372+
}
373+
// Validating ENUM value
374+
if (field->enum_type() != nullptr) {
375+
// Handling repeated enum values. Ex: OptionTargetType option
376+
if (field->is_repeated()) {
377+
for (int index = 0; index < reflector->FieldSize(message, field);
378+
index++) {
379+
int number = reflector->GetRepeatedEnumValue(message, field, index);
380+
auto value = field->enum_type()->FindValueByNumber(number);
381+
if (value == nullptr) {
382+
continue;
383+
}
384+
ValidateSingleFeatureLifetimes(edition, value->full_name(),
385+
value->options().feature_support(),
386+
results);
387+
}
388+
} else {
389+
int number = reflector->GetEnumValue(message, field);
372390
auto value = field->enum_type()->FindValueByNumber(number);
373391
if (value == nullptr) {
374-
results.errors.emplace_back(absl::StrCat(
375-
"Feature ", field->full_name(), " has no known value ", number));
376392
continue;
377393
}
378394
ValidateSingleFeatureLifetimes(edition, value->full_name(),
379395
value->options().feature_support(),
380396
results);
381397
}
382398
}
383-
// TODO: Support custom options
384-
if (field->is_extension()) {
385-
continue;
386-
}
387399
ValidateSingleFeatureLifetimes(edition, field->full_name(),
388400
field->options().feature_support(), results);
389401
}
@@ -521,10 +533,7 @@ FeatureResolver::ValidationResults FeatureResolver::ValidateFeatureLifetimes(
521533
const Message* pool_option = nullptr;
522534
DynamicMessageFactory factory;
523535
std::unique_ptr<Message> message_storage;
524-
bool is_feature =
525-
option.GetDescriptor()->name() == FeatureSet::descriptor()->name();
526-
// TODO: Support custom options
527-
if (pool_descriptor != nullptr && is_feature) {
536+
if (pool_descriptor != nullptr) {
528537
// Move the messages back to the current pool so that we can reflect on
529538
// any extensions.
530539
message_storage =
@@ -543,7 +552,7 @@ FeatureResolver::ValidationResults FeatureResolver::ValidateFeatureLifetimes(
543552

544553
ValidationResults results;
545554
// Validate feature support
546-
ValidateFeatureLifetimesImpl(edition, *pool_option, results, is_feature);
555+
ValidateFeatureLifetimesImpl(edition, *pool_option, results);
547556

548557
return results;
549558
}

0 commit comments

Comments
 (0)