• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google LLC.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "upb/util/def_to_proto.h"
9 
10 #include <cstddef>
11 #include <memory>
12 #include <string>
13 
14 #include "google/protobuf/descriptor.pb.h"
15 #include "google/protobuf/descriptor.upb.h"
16 #include "google/protobuf/descriptor.upbdefs.h"
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19 #include "google/protobuf/dynamic_message.h"
20 #include "google/protobuf/util/message_differencer.h"
21 #include "upb/base/string_view.h"
22 #include "upb/mem/arena.hpp"
23 #include "upb/reflection/def.hpp"
24 #include "upb/test/parse_text_proto.h"
25 #include "upb/util/def_to_proto_editions_test.upbdefs.h"
26 #include "upb/util/def_to_proto_test.h"
27 #include "upb/util/def_to_proto_test.upbdefs.h"
28 
29 namespace upb_test {
30 
31 // Loads and retrieves a descriptor for `msgdef` into the given `pool`.
AddMessageDescriptor(upb::MessageDefPtr msgdef,google::protobuf::DescriptorPool * pool)32 const google::protobuf::Descriptor* AddMessageDescriptor(
33     upb::MessageDefPtr msgdef, google::protobuf::DescriptorPool* pool) {
34   upb::Arena tmp_arena;
35   upb::FileDefPtr file = msgdef.file();
36   google_protobuf_FileDescriptorProto* upb_proto =
37       upb_FileDef_ToProto(file.ptr(), tmp_arena.ptr());
38   size_t size;
39   const char* buf = google_protobuf_FileDescriptorProto_serialize(
40       upb_proto, tmp_arena.ptr(), &size);
41   google::protobuf::FileDescriptorProto google_proto;
42   google_proto.ParseFromArray(buf, size);
43   const google::protobuf::FileDescriptor* file_desc =
44       pool->BuildFile(google_proto);
45   EXPECT_TRUE(file_desc != nullptr);
46   return pool->FindMessageTypeByName(msgdef.full_name());
47 }
48 
49 // Converts a upb `msg` (with type `msgdef`) into a protobuf Message object from
50 // the given factory and descriptor.
ToProto(const upb_Message * msg,const upb_MessageDef * msgdef,const google::protobuf::Descriptor * desc,google::protobuf::MessageFactory * factory)51 std::unique_ptr<google::protobuf::Message> ToProto(
52     const upb_Message* msg, const upb_MessageDef* msgdef,
53     const google::protobuf::Descriptor* desc,
54     google::protobuf::MessageFactory* factory) {
55   upb::Arena arena;
56   EXPECT_TRUE(desc != nullptr);
57   std::unique_ptr<google::protobuf::Message> google_msg(
58       factory->GetPrototype(desc)->New());
59   char* buf;
60   size_t size;
61   upb_EncodeStatus status = upb_Encode(msg, upb_MessageDef_MiniTable(msgdef), 0,
62                                        arena.ptr(), &buf, &size);
63   EXPECT_EQ(status, kUpb_EncodeStatus_Ok);
64   google_msg->ParseFromArray(buf, size);
65   return google_msg;
66 }
67 
68 // A gtest matcher that verifies that a proto is equal to `proto`.  Both `proto`
69 // and `arg` must be messages of type `msgdef_func` (a .upbdefs.h function that
70 // loads a known msgdef into the given defpool).
71 MATCHER_P2(EqualsUpbProto, proto, msgdef_func,
72            negation ? "are not equal" : "are equal") {
73   upb::DefPool defpool;
74   google::protobuf::DescriptorPool pool;
75   google::protobuf::DynamicMessageFactory factory;
76   upb::MessageDefPtr msgdef(msgdef_func(defpool.ptr()));
77   EXPECT_TRUE(msgdef.ptr() != nullptr);
78   const google::protobuf::Descriptor* desc =
79       AddMessageDescriptor(msgdef, &pool);
80   EXPECT_TRUE(desc != nullptr);
81   std::unique_ptr<google::protobuf::Message> m1(
82       ToProto(UPB_UPCAST(proto), msgdef.ptr(), desc, &factory));
83   std::unique_ptr<google::protobuf::Message> m2(
84       ToProto((upb_Message*)arg, msgdef.ptr(), desc, &factory));
85   std::string differences;
86   google::protobuf::util::MessageDifferencer differencer;
87   differencer.ReportDifferencesToString(&differences);
88   bool eq = differencer.Compare(*m2, *m1);
89   if (!eq) {
90     *result_listener << differences;
91   }
92   return eq;
93 }
94 
95 // Verifies that the given upb FileDef can be converted to a proto that matches
96 // `proto`.
CheckFile(const upb::FileDefPtr file,const google_protobuf_FileDescriptorProto * proto)97 void CheckFile(const upb::FileDefPtr file,
98                const google_protobuf_FileDescriptorProto* proto) {
99   upb::Arena arena;
100   google_protobuf_FileDescriptorProto* proto2 =
101       upb_FileDef_ToProto(file.ptr(), arena.ptr());
102   ASSERT_THAT(
103       proto,
104       EqualsUpbProto(proto2, google_protobuf_FileDescriptorProto_getmsgdef));
105 }
106 
107 // Verifies that upb/util/def_to_proto_test.proto can round-trip:
108 //   serialized descriptor -> upb def -> serialized descriptor
TEST(DefToProto,Test)109 TEST(DefToProto, Test) {
110   upb::Arena arena;
111   upb::DefPool defpool;
112   upb_StringView test_file_desc =
113       upb_util_def_to_proto_test_proto_upbdefinit.descriptor;
114   const auto* file_desc = google_protobuf_FileDescriptorProto_parse(
115       test_file_desc.data, test_file_desc.size, arena.ptr());
116 
117   upb::MessageDefPtr msgdef(pkg_Message_getmsgdef(defpool.ptr()));
118   upb::FileDefPtr file = msgdef.file();
119   CheckFile(file, file_desc);
120 }
121 
122 // Verifies that editions don't leak out legacy feature APIs (e.g. TYPE_GROUP
123 // and LABEL_REQUIRED):
124 //   serialized descriptor -> upb def -> serialized descriptor
TEST(DefToProto,TestEditionsLegacyFeatures)125 TEST(DefToProto, TestEditionsLegacyFeatures) {
126   upb::Arena arena;
127   upb::DefPool defpool;
128   upb_StringView test_file_desc =
129       upb_util_def_to_proto_editions_test_proto_upbdefinit
130           .descriptor;
131   const auto* file = google_protobuf_FileDescriptorProto_parse(
132       test_file_desc.data, test_file_desc.size, arena.ptr());
133 
134   size_t size;
135   const auto* messages = google_protobuf_FileDescriptorProto_message_type(file, &size);
136   ASSERT_EQ(size, 1);
137   const auto* fields = google_protobuf_DescriptorProto_field(messages[0], &size);
138   ASSERT_EQ(size, 2);
139   EXPECT_EQ(google_protobuf_FieldDescriptorProto_label(fields[0]),
140             google_protobuf_FieldDescriptorProto_LABEL_OPTIONAL);
141   EXPECT_EQ(google_protobuf_FieldDescriptorProto_type(fields[1]),
142             google_protobuf_FieldDescriptorProto_TYPE_MESSAGE);
143 }
144 
145 // Like the previous test, but uses a message layout built at runtime.
TEST(DefToProto,TestRuntimeReflection)146 TEST(DefToProto, TestRuntimeReflection) {
147   upb::Arena arena;
148   upb::DefPool defpool;
149   upb_StringView test_file_desc =
150       upb_util_def_to_proto_test_proto_upbdefinit.descriptor;
151   const auto* file_desc = google_protobuf_FileDescriptorProto_parse(
152       test_file_desc.data, test_file_desc.size, arena.ptr());
153 
154   _upb_DefPool_LoadDefInitEx(
155       defpool.ptr(),
156       &upb_util_def_to_proto_test_proto_upbdefinit, true);
157   upb::FileDefPtr file = defpool.FindFileByName(
158       upb_util_def_to_proto_test_proto_upbdefinit.filename);
159   CheckFile(file, file_desc);
160 }
161 
162 // Fuzz test regressions.
163 
TEST(FuzzTest,EmptyPackage)164 TEST(FuzzTest, EmptyPackage) {
165   RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { package: "" })pb"));
166 }
167 
TEST(FuzzTest,EmptyName)168 TEST(FuzzTest, EmptyName) {
169   RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { name: "" })pb"));
170 }
171 
TEST(FuzzTest,EmptyPackage2)172 TEST(FuzzTest, EmptyPackage2) {
173   RoundTripDescriptor(
174       ParseTextProtoOrDie(R"pb(file { name: "n" package: "" })pb"));
175 }
176 
TEST(FuzzTest,FileNameEmbeddedNull)177 TEST(FuzzTest, FileNameEmbeddedNull) {
178   RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { name: "\000" })pb"));
179 }
180 
TEST(FuzzTest,DuplicateOneofIndex)181 TEST(FuzzTest, DuplicateOneofIndex) {
182   RoundTripDescriptor(ParseTextProtoOrDie(
183       R"pb(file {
184              name: "F"
185              message_type {
186                name: "M"
187                oneof_decl { name: "O" }
188                field { name: "f1" number: 1 type: TYPE_INT32 oneof_index: 0 }
189                field { name: "f2" number: 1 type: TYPE_INT32 oneof_index: 0 }
190              }
191            })pb"));
192 }
193 
TEST(FuzzTest,NanValue)194 TEST(FuzzTest, NanValue) {
195   RoundTripDescriptor(ParseTextProtoOrDie(
196       R"pb(file {
197              enum_type {
198                value {
199                  number: 0
200                  options { uninterpreted_option { double_value: nan } }
201                }
202              }
203            })pb"));
204 }
205 
TEST(FuzzTest,EnumValueEmbeddedNull)206 TEST(FuzzTest, EnumValueEmbeddedNull) {
207   RoundTripDescriptor(ParseTextProtoOrDie(
208       R"pb(file {
209              name: "\035"
210              enum_type {
211                name: "f"
212                value { name: "\000" number: 0 }
213              }
214            })pb"));
215 }
216 
TEST(FuzzTest,EnumValueNoNumber)217 TEST(FuzzTest, EnumValueNoNumber) {
218   RoundTripDescriptor(ParseTextProtoOrDie(
219       R"pb(file {
220              name: "\035"
221              enum_type {
222                name: "f"
223                value { name: "abc" }
224              }
225            })pb"));
226 }
227 
TEST(FuzzTest,DefaultWithUnterminatedHex)228 TEST(FuzzTest, DefaultWithUnterminatedHex) {
229   RoundTripDescriptor(ParseTextProtoOrDie(
230       R"pb(file {
231              name: "\035"
232              message_type {
233                name: "A"
234                field {
235                  name: "f"
236                  number: 1
237                  label: LABEL_OPTIONAL
238                  type: TYPE_BYTES
239                  default_value: "\\x"
240                }
241              }
242            })pb"));
243 }
244 
TEST(FuzzTest,DefaultWithValidHexEscape)245 TEST(FuzzTest, DefaultWithValidHexEscape) {
246   RoundTripDescriptor(ParseTextProtoOrDie(
247       R"pb(file {
248              name: "\035"
249              message_type {
250                name: "A"
251                field {
252                  name: "f"
253                  number: 1
254                  label: LABEL_OPTIONAL
255                  type: TYPE_BYTES
256                  default_value: "\\x03"
257                }
258              }
259            })pb"));
260 }
261 
TEST(FuzzTest,DefaultWithValidHexEscapePrintable)262 TEST(FuzzTest, DefaultWithValidHexEscapePrintable) {
263   RoundTripDescriptor(ParseTextProtoOrDie(
264       R"pb(file {
265              name: "\035"
266              message_type {
267                name: "A"
268                field {
269                  name: "f"
270                  number: 1
271                  label: LABEL_OPTIONAL
272                  type: TYPE_BYTES
273                  default_value: "\\x23"  # 0x32 = '#'
274                }
275              }
276            })pb"));
277 }
278 
TEST(FuzzTest,PackageStartsWithNumber)279 TEST(FuzzTest, PackageStartsWithNumber) {
280   RoundTripDescriptor(
281       ParseTextProtoOrDie(R"pb(file { name: "" package: "0" })pb"));
282 }
283 
TEST(FuzzTest,RoundTripDescriptorRegression)284 TEST(FuzzTest, RoundTripDescriptorRegression) {
285   RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file {
286                                                  name: ""
287                                                  message_type {
288                                                    name: "A"
289                                                    field {
290                                                      name: "B"
291                                                      number: 1
292                                                      type: TYPE_BYTES
293                                                      default_value: "\007"
294                                                    }
295                                                  }
296                                                })pb"));
297 }
298 
299 // Multiple oneof fields which have the same name.
TEST(FuzzTest,RoundTripDescriptorRegressionOneofSameName)300 TEST(FuzzTest, RoundTripDescriptorRegressionOneofSameName) {
301   RoundTripDescriptor(ParseTextProtoOrDie(
302       R"pb(file {
303              name: "N"
304              package: ""
305              message_type {
306                name: "b"
307                field { name: "W" number: 1 type: TYPE_BYTES oneof_index: 0 }
308                field { name: "W" number: 17 type: TYPE_UINT32 oneof_index: 0 }
309                oneof_decl { name: "k" }
310              }
311            })pb"));
312 }
313 
TEST(FuzzTest,NegativeOneofIndex)314 TEST(FuzzTest, NegativeOneofIndex) {
315   RoundTripDescriptor(ParseTextProtoOrDie(
316       R"pb(file {
317              message_type {
318                name: "A"
319                field { name: "A" number: 0 type_name: "" oneof_index: -1 }
320              }
321            }
322       )pb"));
323 }
324 
325 }  // namespace upb_test
326