Automated rollback of commit 138b748279110e99082118561e572d7212e59ac1.
PiperOrigin-RevId: 843860118
diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc
index e4c9557..846507c 100644
--- a/src/google/protobuf/compiler/command_line_interface_unittest.cc
+++ b/src/google/protobuf/compiler/command_line_interface_unittest.cc
@@ -2631,7 +2631,8 @@
Run("protocol_compiler --proto_path=$tmpdir "
"foo.proto --test_out=$tmpdir --experimental_editions");
ExpectErrorSubstring(
- "The `java_multiple_files` behavior is enabled by default");
+ "google.protobuf.FileOptions.java_multiple_files has been removed in edition "
+ "2024: This behavior is enabled by default");
}
@@ -2645,7 +2646,8 @@
Run("protocol_compiler --proto_path=$tmpdir "
"foo.proto --test_out=$tmpdir --experimental_editions");
ExpectErrorSubstring(
- "The `java_multiple_files` behavior is enabled by default");
+ "google.protobuf.FileOptions.java_multiple_files has been removed in edition "
+ "2024: This behavior is enabled by default");
}
diff --git a/src/google/protobuf/descriptor.cc b/src/google/protobuf/descriptor.cc
index 2931c67..be1febc 100644
--- a/src/google/protobuf/descriptor.cc
+++ b/src/google/protobuf/descriptor.cc
@@ -1611,8 +1611,9 @@
}
struct LifetimesInfo {
- const FeatureSet* proto_features;
+ const Message* option_to_validate; // features or options to validate
const Message* proto;
+ bool is_feature;
absl::string_view full_name;
absl::string_view filename;
};
@@ -1640,16 +1641,19 @@
if (lifetimes_info_map_.empty()) return true;
static absl::string_view feature_set_name = "google.protobuf.FeatureSet";
- const Descriptor* feature_set =
- pool_->FindMessageTypeByName(feature_set_name);
-
bool has_errors = false;
for (const auto& it : lifetimes_info_map_) {
const FileDescriptor* file = it.first;
for (const auto& info : it.second) {
+ const Descriptor* feature_set_descriptor = nullptr;
+ // TODO: Support custom options
+ if (info.is_feature) {
+ feature_set_descriptor =
+ pool_->FindMessageTypeByName(feature_set_name);
+ }
auto results = FeatureResolver::ValidateFeatureLifetimes(
- file->edition(), *info.proto_features, feature_set);
+ file->edition(), *info.option_to_validate, feature_set_descriptor);
for (const auto& error : results.errors) {
has_errors = true;
if (error_collector_ == nullptr) {
@@ -6227,6 +6231,11 @@
}
}
+template <typename OptionT>
+bool IsDefaultInstance(const OptionT& opt) {
+ return &opt == &OptionT::default_instance();
+}
+
const FileDescriptor* DescriptorBuilder::BuildFile(
const FileDescriptorProto& proto) {
// Ensure the generated pool has been lazily initialized. This is most
@@ -6685,10 +6694,17 @@
if (!had_errors_ && !pool_->lazily_build_dependencies_) {
internal::VisitDescriptors(
*result, proto, [&](const auto& descriptor, const auto& desc_proto) {
- if (descriptor.proto_features_ != &FeatureSet::default_instance()) {
+ if (!IsDefaultInstance(*descriptor.proto_features_)) {
deferred_validation_.ValidateFeatureLifetimes(
- GetFile(descriptor), {descriptor.proto_features_, &desc_proto,
- GetFullName(descriptor), proto.name()});
+ GetFile(descriptor),
+ {descriptor.proto_features_, &desc_proto, /*is_feature=*/true,
+ GetFullName(descriptor), proto.name()});
+ }
+ if (!IsDefaultInstance(*descriptor.options_)) {
+ deferred_validation_.ValidateFeatureLifetimes(
+ GetFile(descriptor),
+ {descriptor.options_, &desc_proto,
+ /*is_feature=*/false, GetFullName(descriptor), proto.name()});
}
});
}
@@ -8394,13 +8410,6 @@
}
if (file->edition() >= Edition::EDITION_2024) {
- if (file->options().has_java_multiple_files()) {
- AddError(file->name(), proto, DescriptorPool::ErrorCollector::OPTION_NAME,
- "The `java_multiple_files` behavior is enabled by default in "
- "editions 2024 and above. To disable it, you can set "
- "`features.(pb.java).nest_in_file_class = YES` on individual "
- "messages, enums, or services.");
- }
if (file->weak_dependency_count() > 0) {
AddError("weak", proto, DescriptorPool::ErrorCollector::IMPORT,
"weak imports are not allowed under edition 2024 and beyond.");
diff --git a/src/google/protobuf/descriptor_unittest.cc b/src/google/protobuf/descriptor_unittest.cc
index 6ab352c..b35b00c 100644
--- a/src/google/protobuf/descriptor_unittest.cc
+++ b/src/google/protobuf/descriptor_unittest.cc
@@ -12323,6 +12323,49 @@
"Custom feature removal error\n");
}
+TEST_F(FeaturesTest, RemovedOption) {
+ BuildDescriptorMessagesInTestPool();
+ BuildFileWithErrors(
+ R"pb(
+ name: "foo.proto"
+ syntax: "editions"
+ edition: EDITION_2024
+ options { java_multiple_files: true }
+ )pb",
+ "foo.proto: foo.proto: NAME: "
+ "google.protobuf.FileOptions.java_multiple_files has been removed in edition "
+ "2024: This behavior is enabled by default in "
+ "editions 2024 and above. To disable it, you can set "
+ "`features.(pb.java).nest_in_file_class = YES` on individual messages, "
+ "enums, or services.\n");
+}
+
+TEST_F(FeaturesTest, RemoveOptionAndFeature) {
+ BuildDescriptorMessagesInTestPool();
+ BuildFileInTestPool(pb::TestFeatures::descriptor()->file());
+ BuildFileWithErrors(
+ R"pb(
+ name: "foo.proto"
+ syntax: "editions"
+ edition: EDITION_2024
+ dependency: "google/protobuf/unittest_features.proto"
+ options {
+ java_multiple_files: true
+ features {
+ [pb.test] { removed_feature: VALUE9 }
+ }
+ }
+ )pb",
+ "foo.proto: foo.proto: NAME: "
+ "pb.TestFeatures.removed_feature has been removed in edition 2024: "
+ "Custom feature removal error\n"
+ "foo.proto: foo.proto: NAME: google.protobuf.FileOptions.java_multiple_files has "
+ "been removed in edition 2024: This behavior is enabled by default in "
+ "editions 2024 and above. To disable it, you can set "
+ "`features.(pb.java).nest_in_file_class = YES` on individual messages, "
+ "enums, or services.\n");
+}
+
TEST_F(FeaturesTest, RemovedFeatureDefault) {
BuildDescriptorMessagesInTestPool();
BuildFileInTestPool(pb::TestFeatures::descriptor()->file());
diff --git a/src/google/protobuf/feature_resolver.cc b/src/google/protobuf/feature_resolver.cc
index 1ea9047..48f4e43 100644
--- a/src/google/protobuf/feature_resolver.cc
+++ b/src/google/protobuf/feature_resolver.cc
@@ -399,32 +399,40 @@
}
void ValidateFeatureLifetimesImpl(Edition edition, const Message& message,
- FeatureResolver::ValidationResults& results) {
+ FeatureResolver::ValidationResults& results,
+ bool is_feature) {
std::vector<const FieldDescriptor*> fields;
message.GetReflection()->ListFields(message, &fields);
for (const FieldDescriptor* field : fields) {
- // Recurse into message extension.
- if (field->is_extension() &&
- field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
- ValidateFeatureLifetimesImpl(
- edition, message.GetReflection()->GetMessage(message, field),
- results);
- continue;
- }
-
- if (field->enum_type() != nullptr) {
- int number = message.GetReflection()->GetEnumValue(message, field);
- auto value = field->enum_type()->FindValueByNumber(number);
- if (value == nullptr) {
- results.errors.emplace_back(absl::StrCat(
- "Feature ", field->full_name(), " has no known value ", number));
+ // TODO: Support repeated option enum values and custom options
+ // feature support
+ if (is_feature) {
+ // Recurse into message extension.
+ if (field->is_extension() &&
+ field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+ ValidateFeatureLifetimesImpl(
+ edition, message.GetReflection()->GetMessage(message, field),
+ results, is_feature);
continue;
}
- ValidateSingleFeatureLifetimes(edition, value->full_name(),
- value->options().feature_support(),
- results);
- }
+ if (field->enum_type() != nullptr) {
+ int number = message.GetReflection()->GetEnumValue(message, field);
+ auto value = field->enum_type()->FindValueByNumber(number);
+ if (value == nullptr) {
+ results.errors.emplace_back(absl::StrCat(
+ "Feature ", field->full_name(), " has no known value ", number));
+ continue;
+ }
+ ValidateSingleFeatureLifetimes(edition, value->full_name(),
+ value->options().feature_support(),
+ results);
+ }
+ }
+ // TODO: Support custom options
+ if (field->is_extension()) {
+ continue;
+ }
ValidateSingleFeatureLifetimes(edition, field->full_name(),
field->options().feature_support(), results);
}
@@ -558,30 +566,34 @@
}
FeatureResolver::ValidationResults FeatureResolver::ValidateFeatureLifetimes(
- Edition edition, const FeatureSet& features,
- const Descriptor* pool_descriptor) {
- const Message* pool_features = nullptr;
+ Edition edition, const Message& option, const Descriptor* pool_descriptor) {
+ const Message* pool_option = nullptr;
DynamicMessageFactory factory;
- std::unique_ptr<Message> features_storage;
- if (pool_descriptor == nullptr) {
- // The FeatureSet descriptor can be null if no custom extensions are defined
- // in any transitive dependency. In this case, we can just use the
- // generated pool for validation, since there wouldn't be any feature
- // extensions defined anyway.
- pool_features = &features;
- } else {
- // Move the features back to the current pool so that we can reflect on any
- // extensions.
- features_storage =
+ std::unique_ptr<Message> message_storage;
+ bool is_feature =
+ option.GetDescriptor()->name() == FeatureSet::descriptor()->name();
+ // TODO: Support custom options
+ if (pool_descriptor != nullptr && is_feature) {
+ // Move the messages back to the current pool so that we can reflect on
+ // any extensions.
+ message_storage =
absl::WrapUnique(factory.GetPrototype(pool_descriptor)->New());
- // TODO: Remove this suppression.
- (void)features_storage->ParseFromString(features.SerializeAsString());
- pool_features = features_storage.get();
+ // TODO: Remove this suppression
+ (void)message_storage->ParseFromString(option.SerializeAsString());
+ pool_option = message_storage.get();
+ } else {
+ // The Message descriptor can be null if no custom extensions are
+ // defined in any transitive dependency. In this case, we can just use
+ // the generated pool for validation, since there wouldn't be any feature
+ // extensions defined anyway.
+ pool_option = &option;
}
- ABSL_CHECK(pool_features != nullptr);
+ ABSL_CHECK(pool_option != nullptr);
ValidationResults results;
- ValidateFeatureLifetimesImpl(edition, *pool_features, results);
+ // Validate feature support
+ ValidateFeatureLifetimesImpl(edition, *pool_option, results, is_feature);
+
return results;
}
diff --git a/src/google/protobuf/feature_resolver.h b/src/google/protobuf/feature_resolver.h
index d66c156..985e0bf 100644
--- a/src/google/protobuf/feature_resolver.h
+++ b/src/google/protobuf/feature_resolver.h
@@ -70,7 +70,7 @@
std::vector<std::string> warnings;
};
static ValidationResults ValidateFeatureLifetimes(
- Edition edition, const FeatureSet& features,
+ Edition edition, const Message& option,
const Descriptor* pool_descriptor);
private:
@@ -91,4 +91,3 @@
#include "google/protobuf/port_undef.inc"
#endif // GOOGLE_PROTOBUF_FEATURE_RESOLVER_H__
-
diff --git a/src/google/protobuf/feature_resolver_test.cc b/src/google/protobuf/feature_resolver_test.cc
index 6d5c658..131f939 100644
--- a/src/google/protobuf/feature_resolver_test.cc
+++ b/src/google/protobuf/feature_resolver_test.cc
@@ -10,6 +10,7 @@
#include <utility>
#include <vector>
+#include "google/protobuf/descriptor.pb.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/log/absl_check.h"
@@ -598,7 +599,7 @@
EXPECT_THAT(edition_2023_feature, HasError(HasSubstr("No valid default")));
}
-TEST(FeatureResolverLifetimesTest, Valid) {
+TEST(FeatureResolverLifetimesTest, ValidFeature) {
FeatureSet features = ParseTextOrDie(R"pb(
[pb.test] { file_feature: VALUE1 }
)pb");
@@ -618,6 +619,16 @@
EXPECT_THAT(results.warnings, IsEmpty());
}
+TEST(FeatureResolverLifetimesTest, ValidOption) {
+ FileOptions options = ParseTextOrDie(R"pb(
+ java_multiple_files: true
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2023, options, nullptr);
+ EXPECT_THAT(results.errors, IsEmpty());
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
TEST(FeatureResolverLifetimesTest, DeprecatedFeature) {
FeatureSet features = ParseTextOrDie(R"pb(
[pb.test] { removed_feature: VALUE1 }
@@ -632,6 +643,58 @@
HasSubstr("Custom feature deprecation warning"))));
}
+TEST(FeatureResolverLifetimesTest, CustomRemovedOptionNotSupported) {
+ MessageOptions option = ParseTextOrDie(R"pb(
+ [proto2_unittest.removed_option]: true
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2024, option, nullptr);
+ EXPECT_THAT(results.errors, IsEmpty());
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
+TEST(FeatureResolverLifetimesTest, CustomDeprecatedOptionNotSupported) {
+ MessageOptions option = ParseTextOrDie(R"pb(
+ [proto2_unittest.deprecated_option]: true
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2024, option, nullptr);
+ EXPECT_THAT(results.errors, IsEmpty());
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
+TEST(FeatureResolverLifetimesTest, CustomRemovedMessageOptionNotSupported) {
+ MessageOptions option = ParseTextOrDie(R"pb(
+ [proto2_unittest.custom_option] { removed_message_option: "test" }
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2024, option, nullptr);
+ EXPECT_THAT(results.errors, IsEmpty());
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
+TEST(FeatureResolverLifetimesTest, CustomDeprecatedMessageOptionNotSupported) {
+ MessageOptions option = ParseTextOrDie(R"pb(
+ [proto2_unittest.custom_option] { deprecated_message_option: "test" }
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2024, option, nullptr);
+ EXPECT_THAT(results.errors, IsEmpty());
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
+TEST(FeatureResolverLifetimesTest, CustomNestedMessageOptionNotSupported) {
+ MessageOptions option = ParseTextOrDie(R"pb(
+ [proto2_unittest.CustomOption.Nested.nested_custom_option] {
+ removed_message_option: "test"
+ }
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2024, option, nullptr);
+ EXPECT_THAT(results.errors, IsEmpty());
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
TEST(FeatureResolverLifetimesTest, RemovedFeature) {
FeatureSet features = ParseTextOrDie(R"pb(
[pb.test] { removed_feature: VALUE1 }
@@ -659,6 +722,22 @@
EXPECT_THAT(results.warnings, IsEmpty());
}
+TEST(FeatureResolverLifetimesTest, RemovedOption) {
+ FileOptions options = ParseTextOrDie(R"pb(
+ java_multiple_files: true
+ )pb");
+ auto results =
+ FeatureResolver::ValidateFeatureLifetimes(EDITION_2024, options, nullptr);
+ EXPECT_THAT(
+ results.errors,
+ ElementsAre(AllOf(
+ HasSubstr("google.protobuf.FileOptions.java_multiple_files"),
+ HasSubstr("removed in edition 2024:"),
+ HasSubstr(
+ "you can set `features.(pb.java).nest_in_file_class = YES`"))));
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
TEST(FeatureResolverLifetimesTest, RemovedFeatureWithNoRemovalError) {
FeatureSet features = ParseTextOrDie(R"pb(
[pb.test] { same_edition_removed_feature: VALUE1 }
@@ -672,7 +751,7 @@
EXPECT_THAT(results.warnings, IsEmpty());
}
-TEST(FeatureResolverLifetimesTest, NotIntroduced) {
+TEST(FeatureResolverLifetimesTest, NotIntroducedFeature) {
FeatureSet features = ParseTextOrDie(R"pb(
[pb.test] { future_feature: VALUE1 }
)pb");
@@ -686,6 +765,20 @@
EXPECT_THAT(results.warnings, IsEmpty());
}
+TEST(FeatureResolverLifetimesTest, NotIntroducedOption) {
+ FileOptions options = ParseTextOrDie(R"pb(
+ java_multiple_files: true
+ )pb");
+ auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_1_TEST_ONLY,
+ options, nullptr);
+ EXPECT_THAT(
+ results.errors,
+ ElementsAre(AllOf(HasSubstr("google.protobuf.FileOptions.java_multiple_files"),
+ HasSubstr("wasn't introduced until edition PROTO2"),
+ HasSubstr("can't be used in edition 1_TEST_ONLY"))));
+ EXPECT_THAT(results.warnings, IsEmpty());
+}
+
TEST(FeatureResolverLifetimesTest, WarningsAndErrors) {
FeatureSet features = ParseTextOrDie(R"pb(
[pb.test] { future_feature: VALUE1 removed_feature: VALUE1 }
@@ -694,8 +787,10 @@
features, nullptr);
EXPECT_THAT(results.errors,
ElementsAre(HasSubstr("pb.TestFeatures.future_feature")));
- EXPECT_THAT(results.warnings,
- ElementsAre(HasSubstr("pb.TestFeatures.removed_feature")));
+ EXPECT_THAT(
+ results.warnings,
+ ElementsAre(AllOf(HasSubstr("pb.TestFeatures.removed_feature"),
+ HasSubstr("Custom feature deprecation warning"))));
}
TEST(FeatureResolverLifetimesTest, MultipleErrors) {
@@ -710,7 +805,7 @@
EXPECT_THAT(results.warnings, IsEmpty());
}
-TEST(FeatureResolverLifetimesTest, DynamicPool) {
+TEST(FeatureResolverLifetimesTest, FeatureDynamicPool) {
DescriptorPool pool;
{
FileDescriptorProto file;
diff --git a/src/google/protobuf/unittest_custom_options.proto b/src/google/protobuf/unittest_custom_options.proto
index 3ebb8e3..fa39a1c 100644
--- a/src/google/protobuf/unittest_custom_options.proto
+++ b/src/google/protobuf/unittest_custom_options.proto
@@ -71,6 +71,35 @@
optional MethodOpt1 method_opt1 = 7890860;
}
+// Custom options with feature support
+extend google.protobuf.MessageOptions {
+ optional bool removed_option = 7733026 [feature_support = {
+ edition_removed: EDITION_2023
+ removal_error: "Custom feature removal warning"
+ }];
+ optional bool deprecated_option = 7737036 [feature_support = {
+ edition_deprecated: EDITION_2023
+ deprecation_warning: "Custom feature deprecation warning"
+ }];
+ optional CustomOption custom_option = 7737026;
+}
+
+message CustomOption {
+ optional string removed_message_option = 7734026 [feature_support = {
+ edition_removed: EDITION_2023
+ removal_error: "Custom feature removal warning"
+ }];
+ optional string deprecated_message_option = 7734016 [feature_support = {
+ edition_deprecated: EDITION_2023
+ deprecation_warning: "Custom feature deprecation warning"
+ }];
+ message Nested {
+ extend google.protobuf.MessageOptions {
+ optional CustomOption nested_custom_option = 7732016;
+ }
+ }
+}
+
// A test message with custom options at all possible locations (and also some
// regular options, to make sure they interact nicely).
message TestMessageWithCustomOptions {