// Protocol Buffers - Google's data interchange format // Copyright 2023 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "google/protobuf/compiler/retention.h" #include #include #include "google/protobuf/descriptor.pb.h" #include #include #include "absl/log/absl_check.h" #include "absl/log/die_if_null.h" #include "absl/strings/substitute.h" #include "google/protobuf/compiler/parser.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/dynamic_message.h" #include "google/protobuf/io/tokenizer.h" #include "google/protobuf/io/zero_copy_stream_impl_lite.h" #include "google/protobuf/text_format.h" namespace google { namespace protobuf { namespace compiler { namespace { MATCHER_P(EqualsProto, msg, "") { return msg.DebugString() == arg.DebugString(); } class FakeErrorCollector : public io::ErrorCollector { public: FakeErrorCollector() = default; ~FakeErrorCollector() override = default; void RecordError(int line, io::ColumnNumber column, absl::string_view message) override { ABSL_CHECK(false) << line << ":" << column << ": " << message; } void RecordWarning(int line, io::ColumnNumber column, absl::string_view message) override { ABSL_CHECK(false) << line << ":" << column << ": " << message; } }; class RetentionStripTest : public testing::Test { protected: void SetUp() override { FileDescriptorProto descriptor_proto_descriptor; FileDescriptorSet::descriptor()->file()->CopyTo( &descriptor_proto_descriptor); pool_.BuildFile(descriptor_proto_descriptor); } const FileDescriptor* ParseSchema(absl::string_view contents, absl::string_view file_name = "foo.proto") { std::string proto_file = absl::Substitute( R"schema( syntax = "proto2"; package google.protobuf.internal; import "$0"; $1 )schema", FileDescriptorSet::descriptor()->file()->name(), contents); io::ArrayInputStream input_stream(proto_file.data(), static_cast(proto_file.size())); FakeErrorCollector error_collector; io::Tokenizer tokenizer(&input_stream, &error_collector); Parser parser; parser.RecordErrorsTo(&error_collector); FileDescriptorProto file_descriptor; ABSL_CHECK(parser.Parse(&tokenizer, &file_descriptor)); file_descriptor.set_name(file_name); return pool_.BuildFile(file_descriptor); } template ProtoType BuildDynamicProto(absl::string_view data) { // We use a dynamic message to generate the expected options proto. This // lets us parse the custom options in text format. const Descriptor* file_options_descriptor = pool_.FindMessageTypeByName(ProtoType().GetTypeName()); DynamicMessageFactory factory; std::unique_ptr dynamic_message( factory.GetPrototype(file_options_descriptor)->New()); ABSL_CHECK(TextFormat::ParseFromString(data, dynamic_message.get())); ProtoType ret; ABSL_CHECK(ret.ParseFromString(dynamic_message->SerializeAsString())); return ret; } DescriptorPool pool_; }; TEST_F(RetentionStripTest, StripSourceRetentionFileOptions) { const FileDescriptor* file = ParseSchema(R"schema( option (source_retention_option) = 123; option (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }; option (repeated_options) = { i1: 111 i2: 222 }; message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.FileOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); FileOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); FileDescriptorProto stripped_file = StripSourceRetentionOptions(*file); EXPECT_THAT(StripSourceRetentionOptions(*file).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*file), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionProtoFileOptions) { const FileDescriptor* file = ParseSchema(R"schema( option (source_retention_option) = 123; option (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }; option (repeated_options) = { i1: 111 i2: 222 }; message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.FileOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; } )schema"); FileDescriptorProto proto; file->CopyTo(&proto); ASSERT_THAT(proto.options(), EqualsProto(BuildDynamicProto(R"pb( [google.protobuf.internal.source_retention_option]: 123 [google.protobuf.internal.options] { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } } [google.protobuf.internal.repeated_options] { i1: 111 i2: 222 })pb"))); StripSourceRetentionOptions(*file->pool(), proto); EXPECT_THAT(proto.options(), EqualsProto(BuildDynamicProto(R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"))); } TEST_F(RetentionStripTest, StripSourceRetentionMessageOptions) { const FileDescriptor* file = ParseSchema(R"schema( message TestMessage { option (source_retention_option) = 123; option (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }; option (repeated_options) = { i1: 111 i2: 222 }; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.MessageOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); MessageOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const Descriptor* message = ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage")); EXPECT_THAT(StripSourceRetentionOptions(*file).message_type(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*message).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*message), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionEnumOptions) { const FileDescriptor* file = ParseSchema(R"schema( enum TestEnum { option (source_retention_option) = 123; option (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }; option (repeated_options) = { i1: 111 i2: 222 }; VALUE1 = 0; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.EnumOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); EnumOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const EnumDescriptor* enm = ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum")); EXPECT_THAT(StripSourceRetentionOptions(*file).enum_type(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*enm).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*enm), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionEnumValueOptions) { const FileDescriptor* file = ParseSchema(R"schema( enum TestEnum { VALUE1 = 0 [(source_retention_option) = 123, (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }, (repeated_options) = { i1: 111 i2: 222 }]; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.EnumValueOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); EnumValueOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const EnumDescriptor* enm = ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum")); const EnumValueDescriptor* value = ABSL_DIE_IF_NULL(enm->value(0)); EXPECT_THAT( StripSourceRetentionOptions(*file).enum_type(0).value(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*enm).value(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*value), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionFieldOptions) { const FileDescriptor* file = ParseSchema(R"schema( message TestMessage { optional string test_field = 1 [(source_retention_option) = 123, (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }, (repeated_options) = { i1: 111 i2: 222 }]; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.FieldOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); FieldOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const Descriptor* message = ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage")); const FieldDescriptor* field = ABSL_DIE_IF_NULL(message->FindFieldByName("test_field")); EXPECT_THAT( StripSourceRetentionOptions(*file).message_type(0).field(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*message).field(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*field).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*field), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionExtensionOptions) { const FileDescriptor* file = ParseSchema(R"schema( message TestMessage { extensions 1; } extend TestMessage { optional string test_field = 1 [(source_retention_option) = 123, (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }, (repeated_options) = { i1: 111 i2: 222 }]; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.FieldOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); FieldOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const FieldDescriptor* field = ABSL_DIE_IF_NULL(file->FindExtensionByName("test_field")); EXPECT_THAT(StripSourceRetentionOptions(*file).extension(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*field).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*field), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionOneofOptions) { const FileDescriptor* file = ParseSchema(R"schema( message TestMessage { oneof test_oneof { option (source_retention_option) = 123; option (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }; option (repeated_options) = { i1: 111 i2: 222 }; string field1 = 1; string field2 = 2; }; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.OneofOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); OneofOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const Descriptor* message = ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage")); const OneofDescriptor* oneof = ABSL_DIE_IF_NULL(message->FindOneofByName("test_oneof")); EXPECT_THAT(StripSourceRetentionOptions(*file) .message_type(0) .oneof_decl(0) .options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*message).oneof_decl(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*oneof).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*oneof), EqualsProto(expected_options)); } TEST_F(RetentionStripTest, StripSourceRetentionExtensionRangeOptions) { const FileDescriptor* file = ParseSchema(R"schema( message TestMessage { extensions 1 to max [(source_retention_option) = 123, (options) = { i1: 123 i2: 456 c { s: "abc" } rc { s: "abc" } }, (repeated_options) = { i1: 111 i2: 222 }]; } message Options { optional int32 i1 = 1 [retention = RETENTION_SOURCE]; optional int32 i2 = 2; message ChildMessage { optional string s = 1 [retention = RETENTION_SOURCE]; } optional ChildMessage c = 3; repeated ChildMessage rc = 4; } extend google.protobuf.ExtensionRangeOptions { optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; optional Options options = 50001; repeated Options repeated_options = 50002; })schema"); ExtensionRangeOptions expected_options = BuildDynamicProto( R"pb( [google.protobuf.internal.options] { i2: 456 c {} rc {} } [google.protobuf.internal.repeated_options] { i2: 222 })pb"); const Descriptor* message = ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage")); const Descriptor::ExtensionRange* range = ABSL_DIE_IF_NULL(message->FindExtensionRangeContainingNumber(2)); EXPECT_THAT(StripSourceRetentionOptions(*file) .message_type(0) .extension_range(0) .options(), EqualsProto(expected_options)); EXPECT_THAT( StripSourceRetentionOptions(*message).extension_range(0).options(), EqualsProto(expected_options)); EXPECT_THAT(StripSourceRetentionOptions(*message, *range).options(), EqualsProto(expected_options)); EXPECT_THAT(StripLocalSourceRetentionOptions(*message, *range), EqualsProto(expected_options)); } } // namespace } // namespace compiler } // namespace protobuf } // namespace google