Skip to content

Commit ed3c571

Browse files
karenwuzcopybara-github
authored andcommitted
Generalizing and implementing ValidateFeatureSupport for both Options and Features during proto parsing
PiperOrigin-RevId: 845387685
1 parent ab964c0 commit ed3c571

File tree

10 files changed

+464
-221
lines changed

10 files changed

+464
-221
lines changed

cmake/abseil-cpp.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,6 @@ else()
9292
)
9393
set(protobuf_ABSL_USED_TEST_TARGETS
9494
absl::scoped_mock_log
95+
absl::status_matchers
9596
)
9697
endif ()

src/google/protobuf/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,7 @@ cc_test(
18931893
"@abseil-cpp//absl/log:die_if_null",
18941894
"@abseil-cpp//absl/memory",
18951895
"@abseil-cpp//absl/status",
1896+
"@abseil-cpp//absl/status:status_matchers",
18961897
"@abseil-cpp//absl/status:statusor",
18971898
"@abseil-cpp//absl/strings",
18981899
"@googletest//:gtest",

src/google/protobuf/compiler/command_line_interface.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,7 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
13671367
descriptor_pool->EnforceWeakDependencies(true);
13681368
descriptor_pool->EnforceSymbolVisibility(true);
13691369
descriptor_pool->EnforceNamingStyle(true);
1370+
descriptor_pool->EnforceFeatureSupportValidation(true);
13701371

13711372
if (!SetupFeatureResolution(*descriptor_pool)) {
13721373
return EXIT_FAILURE;

src/google/protobuf/compiler/command_line_interface_unittest.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,41 @@ TEST_F(CommandLineInterfaceTest, ImportOptions_MissingImport) {
16171617
ExpectErrorSubstring("options.proto: File not found");
16181618
}
16191619

1620+
TEST_F(CommandLineInterfaceTest, ValidateFeatureSupportError) {
1621+
CreateTempFile("foo.proto",
1622+
R"schema(
1623+
edition = "2023";
1624+
option features.field_presence = IMPLICIT;
1625+
message Foo {
1626+
int32 bar = 1 [
1627+
feature_support = {
1628+
edition_removed: EDITION_2023
1629+
}
1630+
];
1631+
})schema");
1632+
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
1633+
ExpectErrorSubstring(
1634+
"foo.proto: Foo.bar has been removed but does not specify a removal "
1635+
"error.");
1636+
}
1637+
1638+
TEST_F(CommandLineInterfaceTest, ValidateFeatureSupportValid) {
1639+
CreateTempFile("foo.proto",
1640+
R"schema(
1641+
edition = "2023";
1642+
option features.field_presence = IMPLICIT;
1643+
message Foo {
1644+
int32 bar = 1 [
1645+
feature_support = {
1646+
edition_removed: EDITION_2023
1647+
removal_error: "Custom removal error"
1648+
}
1649+
];
1650+
})schema");
1651+
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
1652+
ExpectNoErrors();
1653+
}
1654+
16201655
TEST_F(CommandLineInterfaceTest, FeatureValidationError) {
16211656
CreateTempFile("foo.proto",
16221657
R"schema(

src/google/protobuf/descriptor.cc

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,7 +1638,9 @@ class DescriptorPool::DeferredValidation {
16381638
}
16391639

16401640
bool Validate() {
1641-
if (lifetimes_info_map_.empty()) return true;
1641+
if (lifetimes_info_map_.empty()) {
1642+
return true;
1643+
}
16421644

16431645
static absl::string_view feature_set_name = "google.protobuf.FeatureSet";
16441646
bool has_errors = false;
@@ -5013,6 +5015,9 @@ class DescriptorBuilder {
50135015
const DescriptorProto::ExtensionRange& proto) {}
50145016
void ValidateExtensionRangeOptions(const DescriptorProto& proto,
50155017
const Descriptor& message);
5018+
void MaybeAddFeatureSupportError(const absl::Status& feature_support_status,
5019+
const Message& proto,
5020+
absl::string_view full_name);
50165021
void ValidateExtensionDeclaration(
50175022
absl::string_view full_name,
50185023
const RepeatedPtrField<ExtensionRangeOptions_Declaration>& declarations,
@@ -8503,11 +8508,28 @@ void DescriptorBuilder::ValidateOptions(const OneofDescriptor* /*oneof*/,
85038508
}
85048509

85058510

8511+
void DescriptorBuilder::MaybeAddFeatureSupportError(
8512+
const absl::Status& feature_support_status, const Message& proto,
8513+
absl::string_view full_name) {
8514+
if (feature_support_status.ok()) {
8515+
return;
8516+
}
8517+
std::string feature_support_error(feature_support_status.message());
8518+
AddError(full_name, proto, DescriptorPool::ErrorCollector::OPTION_NAME,
8519+
feature_support_error.c_str());
8520+
}
8521+
85068522
void DescriptorBuilder::ValidateOptions(const FieldDescriptor* field,
85078523
const FieldDescriptorProto& proto) {
85088524
if (pool_->lazily_build_dependencies_ && (!field || !field->message_type())) {
85098525
return;
85108526
}
8527+
if (pool_->enforce_feature_support_validation_) {
8528+
absl::Status feature_support_status =
8529+
FeatureResolver::ValidateFieldFeatureSupport(*field);
8530+
MaybeAddFeatureSupportError(feature_support_status, proto,
8531+
field->full_name());
8532+
}
85118533

85128534
ValidateFieldFeatures(field, proto);
85138535

@@ -8838,10 +8860,15 @@ void DescriptorBuilder::ValidateOptions(const EnumDescriptor* enm,
88388860
}
88398861
}
88408862

8841-
void DescriptorBuilder::ValidateOptions(
8842-
const EnumValueDescriptor* /* enum_value */,
8843-
const EnumValueDescriptorProto& /* proto */) {
8844-
// Nothing to do so far.
8863+
void DescriptorBuilder::ValidateOptions(const EnumValueDescriptor* enum_value,
8864+
const EnumValueDescriptorProto& proto) {
8865+
if (pool_->enforce_feature_support_validation_) {
8866+
absl::Status feature_support_result =
8867+
FeatureResolver::ValidateFeatureSupport(
8868+
enum_value->options().feature_support(), enum_value->full_name());
8869+
MaybeAddFeatureSupportError(feature_support_result, proto,
8870+
enum_value->full_name());
8871+
}
88458872
}
88468873

88478874
namespace {

src/google/protobuf/descriptor.h

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,9 +1594,9 @@ class PROTOBUF_EXPORT EnumValueDescriptor : private internal::SymbolBaseN<0>,
15941594
EnumValueDescriptor& operator=(const EnumValueDescriptor&) = delete;
15951595
#endif
15961596

1597-
absl::string_view name() const; // Name of this enum constant.
1598-
int index() const; // Index within the enums's Descriptor.
1599-
int number() const; // Numeric value of this enum constant.
1597+
absl::string_view name() const; // Name of this enum constant.
1598+
int index() const; // Index within the enums's Descriptor.
1599+
int number() const; // Numeric value of this enum constant.
16001600

16011601
// The full_name of an enum value is a sibling symbol of the enum type.
16021602
// e.g. the full name of FieldDescriptorProto::TYPE_INT32 is actually
@@ -2365,6 +2365,14 @@ class PROTOBUF_EXPORT DescriptorPool {
23652365
// of this enforcement.
23662366
void EnforceNamingStyle(bool enforce) { enforce_naming_style_ = enforce; }
23672367

2368+
// Enforce validation of feature support.
2369+
//
2370+
// This is used to guard feature support validation for the lifetimes of
2371+
// options and features.
2372+
void EnforceFeatureSupportValidation(bool enforce) {
2373+
enforce_feature_support_validation_ = enforce;
2374+
}
2375+
23682376
// Enforce symbol visibility rules. This will enable enforcement of the
23692377
// `export` and `local` keywords added in edition 2024, honoring the behavior
23702378
// of the `default_symbol_visibility` feature.
@@ -2670,6 +2678,7 @@ class PROTOBUF_EXPORT DescriptorPool {
26702678
bool disallow_enforce_utf8_;
26712679
bool deprecated_legacy_json_field_conflicts_;
26722680
bool enforce_naming_style_;
2681+
bool enforce_feature_support_validation_ = false;
26732682
bool enforce_symbol_visibility_ = false;
26742683
mutable bool build_started_ = false;
26752684

@@ -3221,8 +3230,8 @@ PROTOBUF_EXPORT inline bool IsTrackingEnabled() {
32213230
}
32223231

32233232
template <typename F>
3224-
auto VisitDescriptorsInFileOrder(const Descriptor* desc,
3225-
F& f) -> decltype(f(desc)) {
3233+
auto VisitDescriptorsInFileOrder(const Descriptor* desc, F& f)
3234+
-> decltype(f(desc)) {
32263235
for (int i = 0; i < desc->nested_type_count(); i++) {
32273236
if (auto res = VisitDescriptorsInFileOrder(desc->nested_type(i), f)) {
32283237
return res;
@@ -3238,8 +3247,8 @@ auto VisitDescriptorsInFileOrder(const Descriptor* desc,
32383247
// If any call returns a "truthy" value, it stops visitation and returns that
32393248
// value right away. Otherwise returns `{}` after visiting all types.
32403249
template <typename F>
3241-
auto VisitDescriptorsInFileOrder(const FileDescriptor* file,
3242-
F f) -> decltype(f(file->message_type(0))) {
3250+
auto VisitDescriptorsInFileOrder(const FileDescriptor* file, F f)
3251+
-> decltype(f(file->message_type(0))) {
32433252
for (int i = 0; i < file->message_type_count(); i++) {
32443253
if (auto res = VisitDescriptorsInFileOrder(file->message_type(i), f)) {
32453254
return res;

src/google/protobuf/descriptor_unittest.cc

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4904,6 +4904,154 @@ TEST(CustomOptions, DebugString) {
49044904
descriptor->DebugString());
49054905
}
49064906

4907+
TEST(CustomOptions, FeatureSupportInvalidDeprecatedAfterRemoved) {
4908+
DescriptorPool pool;
4909+
pool.EnforceFeatureSupportValidation(true);
4910+
4911+
FileDescriptorProto file_proto;
4912+
FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
4913+
ASSERT_TRUE(pool.BuildFile(file_proto) != nullptr);
4914+
4915+
ASSERT_TRUE(TextFormat::ParseFromString(
4916+
R"pb(
4917+
name: "foo.proto"
4918+
edition: EDITION_2024
4919+
package: "proto2_unittest"
4920+
dependency: "google/protobuf/descriptor.proto"
4921+
extension {
4922+
name: "file_opt1"
4923+
number: 7739974
4924+
label: LABEL_OPTIONAL
4925+
type: TYPE_UINT64
4926+
extendee: ".google.protobuf.FieldOptions"
4927+
options {
4928+
feature_support {
4929+
edition_introduced: EDITION_2023
4930+
edition_deprecated: EDITION_2024
4931+
deprecation_warning: "warning"
4932+
edition_removed: EDITION_2024
4933+
removal_error: "Custom feature removal error"
4934+
}
4935+
}
4936+
})pb",
4937+
&file_proto));
4938+
4939+
MockErrorCollector error_collector;
4940+
EXPECT_FALSE(pool.BuildFileCollectingErrors(file_proto, &error_collector));
4941+
EXPECT_EQ(error_collector.text_,
4942+
"foo.proto: proto2_unittest.file_opt1: OPTION_NAME: proto"
4943+
"2_unittest.file_opt1 was deprecated after it was removed.\n");
4944+
}
4945+
4946+
TEST(CustomOptions, FeatureSupportInvalidValueDeprecatedAfterOption) {
4947+
DescriptorPool pool;
4948+
pool.EnforceFeatureSupportValidation(true);
4949+
4950+
FileDescriptorProto file_proto;
4951+
FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
4952+
ASSERT_TRUE(pool.BuildFile(file_proto) != nullptr);
4953+
4954+
ASSERT_TRUE(TextFormat::ParseFromString(
4955+
R"pb(
4956+
name: "foo.proto"
4957+
edition: EDITION_2024
4958+
package: "proto2_unittest"
4959+
dependency: "google/protobuf/descriptor.proto"
4960+
enum_type {
4961+
name: "Foo"
4962+
value { name: "UNKNOWN" number: 0 }
4963+
value {
4964+
name: "VALUE"
4965+
number: 1
4966+
options {
4967+
feature_support {
4968+
edition_deprecated: EDITION_99997_TEST_ONLY
4969+
deprecation_warning: "warning"
4970+
}
4971+
}
4972+
}
4973+
}
4974+
message_type {
4975+
name: "Bar"
4976+
extension {
4977+
name: "bool_field"
4978+
number: 7739973
4979+
label: LABEL_OPTIONAL
4980+
type: TYPE_ENUM
4981+
type_name: "Foo"
4982+
extendee: ".google.protobuf.FieldOptions"
4983+
options {
4984+
feature_support {
4985+
edition_introduced: EDITION_2023
4986+
edition_deprecated: EDITION_2024
4987+
deprecation_warning: "warning"
4988+
}
4989+
}
4990+
}
4991+
})pb",
4992+
&file_proto));
4993+
4994+
MockErrorCollector error_collector;
4995+
EXPECT_FALSE(pool.BuildFileCollectingErrors(file_proto, &error_collector));
4996+
EXPECT_THAT(error_collector.text_,
4997+
testing::HasSubstr(
4998+
"foo.proto: proto2_unittest.Bar.bool_field: "
4999+
"OPTION_NAME: value proto2_unittest.VALUE was "
5000+
"deprecated after proto2_unittest.Bar.bool_field was.\n"));
5001+
}
5002+
5003+
TEST(CustomOptions, FeatureSupportValid) {
5004+
DescriptorPool pool;
5005+
pool.EnforceFeatureSupportValidation(true);
5006+
5007+
FileDescriptorProto file_proto;
5008+
FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
5009+
ASSERT_TRUE(pool.BuildFile(file_proto) != nullptr);
5010+
5011+
ASSERT_TRUE(TextFormat::ParseFromString(
5012+
R"pb(
5013+
name: "foo.proto"
5014+
edition: EDITION_2024
5015+
package: "proto2_unittest"
5016+
dependency: "google/protobuf/descriptor.proto"
5017+
enum_type {
5018+
name: "Foo"
5019+
value { name: "UNKNOWN" number: 0 }
5020+
value {
5021+
name: "VALUE"
5022+
number: 1
5023+
options {
5024+
feature_support {
5025+
edition_introduced: EDITION_2024
5026+
edition_deprecated: EDITION_99997_TEST_ONLY
5027+
deprecation_warning: "warning"
5028+
}
5029+
}
5030+
}
5031+
}
5032+
message_type {
5033+
name: "Bar"
5034+
extension {
5035+
name: "bool_field"
5036+
number: 7739971
5037+
label: LABEL_OPTIONAL
5038+
type: TYPE_ENUM
5039+
type_name: "Foo"
5040+
extendee: ".google.protobuf.FieldOptions"
5041+
options {
5042+
feature_support {
5043+
edition_introduced: EDITION_2023
5044+
edition_removed: EDITION_99998_TEST_ONLY
5045+
removal_error: "removed"
5046+
}
5047+
}
5048+
}
5049+
})pb",
5050+
&file_proto));
5051+
5052+
EXPECT_NE(pool.BuildFile(file_proto), nullptr);
5053+
}
5054+
49075055
// ===================================================================
49085056

49095057
TEST_F(ValidationErrorTest, AlreadyDefined) {

0 commit comments

Comments
 (0)