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