Skip to content

Commit 24b91a7

Browse files
mkruskal-googlecopybara-github
authored andcommitted
Prohibit using features in the same file they're defined in.
This is an edge case we can't handle properly today. Rather than allowing poorly defined behavior, we'll make this an error condition until we can actually support it. In the future, it may be necessary to upgrade feature files to newer editions. Closes #16756 PiperOrigin-RevId: 634512378
1 parent baeab50 commit 24b91a7

File tree

2 files changed

+70
-16
lines changed

2 files changed

+70
-16
lines changed

src/google/protobuf/descriptor.cc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,19 +1110,6 @@ void RestoreFeaturesToOptions(const FeatureSet* features, ProtoT* proto) {
11101110
}
11111111
}
11121112

1113-
template <typename OptionsT>
1114-
bool HasFeatures(const OptionsT& options) {
1115-
if (options.has_features()) return true;
1116-
1117-
for (const auto& opt : options.uninterpreted_option()) {
1118-
if (opt.name_size() > 0 && opt.name(0).name_part() == "features" &&
1119-
!opt.name(0).is_extension()) {
1120-
return true;
1121-
}
1122-
}
1123-
return false;
1124-
}
1125-
11261113
template <typename DescriptorT>
11271114
absl::string_view GetFullName(const DescriptorT& desc) {
11281115
return desc.full_name();
@@ -8784,6 +8771,19 @@ bool DescriptorBuilder::OptionInterpreter::InterpretSingleOption(
87848771
// accumulate field numbers to form path to interpreted option
87858772
dest_path.push_back(field->number());
87868773

8774+
// Special handling to prevent feature use in the same file as the
8775+
// definition.
8776+
// TODO Add proper support for cases where this can work.
8777+
if (field->file() == builder_->file_ &&
8778+
uninterpreted_option_->name(0).name_part() == "features" &&
8779+
!uninterpreted_option_->name(0).is_extension()) {
8780+
return AddNameError([&] {
8781+
return absl::StrCat(
8782+
"Feature \"", debug_msg_name,
8783+
"\" can't be used in the same file it's defined in.");
8784+
});
8785+
}
8786+
87878787
if (i < uninterpreted_option_->name_size() - 1) {
87888788
if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
87898789
return AddNameError([&] {

src/google/protobuf/descriptor_unittest.cc

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4059,8 +4059,8 @@ class ValidationErrorTest : public testing::Test {
40594059
return ABSL_DIE_IF_NULL(pool_.BuildFile(file_proto));
40604060
}
40614061

4062-
const FileDescriptor* ParseAndBuildFile(absl::string_view file_name,
4063-
absl::string_view file_text) {
4062+
FileDescriptorProto ParseFile(absl::string_view file_name,
4063+
absl::string_view file_text) {
40644064
io::ArrayInputStream input_stream(file_text.data(), file_text.size());
40654065
SimpleErrorCollector error_collector;
40664066
io::Tokenizer tokenizer(&input_stream, &error_collector);
@@ -4072,7 +4072,12 @@ class ValidationErrorTest : public testing::Test {
40724072
<< file_text;
40734073
ABSL_CHECK_EQ("", error_collector.last_error());
40744074
proto.set_name(file_name);
4075-
return pool_.BuildFile(proto);
4075+
return proto;
4076+
}
4077+
4078+
const FileDescriptor* ParseAndBuildFile(absl::string_view file_name,
4079+
absl::string_view file_text) {
4080+
return pool_.BuildFile(ParseFile(file_name, file_text));
40764081
}
40774082

40784083

@@ -4096,6 +4101,17 @@ class ValidationErrorTest : public testing::Test {
40964101
BuildFileWithErrors(file_proto, expected_errors);
40974102
}
40984103

4104+
// Parse a proto file and build it. Expect errors to be produced which match
4105+
// the given error text.
4106+
void ParseAndBuildFileWithErrors(absl::string_view file_name,
4107+
absl::string_view file_text,
4108+
absl::string_view expected_errors) {
4109+
MockErrorCollector error_collector;
4110+
EXPECT_TRUE(pool_.BuildFileCollectingErrors(ParseFile(file_name, file_text),
4111+
&error_collector) == nullptr);
4112+
EXPECT_EQ(expected_errors, error_collector.text_);
4113+
}
4114+
40994115
// Parse file_text as a FileDescriptorProto in text format and add it
41004116
// to the DescriptorPool. Expect errors to be produced which match the
41014117
// given warning text.
@@ -10354,6 +10370,44 @@ TEST_F(FeaturesTest, InvalidOpenEnumNonZeroFirstValue) {
1035410370
"enums.\n");
1035510371
}
1035610372

10373+
TEST_F(FeaturesTest, InvalidUseFeaturesInSameFile) {
10374+
BuildDescriptorMessagesInTestPool();
10375+
ParseAndBuildFileWithErrors("foo.proto", R"schema(
10376+
edition = "2023";
10377+
10378+
package test;
10379+
import "google/protobuf/descriptor.proto";
10380+
10381+
message Foo {
10382+
string bar = 1 [
10383+
features.(test.custom).foo = "xyz",
10384+
features.(test.another) = {foo: -321}
10385+
];
10386+
}
10387+
10388+
message Custom {
10389+
string foo = 1 [features = { [test.custom]: {foo: "abc"} }];
10390+
}
10391+
message Another {
10392+
Enum foo = 1;
10393+
}
10394+
10395+
enum Enum {
10396+
option features.enum_type = CLOSED;
10397+
ZERO = 0;
10398+
ONE = 1;
10399+
}
10400+
10401+
extend google.protobuf.FeatureSet {
10402+
Custom custom = 1002 [features.message_encoding=DELIMITED];
10403+
Another another = 1001;
10404+
}
10405+
)schema",
10406+
"foo.proto: test.Foo.bar: OPTION_NAME: Feature "
10407+
"\"features.(test.custom)\" can't be used in the "
10408+
"same file it's defined in.\n");
10409+
}
10410+
1035710411
TEST_F(FeaturesTest, ClosedEnumNonZeroFirstValue) {
1035810412
BuildDescriptorMessagesInTestPool();
1035910413
const FileDescriptor* file = BuildFile(

0 commit comments

Comments
 (0)