| // Protocol Buffers - Google's data interchange format |
| // Copyright 2023 Google LLC. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| |
| #include "upb/util/def_to_proto.h" |
| |
| #include <cstddef> |
| #include <memory> |
| #include <string> |
| |
| #include "google/protobuf/descriptor.pb.h" |
| #include "google/protobuf/descriptor.upb.h" |
| #include "google/protobuf/descriptor.upbdefs.h" |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include "google/protobuf/dynamic_message.h" |
| #include "google/protobuf/util/message_differencer.h" |
| #include "upb/base/string_view.h" |
| #include "upb/mem/arena.hpp" |
| #include "upb/reflection/def.hpp" |
| #include "upb/test/parse_text_proto.h" |
| #include "upb/util/def_to_proto_editions_test.upbdefs.h" |
| #include "upb/util/def_to_proto_test.h" |
| #include "upb/util/def_to_proto_test.upbdefs.h" |
| |
| namespace upb_test { |
| |
| // Loads and retrieves a descriptor for `msgdef` into the given `pool`. |
| const google::protobuf::Descriptor* AddMessageDescriptor( |
| upb::MessageDefPtr msgdef, google::protobuf::DescriptorPool* pool) { |
| upb::Arena tmp_arena; |
| upb::FileDefPtr file = msgdef.file(); |
| google_protobuf_FileDescriptorProto* upb_proto = |
| upb_FileDef_ToProto(file.ptr(), tmp_arena.ptr()); |
| size_t size; |
| const char* buf = google_protobuf_FileDescriptorProto_serialize( |
| upb_proto, tmp_arena.ptr(), &size); |
| google::protobuf::FileDescriptorProto google_proto; |
| google_proto.ParseFromArray(buf, size); |
| const google::protobuf::FileDescriptor* file_desc = |
| pool->BuildFile(google_proto); |
| EXPECT_TRUE(file_desc != nullptr); |
| return pool->FindMessageTypeByName(msgdef.full_name()); |
| } |
| |
| // Converts a upb `msg` (with type `msgdef`) into a protobuf Message object from |
| // the given factory and descriptor. |
| std::unique_ptr<google::protobuf::Message> ToProto( |
| const upb_Message* msg, const upb_MessageDef* msgdef, |
| const google::protobuf::Descriptor* desc, |
| google::protobuf::MessageFactory* factory) { |
| upb::Arena arena; |
| EXPECT_TRUE(desc != nullptr); |
| std::unique_ptr<google::protobuf::Message> google_msg( |
| factory->GetPrototype(desc)->New()); |
| char* buf; |
| size_t size; |
| upb_EncodeStatus status = upb_Encode(msg, upb_MessageDef_MiniTable(msgdef), 0, |
| arena.ptr(), &buf, &size); |
| EXPECT_EQ(status, kUpb_EncodeStatus_Ok); |
| google_msg->ParseFromArray(buf, size); |
| return google_msg; |
| } |
| |
| // A gtest matcher that verifies that a proto is equal to `proto`. Both `proto` |
| // and `arg` must be messages of type `msgdef_func` (a .upbdefs.h function that |
| // loads a known msgdef into the given defpool). |
| MATCHER_P2(EqualsUpbProto, proto, msgdef_func, |
| negation ? "are not equal" : "are equal") { |
| upb::DefPool defpool; |
| google::protobuf::DescriptorPool pool; |
| google::protobuf::DynamicMessageFactory factory; |
| upb::MessageDefPtr msgdef(msgdef_func(defpool.ptr())); |
| EXPECT_TRUE(msgdef.ptr() != nullptr); |
| const google::protobuf::Descriptor* desc = |
| AddMessageDescriptor(msgdef, &pool); |
| EXPECT_TRUE(desc != nullptr); |
| std::unique_ptr<google::protobuf::Message> m1( |
| ToProto(UPB_UPCAST(proto), msgdef.ptr(), desc, &factory)); |
| std::unique_ptr<google::protobuf::Message> m2( |
| ToProto((upb_Message*)arg, msgdef.ptr(), desc, &factory)); |
| std::string differences; |
| google::protobuf::util::MessageDifferencer differencer; |
| differencer.ReportDifferencesToString(&differences); |
| bool eq = differencer.Compare(*m2, *m1); |
| if (!eq) { |
| *result_listener << differences; |
| } |
| return eq; |
| } |
| |
| // Verifies that the given upb FileDef can be converted to a proto that matches |
| // `proto`. |
| void CheckFile(const upb::FileDefPtr file, |
| const google_protobuf_FileDescriptorProto* proto) { |
| upb::Arena arena; |
| google_protobuf_FileDescriptorProto* proto2 = |
| upb_FileDef_ToProto(file.ptr(), arena.ptr()); |
| ASSERT_THAT( |
| proto, |
| EqualsUpbProto(proto2, google_protobuf_FileDescriptorProto_getmsgdef)); |
| } |
| |
| // Verifies that upb/util/def_to_proto_test.proto can round-trip: |
| // serialized descriptor -> upb def -> serialized descriptor |
| TEST(DefToProto, Test) { |
| upb::Arena arena; |
| upb::DefPool defpool; |
| upb_StringView test_file_desc = |
| upb_util_def_to_proto_test_proto_upbdefinit.descriptor; |
| const auto* file_desc = google_protobuf_FileDescriptorProto_parse( |
| test_file_desc.data, test_file_desc.size, arena.ptr()); |
| |
| upb::MessageDefPtr msgdef(pkg_Message_getmsgdef(defpool.ptr())); |
| upb::FileDefPtr file = msgdef.file(); |
| CheckFile(file, file_desc); |
| } |
| |
| // Verifies that editions don't leak out legacy feature APIs (e.g. TYPE_GROUP |
| // and LABEL_REQUIRED): |
| // serialized descriptor -> upb def -> serialized descriptor |
| TEST(DefToProto, TestEditionsLegacyFeatures) { |
| upb::Arena arena; |
| upb::DefPool defpool; |
| upb_StringView test_file_desc = |
| upb_util_def_to_proto_editions_test_proto_upbdefinit |
| .descriptor; |
| const auto* file = google_protobuf_FileDescriptorProto_parse( |
| test_file_desc.data, test_file_desc.size, arena.ptr()); |
| |
| size_t size; |
| const auto* messages = google_protobuf_FileDescriptorProto_message_type(file, &size); |
| ASSERT_EQ(size, 1); |
| const auto* fields = google_protobuf_DescriptorProto_field(messages[0], &size); |
| ASSERT_EQ(size, 2); |
| EXPECT_EQ(google_protobuf_FieldDescriptorProto_label(fields[0]), |
| google_protobuf_FieldDescriptorProto_LABEL_OPTIONAL); |
| EXPECT_EQ(google_protobuf_FieldDescriptorProto_type(fields[1]), |
| google_protobuf_FieldDescriptorProto_TYPE_MESSAGE); |
| } |
| |
| // Like the previous test, but uses a message layout built at runtime. |
| TEST(DefToProto, TestRuntimeReflection) { |
| upb::Arena arena; |
| upb::DefPool defpool; |
| upb_StringView test_file_desc = |
| upb_util_def_to_proto_test_proto_upbdefinit.descriptor; |
| const auto* file_desc = google_protobuf_FileDescriptorProto_parse( |
| test_file_desc.data, test_file_desc.size, arena.ptr()); |
| |
| _upb_DefPool_LoadDefInitEx( |
| defpool.ptr(), |
| &upb_util_def_to_proto_test_proto_upbdefinit, true); |
| upb::FileDefPtr file = defpool.FindFileByName( |
| upb_util_def_to_proto_test_proto_upbdefinit.filename); |
| CheckFile(file, file_desc); |
| } |
| |
| // Fuzz test regressions. |
| |
| TEST(FuzzTest, EmptyPackage) { |
| RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { package: "" })pb")); |
| } |
| |
| TEST(FuzzTest, EmptyName) { |
| RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { name: "" })pb")); |
| } |
| |
| TEST(FuzzTest, EmptyPackage2) { |
| RoundTripDescriptor( |
| ParseTextProtoOrDie(R"pb(file { name: "n" package: "" })pb")); |
| } |
| |
| TEST(FuzzTest, FileNameEmbeddedNull) { |
| RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { name: "\000" })pb")); |
| } |
| |
| TEST(FuzzTest, DuplicateOneofIndex) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "F" |
| message_type { |
| name: "M" |
| oneof_decl { name: "O" } |
| field { name: "f1" number: 1 type: TYPE_INT32 oneof_index: 0 } |
| field { name: "f2" number: 1 type: TYPE_INT32 oneof_index: 0 } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, NanValue) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| enum_type { |
| value { |
| number: 0 |
| options { uninterpreted_option { double_value: nan } } |
| } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, EnumValueEmbeddedNull) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "\035" |
| enum_type { |
| name: "f" |
| value { name: "\000" number: 0 } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, EnumValueNoNumber) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "\035" |
| enum_type { |
| name: "f" |
| value { name: "abc" } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, DefaultWithUnterminatedHex) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "\035" |
| message_type { |
| name: "A" |
| field { |
| name: "f" |
| number: 1 |
| label: LABEL_OPTIONAL |
| type: TYPE_BYTES |
| default_value: "\\x" |
| } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, DefaultWithValidHexEscape) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "\035" |
| message_type { |
| name: "A" |
| field { |
| name: "f" |
| number: 1 |
| label: LABEL_OPTIONAL |
| type: TYPE_BYTES |
| default_value: "\\x03" |
| } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, DefaultWithValidHexEscapePrintable) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "\035" |
| message_type { |
| name: "A" |
| field { |
| name: "f" |
| number: 1 |
| label: LABEL_OPTIONAL |
| type: TYPE_BYTES |
| default_value: "\\x23" # 0x32 = '#' |
| } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, PackageStartsWithNumber) { |
| RoundTripDescriptor( |
| ParseTextProtoOrDie(R"pb(file { name: "" package: "0" })pb")); |
| } |
| |
| TEST(FuzzTest, RoundTripDescriptorRegression) { |
| RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { |
| name: "" |
| message_type { |
| name: "A" |
| field { |
| name: "B" |
| number: 1 |
| type: TYPE_BYTES |
| default_value: "\007" |
| } |
| } |
| })pb")); |
| } |
| |
| // Multiple oneof fields which have the same name. |
| TEST(FuzzTest, RoundTripDescriptorRegressionOneofSameName) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| name: "N" |
| package: "" |
| message_type { |
| name: "b" |
| field { name: "W" number: 1 type: TYPE_BYTES oneof_index: 0 } |
| field { name: "W" number: 17 type: TYPE_UINT32 oneof_index: 0 } |
| oneof_decl { name: "k" } |
| } |
| })pb")); |
| } |
| |
| TEST(FuzzTest, NegativeOneofIndex) { |
| RoundTripDescriptor(ParseTextProtoOrDie( |
| R"pb(file { |
| message_type { |
| name: "A" |
| field { name: "A" number: 0 type_name: "" oneof_index: -1 } |
| } |
| } |
| )pb")); |
| } |
| |
| } // namespace upb_test |