1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google Inc. 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 "google/protobuf/compiler/retention.h"
9
10 #include <memory>
11 #include <string>
12
13 #include "google/protobuf/descriptor.pb.h"
14 #include <gmock/gmock.h>
15 #include <gtest/gtest.h>
16 #include "absl/log/absl_check.h"
17 #include "absl/log/die_if_null.h"
18 #include "absl/strings/substitute.h"
19 #include "google/protobuf/compiler/parser.h"
20 #include "google/protobuf/descriptor.h"
21 #include "google/protobuf/dynamic_message.h"
22 #include "google/protobuf/io/tokenizer.h"
23 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
24 #include "google/protobuf/text_format.h"
25
26
27 namespace google {
28 namespace protobuf {
29 namespace compiler {
30 namespace {
31
32 MATCHER_P(EqualsProto, msg, "") {
33 return msg.DebugString() == arg.DebugString();
34 }
35
36 class FakeErrorCollector : public io::ErrorCollector {
37 public:
38 FakeErrorCollector() = default;
39 ~FakeErrorCollector() override = default;
40
RecordError(int line,io::ColumnNumber column,absl::string_view message)41 void RecordError(int line, io::ColumnNumber column,
42 absl::string_view message) override {
43 ABSL_CHECK(false) << line << ":" << column << ": " << message;
44 }
45
RecordWarning(int line,io::ColumnNumber column,absl::string_view message)46 void RecordWarning(int line, io::ColumnNumber column,
47 absl::string_view message) override {
48 ABSL_CHECK(false) << line << ":" << column << ": " << message;
49 }
50 };
51 class RetentionStripTest : public testing::Test {
52 protected:
SetUp()53 void SetUp() override {
54 FileDescriptorProto descriptor_proto_descriptor;
55 FileDescriptorSet::descriptor()->file()->CopyTo(
56 &descriptor_proto_descriptor);
57 pool_.BuildFile(descriptor_proto_descriptor);
58 }
59
ParseSchema(absl::string_view contents,absl::string_view file_name="foo.proto")60 const FileDescriptor* ParseSchema(absl::string_view contents,
61 absl::string_view file_name = "foo.proto") {
62 std::string proto_file = absl::Substitute(
63 R"schema(
64 syntax = "proto2";
65
66 package google.protobuf.internal;
67
68 import "$0";
69
70 $1
71 )schema",
72 FileDescriptorSet::descriptor()->file()->name(), contents);
73 io::ArrayInputStream input_stream(proto_file.data(),
74 static_cast<int>(proto_file.size()));
75 FakeErrorCollector error_collector;
76 io::Tokenizer tokenizer(&input_stream, &error_collector);
77 Parser parser;
78 parser.RecordErrorsTo(&error_collector);
79 FileDescriptorProto file_descriptor;
80 ABSL_CHECK(parser.Parse(&tokenizer, &file_descriptor));
81 file_descriptor.set_name(file_name);
82
83 return pool_.BuildFile(file_descriptor);
84 }
85
86 template <typename ProtoType>
BuildDynamicProto(absl::string_view data)87 ProtoType BuildDynamicProto(absl::string_view data) {
88 // We use a dynamic message to generate the expected options proto. This
89 // lets us parse the custom options in text format.
90 const Descriptor* file_options_descriptor =
91 pool_.FindMessageTypeByName(ProtoType().GetTypeName());
92 DynamicMessageFactory factory;
93 std::unique_ptr<Message> dynamic_message(
94 factory.GetPrototype(file_options_descriptor)->New());
95 ABSL_CHECK(TextFormat::ParseFromString(data, dynamic_message.get()));
96 ProtoType ret;
97 ABSL_CHECK(ret.ParseFromString(dynamic_message->SerializeAsString()));
98 return ret;
99 }
100
101 DescriptorPool pool_;
102 };
103
TEST_F(RetentionStripTest,StripSourceRetentionFileOptions)104 TEST_F(RetentionStripTest, StripSourceRetentionFileOptions) {
105 const FileDescriptor* file = ParseSchema(R"schema(
106 option (source_retention_option) = 123;
107 option (options) = {
108 i1: 123
109 i2: 456
110 c { s: "abc" }
111 rc { s: "abc" }
112 };
113 option (repeated_options) = {
114 i1: 111 i2: 222
115 };
116
117 message Options {
118 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
119 optional int32 i2 = 2;
120 message ChildMessage {
121 optional string s = 1 [retention = RETENTION_SOURCE];
122 }
123 optional ChildMessage c = 3;
124 repeated ChildMessage rc = 4;
125 }
126
127 extend google.protobuf.FileOptions {
128 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
129 optional Options options = 50001;
130 repeated Options repeated_options = 50002;
131 })schema");
132
133 FileOptions expected_options = BuildDynamicProto<FileOptions>(
134 R"pb(
135 [google.protobuf.internal.options] {
136 i2: 456
137 c {}
138 rc {}
139 }
140 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
141
142 FileDescriptorProto stripped_file = StripSourceRetentionOptions(*file);
143
144 EXPECT_THAT(StripSourceRetentionOptions(*file).options(),
145 EqualsProto(expected_options));
146 EXPECT_THAT(StripLocalSourceRetentionOptions(*file),
147 EqualsProto(expected_options));
148 }
149
TEST_F(RetentionStripTest,StripSourceRetentionProtoFileOptions)150 TEST_F(RetentionStripTest, StripSourceRetentionProtoFileOptions) {
151 const FileDescriptor* file = ParseSchema(R"schema(
152 option (source_retention_option) = 123;
153 option (options) = {
154 i1: 123
155 i2: 456
156 c { s: "abc" }
157 rc { s: "abc" }
158 };
159 option (repeated_options) = {
160 i1: 111 i2: 222
161 };
162
163 message Options {
164 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
165 optional int32 i2 = 2;
166 message ChildMessage {
167 optional string s = 1 [retention = RETENTION_SOURCE];
168 }
169 optional ChildMessage c = 3;
170 repeated ChildMessage rc = 4;
171 }
172
173 extend google.protobuf.FileOptions {
174 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
175 optional Options options = 50001;
176 repeated Options repeated_options = 50002;
177 }
178 )schema");
179
180 FileDescriptorProto proto;
181 file->CopyTo(&proto);
182
183 ASSERT_THAT(proto.options(), EqualsProto(BuildDynamicProto<FileOptions>(R"pb(
184 [google.protobuf.internal.source_retention_option]: 123
185 [google.protobuf.internal.options] {
186 i1: 123
187 i2: 456
188 c { s: "abc" }
189 rc { s: "abc" }
190 }
191 [google.protobuf.internal.repeated_options] { i1: 111 i2: 222 })pb")));
192
193 StripSourceRetentionOptions(*file->pool(), proto);
194
195 EXPECT_THAT(proto.options(), EqualsProto(BuildDynamicProto<FileOptions>(R"pb(
196 [google.protobuf.internal.options] {
197 i2: 456
198 c {}
199 rc {}
200 }
201 [google.protobuf.internal.repeated_options] { i2: 222 })pb")));
202 }
203
TEST_F(RetentionStripTest,StripSourceRetentionMessageOptions)204 TEST_F(RetentionStripTest, StripSourceRetentionMessageOptions) {
205 const FileDescriptor* file = ParseSchema(R"schema(
206 message TestMessage {
207 option (source_retention_option) = 123;
208 option (options) = {
209 i1: 123
210 i2: 456
211 c { s: "abc" }
212 rc { s: "abc" }
213 };
214 option (repeated_options) = {
215 i1: 111 i2: 222
216 };
217 }
218
219 message Options {
220 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
221 optional int32 i2 = 2;
222 message ChildMessage {
223 optional string s = 1 [retention = RETENTION_SOURCE];
224 }
225 optional ChildMessage c = 3;
226 repeated ChildMessage rc = 4;
227 }
228
229 extend google.protobuf.MessageOptions {
230 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
231 optional Options options = 50001;
232 repeated Options repeated_options = 50002;
233 })schema");
234
235 MessageOptions expected_options = BuildDynamicProto<MessageOptions>(
236 R"pb(
237 [google.protobuf.internal.options] {
238 i2: 456
239 c {}
240 rc {}
241 }
242 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
243 const Descriptor* message =
244 ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
245
246 EXPECT_THAT(StripSourceRetentionOptions(*file).message_type(0).options(),
247 EqualsProto(expected_options));
248 EXPECT_THAT(StripSourceRetentionOptions(*message).options(),
249 EqualsProto(expected_options));
250 EXPECT_THAT(StripLocalSourceRetentionOptions(*message),
251 EqualsProto(expected_options));
252 }
253
TEST_F(RetentionStripTest,StripSourceRetentionEnumOptions)254 TEST_F(RetentionStripTest, StripSourceRetentionEnumOptions) {
255 const FileDescriptor* file = ParseSchema(R"schema(
256 enum TestEnum {
257 option (source_retention_option) = 123;
258 option (options) = {
259 i1: 123
260 i2: 456
261 c { s: "abc" }
262 rc { s: "abc" }
263 };
264 option (repeated_options) = {
265 i1: 111 i2: 222
266 };
267 VALUE1 = 0;
268 }
269
270 message Options {
271 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
272 optional int32 i2 = 2;
273 message ChildMessage {
274 optional string s = 1 [retention = RETENTION_SOURCE];
275 }
276 optional ChildMessage c = 3;
277 repeated ChildMessage rc = 4;
278 }
279
280 extend google.protobuf.EnumOptions {
281 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
282 optional Options options = 50001;
283 repeated Options repeated_options = 50002;
284 })schema");
285
286 EnumOptions expected_options = BuildDynamicProto<EnumOptions>(
287 R"pb(
288 [google.protobuf.internal.options] {
289 i2: 456
290 c {}
291 rc {}
292 }
293 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
294 const EnumDescriptor* enm =
295 ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum"));
296
297 EXPECT_THAT(StripSourceRetentionOptions(*file).enum_type(0).options(),
298 EqualsProto(expected_options));
299 EXPECT_THAT(StripSourceRetentionOptions(*enm).options(),
300 EqualsProto(expected_options));
301 EXPECT_THAT(StripLocalSourceRetentionOptions(*enm),
302 EqualsProto(expected_options));
303 }
304
TEST_F(RetentionStripTest,StripSourceRetentionEnumValueOptions)305 TEST_F(RetentionStripTest, StripSourceRetentionEnumValueOptions) {
306 const FileDescriptor* file = ParseSchema(R"schema(
307 enum TestEnum {
308 VALUE1 = 0 [(source_retention_option) = 123, (options) = {
309 i1: 123
310 i2: 456
311 c { s: "abc" }
312 rc { s: "abc" }
313 }, (repeated_options) = {
314 i1: 111 i2: 222
315 }];
316 }
317
318 message Options {
319 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
320 optional int32 i2 = 2;
321 message ChildMessage {
322 optional string s = 1 [retention = RETENTION_SOURCE];
323 }
324 optional ChildMessage c = 3;
325 repeated ChildMessage rc = 4;
326 }
327
328 extend google.protobuf.EnumValueOptions {
329 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
330 optional Options options = 50001;
331 repeated Options repeated_options = 50002;
332 })schema");
333
334 EnumValueOptions expected_options = BuildDynamicProto<EnumValueOptions>(
335 R"pb(
336 [google.protobuf.internal.options] {
337 i2: 456
338 c {}
339 rc {}
340 }
341 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
342 const EnumDescriptor* enm =
343 ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum"));
344 const EnumValueDescriptor* value = ABSL_DIE_IF_NULL(enm->value(0));
345
346 EXPECT_THAT(
347 StripSourceRetentionOptions(*file).enum_type(0).value(0).options(),
348 EqualsProto(expected_options));
349 EXPECT_THAT(StripSourceRetentionOptions(*enm).value(0).options(),
350 EqualsProto(expected_options));
351 EXPECT_THAT(StripLocalSourceRetentionOptions(*value),
352 EqualsProto(expected_options));
353 }
354
TEST_F(RetentionStripTest,StripSourceRetentionFieldOptions)355 TEST_F(RetentionStripTest, StripSourceRetentionFieldOptions) {
356 const FileDescriptor* file = ParseSchema(R"schema(
357 message TestMessage {
358 optional string test_field = 1 [(source_retention_option) = 123, (options) = {
359 i1: 123
360 i2: 456
361 c { s: "abc" }
362 rc { s: "abc" }
363 }, (repeated_options) = {
364 i1: 111 i2: 222
365 }];
366 }
367
368 message Options {
369 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
370 optional int32 i2 = 2;
371 message ChildMessage {
372 optional string s = 1 [retention = RETENTION_SOURCE];
373 }
374 optional ChildMessage c = 3;
375 repeated ChildMessage rc = 4;
376 }
377
378 extend google.protobuf.FieldOptions {
379 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
380 optional Options options = 50001;
381 repeated Options repeated_options = 50002;
382 })schema");
383
384 FieldOptions expected_options = BuildDynamicProto<FieldOptions>(
385 R"pb(
386 [google.protobuf.internal.options] {
387 i2: 456
388 c {}
389 rc {}
390 }
391 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
392 const Descriptor* message =
393 ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
394 const FieldDescriptor* field =
395 ABSL_DIE_IF_NULL(message->FindFieldByName("test_field"));
396
397 EXPECT_THAT(
398 StripSourceRetentionOptions(*file).message_type(0).field(0).options(),
399 EqualsProto(expected_options));
400 EXPECT_THAT(StripSourceRetentionOptions(*message).field(0).options(),
401 EqualsProto(expected_options));
402 EXPECT_THAT(StripSourceRetentionOptions(*field).options(),
403 EqualsProto(expected_options));
404 EXPECT_THAT(StripLocalSourceRetentionOptions(*field),
405 EqualsProto(expected_options));
406 }
407
TEST_F(RetentionStripTest,StripSourceRetentionExtensionOptions)408 TEST_F(RetentionStripTest, StripSourceRetentionExtensionOptions) {
409 const FileDescriptor* file = ParseSchema(R"schema(
410 message TestMessage {
411 extensions 1;
412 }
413
414 extend TestMessage {
415 optional string test_field = 1 [(source_retention_option) = 123, (options) = {
416 i1: 123
417 i2: 456
418 c { s: "abc" }
419 rc { s: "abc" }
420 }, (repeated_options) = {
421 i1: 111 i2: 222
422 }];
423 }
424
425 message Options {
426 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
427 optional int32 i2 = 2;
428 message ChildMessage {
429 optional string s = 1 [retention = RETENTION_SOURCE];
430 }
431 optional ChildMessage c = 3;
432 repeated ChildMessage rc = 4;
433 }
434
435 extend google.protobuf.FieldOptions {
436 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
437 optional Options options = 50001;
438 repeated Options repeated_options = 50002;
439 })schema");
440
441 FieldOptions expected_options = BuildDynamicProto<FieldOptions>(
442 R"pb(
443 [google.protobuf.internal.options] {
444 i2: 456
445 c {}
446 rc {}
447 }
448 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
449 const FieldDescriptor* field =
450 ABSL_DIE_IF_NULL(file->FindExtensionByName("test_field"));
451
452 EXPECT_THAT(StripSourceRetentionOptions(*file).extension(0).options(),
453 EqualsProto(expected_options));
454 EXPECT_THAT(StripSourceRetentionOptions(*field).options(),
455 EqualsProto(expected_options));
456 EXPECT_THAT(StripLocalSourceRetentionOptions(*field),
457 EqualsProto(expected_options));
458 }
459
TEST_F(RetentionStripTest,StripSourceRetentionOneofOptions)460 TEST_F(RetentionStripTest, StripSourceRetentionOneofOptions) {
461 const FileDescriptor* file = ParseSchema(R"schema(
462 message TestMessage {
463 oneof test_oneof {
464 option (source_retention_option) = 123;
465 option (options) = {
466 i1: 123
467 i2: 456
468 c { s: "abc" }
469 rc { s: "abc" }
470 };
471 option (repeated_options) = {
472 i1: 111 i2: 222
473 };
474 string field1 = 1;
475 string field2 = 2;
476 };
477 }
478
479 message Options {
480 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
481 optional int32 i2 = 2;
482 message ChildMessage {
483 optional string s = 1 [retention = RETENTION_SOURCE];
484 }
485 optional ChildMessage c = 3;
486 repeated ChildMessage rc = 4;
487 }
488
489 extend google.protobuf.OneofOptions {
490 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
491 optional Options options = 50001;
492 repeated Options repeated_options = 50002;
493 })schema");
494
495 OneofOptions expected_options = BuildDynamicProto<OneofOptions>(
496 R"pb(
497 [google.protobuf.internal.options] {
498 i2: 456
499 c {}
500 rc {}
501 }
502 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
503 const Descriptor* message =
504 ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
505 const OneofDescriptor* oneof =
506 ABSL_DIE_IF_NULL(message->FindOneofByName("test_oneof"));
507
508 EXPECT_THAT(StripSourceRetentionOptions(*file)
509 .message_type(0)
510 .oneof_decl(0)
511 .options(),
512 EqualsProto(expected_options));
513 EXPECT_THAT(StripSourceRetentionOptions(*message).oneof_decl(0).options(),
514 EqualsProto(expected_options));
515 EXPECT_THAT(StripSourceRetentionOptions(*oneof).options(),
516 EqualsProto(expected_options));
517 EXPECT_THAT(StripLocalSourceRetentionOptions(*oneof),
518 EqualsProto(expected_options));
519 }
520
TEST_F(RetentionStripTest,StripSourceRetentionExtensionRangeOptions)521 TEST_F(RetentionStripTest, StripSourceRetentionExtensionRangeOptions) {
522 const FileDescriptor* file = ParseSchema(R"schema(
523 message TestMessage {
524 extensions 1 to max [(source_retention_option) = 123, (options) = {
525 i1: 123
526 i2: 456
527 c { s: "abc" }
528 rc { s: "abc" }
529 }, (repeated_options) = {
530 i1: 111 i2: 222
531 }];
532 }
533
534 message Options {
535 optional int32 i1 = 1 [retention = RETENTION_SOURCE];
536 optional int32 i2 = 2;
537 message ChildMessage {
538 optional string s = 1 [retention = RETENTION_SOURCE];
539 }
540 optional ChildMessage c = 3;
541 repeated ChildMessage rc = 4;
542 }
543
544 extend google.protobuf.ExtensionRangeOptions {
545 optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
546 optional Options options = 50001;
547 repeated Options repeated_options = 50002;
548 })schema");
549
550 ExtensionRangeOptions expected_options =
551 BuildDynamicProto<ExtensionRangeOptions>(
552 R"pb(
553 [google.protobuf.internal.options] {
554 i2: 456
555 c {}
556 rc {}
557 }
558 [google.protobuf.internal.repeated_options] { i2: 222 })pb");
559 const Descriptor* message =
560 ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
561 const Descriptor::ExtensionRange* range =
562 ABSL_DIE_IF_NULL(message->FindExtensionRangeContainingNumber(2));
563
564 EXPECT_THAT(StripSourceRetentionOptions(*file)
565 .message_type(0)
566 .extension_range(0)
567 .options(),
568 EqualsProto(expected_options));
569 EXPECT_THAT(
570 StripSourceRetentionOptions(*message).extension_range(0).options(),
571 EqualsProto(expected_options));
572 EXPECT_THAT(StripSourceRetentionOptions(*message, *range).options(),
573 EqualsProto(expected_options));
574 EXPECT_THAT(StripLocalSourceRetentionOptions(*message, *range),
575 EqualsProto(expected_options));
576 }
577
578
579 } // namespace
580 } // namespace compiler
581 } // namespace protobuf
582 } // namespace google
583