1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 #include <google/protobuf/compiler/objectivec/objectivec_file.h>
32 #include <google/protobuf/compiler/objectivec/objectivec_enum.h>
33 #include <google/protobuf/compiler/objectivec/objectivec_extension.h>
34 #include <google/protobuf/compiler/objectivec/objectivec_message.h>
35 #include <google/protobuf/compiler/code_generator.h>
36 #include <google/protobuf/io/printer.h>
37 #include <google/protobuf/io/zero_copy_stream_impl.h>
38 #include <google/protobuf/stubs/stl_util.h>
39 #include <google/protobuf/stubs/strutil.h>
40 #include <algorithm> // std::find()
41 #include <iostream>
42 #include <sstream>
43
44 // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
45 // error cases, so it seems to be ok to use as a back door for errors.
46
47 namespace google {
48 namespace protobuf {
49 namespace compiler {
50 namespace objectivec {
51
52 namespace {
53
54 // This is also found in GPBBootstrap.h, and needs to be kept in sync.
55 const int32 GOOGLE_PROTOBUF_OBJC_VERSION = 30004;
56
57 const char* kHeaderExtension = ".pbobjc.h";
58
59 // Checks if a message contains any enums definitions (on the message or
60 // a nested message under it).
MessageContainsEnums(const Descriptor * message)61 bool MessageContainsEnums(const Descriptor* message) {
62 if (message->enum_type_count() > 0) {
63 return true;
64 }
65 for (int i = 0; i < message->nested_type_count(); i++) {
66 if (MessageContainsEnums(message->nested_type(i))) {
67 return true;
68 }
69 }
70 return false;
71 }
72
73 // Checks if a message contains any extension definitions (on the message or
74 // a nested message under it).
MessageContainsExtensions(const Descriptor * message)75 bool MessageContainsExtensions(const Descriptor* message) {
76 if (message->extension_count() > 0) {
77 return true;
78 }
79 for (int i = 0; i < message->nested_type_count(); i++) {
80 if (MessageContainsExtensions(message->nested_type(i))) {
81 return true;
82 }
83 }
84 return false;
85 }
86
87 // Checks if the file contains any enum definitions (at the root or
88 // nested under a message).
FileContainsEnums(const FileDescriptor * file)89 bool FileContainsEnums(const FileDescriptor* file) {
90 if (file->enum_type_count() > 0) {
91 return true;
92 }
93 for (int i = 0; i < file->message_type_count(); i++) {
94 if (MessageContainsEnums(file->message_type(i))) {
95 return true;
96 }
97 }
98 return false;
99 }
100
101 // Checks if the file contains any extensions definitions (at the root or
102 // nested under a message).
FileContainsExtensions(const FileDescriptor * file)103 bool FileContainsExtensions(const FileDescriptor* file) {
104 if (file->extension_count() > 0) {
105 return true;
106 }
107 for (int i = 0; i < file->message_type_count(); i++) {
108 if (MessageContainsExtensions(file->message_type(i))) {
109 return true;
110 }
111 }
112 return false;
113 }
114
115 // Helper for CollectMinimalFileDepsContainingExtensionsWorker that marks all
116 // deps as visited and prunes them from the needed files list.
PruneFileAndDepsMarkingAsVisited(const FileDescriptor * file,std::vector<const FileDescriptor * > * files,std::set<const FileDescriptor * > * files_visited)117 void PruneFileAndDepsMarkingAsVisited(
118 const FileDescriptor* file,
119 std::vector<const FileDescriptor*>* files,
120 std::set<const FileDescriptor*>* files_visited) {
121 std::vector<const FileDescriptor*>::iterator iter =
122 std::find(files->begin(), files->end(), file);
123 if (iter != files->end()) {
124 files->erase(iter);
125 }
126 files_visited->insert(file);
127 for (int i = 0; i < file->dependency_count(); i++) {
128 PruneFileAndDepsMarkingAsVisited(file->dependency(i), files, files_visited);
129 }
130 }
131
132 // Helper for CollectMinimalFileDepsContainingExtensions.
CollectMinimalFileDepsContainingExtensionsWorker(const FileDescriptor * file,std::vector<const FileDescriptor * > * files,std::set<const FileDescriptor * > * files_visited)133 void CollectMinimalFileDepsContainingExtensionsWorker(
134 const FileDescriptor* file,
135 std::vector<const FileDescriptor*>* files,
136 std::set<const FileDescriptor*>* files_visited) {
137 if (files_visited->find(file) != files_visited->end()) {
138 return;
139 }
140 files_visited->insert(file);
141
142 if (FileContainsExtensions(file)) {
143 files->push_back(file);
144 for (int i = 0; i < file->dependency_count(); i++) {
145 const FileDescriptor* dep = file->dependency(i);
146 PruneFileAndDepsMarkingAsVisited(dep, files, files_visited);
147 }
148 } else {
149 for (int i = 0; i < file->dependency_count(); i++) {
150 const FileDescriptor* dep = file->dependency(i);
151 CollectMinimalFileDepsContainingExtensionsWorker(dep, files,
152 files_visited);
153 }
154 }
155 }
156
157 // Collect the deps of the given file that contain extensions. This can be used to
158 // create the chain of roots that need to be wired together.
159 //
160 // NOTE: If any changes are made to this and the supporting functions, you will
161 // need to manually validate what the generated code is for the test files:
162 // objectivec/Tests/unittest_extension_chain_*.proto
163 // There are comments about what the expected code should be line and limited
164 // testing objectivec/Tests/GPBUnittestProtos2.m around compilation (#imports
165 // specifically).
CollectMinimalFileDepsContainingExtensions(const FileDescriptor * file,std::vector<const FileDescriptor * > * files)166 void CollectMinimalFileDepsContainingExtensions(
167 const FileDescriptor* file,
168 std::vector<const FileDescriptor*>* files) {
169 std::set<const FileDescriptor*> files_visited;
170 for (int i = 0; i < file->dependency_count(); i++) {
171 const FileDescriptor* dep = file->dependency(i);
172 CollectMinimalFileDepsContainingExtensionsWorker(dep, files,
173 &files_visited);
174 }
175 }
176
IsDirectDependency(const FileDescriptor * dep,const FileDescriptor * file)177 bool IsDirectDependency(const FileDescriptor* dep, const FileDescriptor* file) {
178 for (int i = 0; i < file->dependency_count(); i++) {
179 if (dep == file->dependency(i)) {
180 return true;
181 }
182 }
183 return false;
184 }
185
186 } // namespace
187
FileGenerator(const FileDescriptor * file,const Options & options)188 FileGenerator::FileGenerator(const FileDescriptor *file, const Options& options)
189 : file_(file),
190 root_class_name_(FileClassName(file)),
191 is_bundled_proto_(IsProtobufLibraryBundledProtoFile(file)),
192 options_(options) {
193 for (int i = 0; i < file_->enum_type_count(); i++) {
194 EnumGenerator *generator = new EnumGenerator(file_->enum_type(i));
195 enum_generators_.emplace_back(generator);
196 }
197 for (int i = 0; i < file_->message_type_count(); i++) {
198 MessageGenerator *generator =
199 new MessageGenerator(root_class_name_, file_->message_type(i), options_);
200 message_generators_.emplace_back(generator);
201 }
202 for (int i = 0; i < file_->extension_count(); i++) {
203 ExtensionGenerator *generator =
204 new ExtensionGenerator(root_class_name_, file_->extension(i));
205 extension_generators_.emplace_back(generator);
206 }
207 }
208
~FileGenerator()209 FileGenerator::~FileGenerator() {}
210
GenerateHeader(io::Printer * printer)211 void FileGenerator::GenerateHeader(io::Printer *printer) {
212 std::vector<string> headers;
213 // Generated files bundled with the library get minimal imports, everything
214 // else gets the wrapper so everything is usable.
215 if (is_bundled_proto_) {
216 headers.push_back("GPBDescriptor.h");
217 headers.push_back("GPBMessage.h");
218 headers.push_back("GPBRootObject.h");
219 } else {
220 headers.push_back("GPBProtocolBuffers.h");
221 }
222 PrintFileRuntimePreamble(printer, headers);
223
224 // Add some verification that the generated code matches the source the
225 // code is being compiled with.
226 // NOTE: This captures the raw numeric values at the time the generator was
227 // compiled, since that will be the versions for the ObjC runtime at that
228 // time. The constants in the generated code will then get their values at
229 // at compile time (so checking against the headers being used to compile).
230 printer->Print(
231 "#if GOOGLE_PROTOBUF_OBJC_VERSION < $google_protobuf_objc_version$\n"
232 "#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.\n"
233 "#endif\n"
234 "#if $google_protobuf_objc_version$ < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION\n"
235 "#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.\n"
236 "#endif\n"
237 "\n",
238 "google_protobuf_objc_version", StrCat(GOOGLE_PROTOBUF_OBJC_VERSION));
239
240 // #import any headers for "public imports" in the proto file.
241 {
242 ImportWriter import_writer(
243 options_.generate_for_named_framework,
244 options_.named_framework_to_proto_path_mappings_path,
245 options_.runtime_import_prefix,
246 is_bundled_proto_);
247 const string header_extension(kHeaderExtension);
248 for (int i = 0; i < file_->public_dependency_count(); i++) {
249 import_writer.AddFile(file_->public_dependency(i), header_extension);
250 }
251 import_writer.Print(printer);
252 }
253
254 // Note:
255 // deprecated-declarations suppression is only needed if some place in this
256 // proto file is something deprecated or if it references something from
257 // another file that is deprecated.
258 printer->Print(
259 "// @@protoc_insertion_point(imports)\n"
260 "\n"
261 "#pragma clang diagnostic push\n"
262 "#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n"
263 "\n"
264 "CF_EXTERN_C_BEGIN\n"
265 "\n");
266
267 std::set<string> fwd_decls;
268 for (const auto& generator : message_generators_) {
269 generator->DetermineForwardDeclarations(&fwd_decls);
270 }
271 for (std::set<string>::const_iterator i(fwd_decls.begin());
272 i != fwd_decls.end(); ++i) {
273 printer->Print("$value$;\n", "value", *i);
274 }
275 if (fwd_decls.begin() != fwd_decls.end()) {
276 printer->Print("\n");
277 }
278
279 printer->Print(
280 "NS_ASSUME_NONNULL_BEGIN\n"
281 "\n");
282
283 // need to write out all enums first
284 for (const auto& generator : enum_generators_) {
285 generator->GenerateHeader(printer);
286 }
287
288 for (const auto& generator : message_generators_) {
289 generator->GenerateEnumHeader(printer);
290 }
291
292 // For extensions to chain together, the Root gets created even if there
293 // are no extensions.
294 printer->Print(
295 "#pragma mark - $root_class_name$\n"
296 "\n"
297 "/**\n"
298 " * Exposes the extension registry for this file.\n"
299 " *\n"
300 " * The base class provides:\n"
301 " * @code\n"
302 " * + (GPBExtensionRegistry *)extensionRegistry;\n"
303 " * @endcode\n"
304 " * which is a @c GPBExtensionRegistry that includes all the extensions defined by\n"
305 " * this file and all files that it depends on.\n"
306 " **/\n"
307 "GPB_FINAL @interface $root_class_name$ : GPBRootObject\n"
308 "@end\n"
309 "\n",
310 "root_class_name", root_class_name_);
311
312 if (!extension_generators_.empty()) {
313 // The dynamic methods block is only needed if there are extensions.
314 printer->Print(
315 "@interface $root_class_name$ (DynamicMethods)\n",
316 "root_class_name", root_class_name_);
317
318 for (const auto& generator : extension_generators_) {
319 generator->GenerateMembersHeader(printer);
320 }
321
322 printer->Print("@end\n\n");
323 } // !extension_generators_.empty()
324
325 for (const auto& generator : message_generators_) {
326 generator->GenerateMessageHeader(printer);
327 }
328
329 printer->Print(
330 "NS_ASSUME_NONNULL_END\n"
331 "\n"
332 "CF_EXTERN_C_END\n"
333 "\n"
334 "#pragma clang diagnostic pop\n"
335 "\n"
336 "// @@protoc_insertion_point(global_scope)\n");
337 }
338
GenerateSource(io::Printer * printer)339 void FileGenerator::GenerateSource(io::Printer *printer) {
340 // #import the runtime support.
341 std::vector<string> headers;
342 headers.push_back("GPBProtocolBuffers_RuntimeSupport.h");
343 PrintFileRuntimePreamble(printer, headers);
344
345 // Enums use atomic in the generated code, so add the system import as needed.
346 if (FileContainsEnums(file_)) {
347 printer->Print(
348 "#import <stdatomic.h>\n"
349 "\n");
350 }
351
352 std::vector<const FileDescriptor*> deps_with_extensions;
353 CollectMinimalFileDepsContainingExtensions(file_, &deps_with_extensions);
354
355 {
356 ImportWriter import_writer(
357 options_.generate_for_named_framework,
358 options_.named_framework_to_proto_path_mappings_path,
359 options_.runtime_import_prefix,
360 is_bundled_proto_);
361 const string header_extension(kHeaderExtension);
362
363 // #import the header for this proto file.
364 import_writer.AddFile(file_, header_extension);
365
366 // #import the headers for anything that a plain dependency of this proto
367 // file (that means they were just an include, not a "public" include).
368 std::set<string> public_import_names;
369 for (int i = 0; i < file_->public_dependency_count(); i++) {
370 public_import_names.insert(file_->public_dependency(i)->name());
371 }
372 for (int i = 0; i < file_->dependency_count(); i++) {
373 const FileDescriptor *dep = file_->dependency(i);
374 bool public_import = (public_import_names.count(dep->name()) != 0);
375 if (!public_import) {
376 import_writer.AddFile(dep, header_extension);
377 }
378 }
379
380 // If any indirect dependency provided extensions, it needs to be directly
381 // imported so it can get merged into the root's extensions registry.
382 // See the Note by CollectMinimalFileDepsContainingExtensions before
383 // changing this.
384 for (std::vector<const FileDescriptor *>::iterator iter =
385 deps_with_extensions.begin();
386 iter != deps_with_extensions.end(); ++iter) {
387 if (!IsDirectDependency(*iter, file_)) {
388 import_writer.AddFile(*iter, header_extension);
389 }
390 }
391
392 import_writer.Print(printer);
393 }
394
395 bool includes_oneof = false;
396 for (const auto& generator : message_generators_) {
397 if (generator->IncludesOneOfDefinition()) {
398 includes_oneof = true;
399 break;
400 }
401 }
402
403 std::set<string> fwd_decls;
404 for (const auto& generator : message_generators_) {
405 generator->DetermineObjectiveCClassDefinitions(&fwd_decls);
406 }
407 for (const auto& generator : extension_generators_) {
408 generator->DetermineObjectiveCClassDefinitions(&fwd_decls);
409 }
410
411 // Note:
412 // deprecated-declarations suppression is only needed if some place in this
413 // proto file is something deprecated or if it references something from
414 // another file that is deprecated.
415 // dollar-in-identifier-extension is needed because we use references to
416 // objc class names that have $ in identifiers.
417 printer->Print(
418 "// @@protoc_insertion_point(imports)\n"
419 "\n"
420 "#pragma clang diagnostic push\n"
421 "#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n");
422 if (includes_oneof) {
423 // The generated code for oneof's uses direct ivar access, suppress the
424 // warning incase developer turn that on in the context they compile the
425 // generated code.
426 printer->Print(
427 "#pragma clang diagnostic ignored \"-Wdirect-ivar-access\"\n");
428 }
429 if (!fwd_decls.empty()) {
430 printer->Print(
431 "#pragma clang diagnostic ignored \"-Wdollar-in-identifier-extension\"\n");
432 }
433 printer->Print(
434 "\n");
435 if (!fwd_decls.empty()) {
436 printer->Print(
437 "#pragma mark - Objective C Class declarations\n"
438 "// Forward declarations of Objective C classes that we can use as\n"
439 "// static values in struct initializers.\n"
440 "// We don't use [Foo class] because it is not a static value.\n");
441 }
442 for (const auto& i : fwd_decls) {
443 printer->Print("$value$\n", "value", i);
444 }
445 if (!fwd_decls.empty()) {
446 printer->Print("\n");
447 }
448 printer->Print(
449 "#pragma mark - $root_class_name$\n"
450 "\n"
451 "@implementation $root_class_name$\n\n",
452 "root_class_name", root_class_name_);
453
454 const bool file_contains_extensions = FileContainsExtensions(file_);
455
456 // If there were any extensions or this file has any dependencies, output
457 // a registry to override to create the file specific registry.
458 if (file_contains_extensions || !deps_with_extensions.empty()) {
459 printer->Print(
460 "+ (GPBExtensionRegistry*)extensionRegistry {\n"
461 " // This is called by +initialize so there is no need to worry\n"
462 " // about thread safety and initialization of registry.\n"
463 " static GPBExtensionRegistry* registry = nil;\n"
464 " if (!registry) {\n"
465 " GPB_DEBUG_CHECK_RUNTIME_VERSIONS();\n"
466 " registry = [[GPBExtensionRegistry alloc] init];\n");
467
468 printer->Indent();
469 printer->Indent();
470
471 if (file_contains_extensions) {
472 printer->Print(
473 "static GPBExtensionDescription descriptions[] = {\n");
474 printer->Indent();
475 for (const auto& generator : extension_generators_) {
476 generator->GenerateStaticVariablesInitialization(printer);
477 }
478 for (const auto& generator : message_generators_) {
479 generator->GenerateStaticVariablesInitialization(printer);
480 }
481 printer->Outdent();
482 printer->Print(
483 "};\n"
484 "for (size_t i = 0; i < sizeof(descriptions) / sizeof(descriptions[0]); ++i) {\n"
485 " GPBExtensionDescriptor *extension =\n"
486 " [[GPBExtensionDescriptor alloc] initWithExtensionDescription:&descriptions[i]\n"
487 " usesClassRefs:YES];\n"
488 " [registry addExtension:extension];\n"
489 " [self globallyRegisterExtension:extension];\n"
490 " [extension release];\n"
491 "}\n");
492 }
493
494 if (deps_with_extensions.empty()) {
495 printer->Print(
496 "// None of the imports (direct or indirect) defined extensions, so no need to add\n"
497 "// them to this registry.\n");
498 } else {
499 printer->Print(
500 "// Merge in the imports (direct or indirect) that defined extensions.\n");
501 for (std::vector<const FileDescriptor *>::iterator iter =
502 deps_with_extensions.begin();
503 iter != deps_with_extensions.end(); ++iter) {
504 const string root_class_name(FileClassName((*iter)));
505 printer->Print(
506 "[registry addExtensions:[$dependency$ extensionRegistry]];\n",
507 "dependency", root_class_name);
508 }
509 }
510
511 printer->Outdent();
512 printer->Outdent();
513
514 printer->Print(
515 " }\n"
516 " return registry;\n"
517 "}\n");
518 } else {
519 if (file_->dependency_count() > 0) {
520 printer->Print(
521 "// No extensions in the file and none of the imports (direct or indirect)\n"
522 "// defined extensions, so no need to generate +extensionRegistry.\n");
523 } else {
524 printer->Print(
525 "// No extensions in the file and no imports, so no need to generate\n"
526 "// +extensionRegistry.\n");
527 }
528 }
529
530 printer->Print("\n@end\n\n");
531
532 // File descriptor only needed if there are messages to use it.
533 if (!message_generators_.empty()) {
534 std::map<string, string> vars;
535 vars["root_class_name"] = root_class_name_;
536 vars["package"] = file_->package();
537 vars["objc_prefix"] = FileClassPrefix(file_);
538 switch (file_->syntax()) {
539 case FileDescriptor::SYNTAX_UNKNOWN:
540 vars["syntax"] = "GPBFileSyntaxUnknown";
541 break;
542 case FileDescriptor::SYNTAX_PROTO2:
543 vars["syntax"] = "GPBFileSyntaxProto2";
544 break;
545 case FileDescriptor::SYNTAX_PROTO3:
546 vars["syntax"] = "GPBFileSyntaxProto3";
547 break;
548 }
549 printer->Print(vars,
550 "#pragma mark - $root_class_name$_FileDescriptor\n"
551 "\n"
552 "static GPBFileDescriptor *$root_class_name$_FileDescriptor(void) {\n"
553 " // This is called by +initialize so there is no need to worry\n"
554 " // about thread safety of the singleton.\n"
555 " static GPBFileDescriptor *descriptor = NULL;\n"
556 " if (!descriptor) {\n"
557 " GPB_DEBUG_CHECK_RUNTIME_VERSIONS();\n");
558 if (!vars["objc_prefix"].empty()) {
559 printer->Print(
560 vars,
561 " descriptor = [[GPBFileDescriptor alloc] initWithPackage:@\"$package$\"\n"
562 " objcPrefix:@\"$objc_prefix$\"\n"
563 " syntax:$syntax$];\n");
564 } else {
565 printer->Print(
566 vars,
567 " descriptor = [[GPBFileDescriptor alloc] initWithPackage:@\"$package$\"\n"
568 " syntax:$syntax$];\n");
569 }
570 printer->Print(
571 " }\n"
572 " return descriptor;\n"
573 "}\n"
574 "\n");
575 }
576
577 for (const auto& generator : enum_generators_) {
578 generator->GenerateSource(printer);
579 }
580 for (const auto& generator : message_generators_) {
581 generator->GenerateSource(printer);
582 }
583
584 printer->Print(
585 "\n"
586 "#pragma clang diagnostic pop\n"
587 "\n"
588 "// @@protoc_insertion_point(global_scope)\n");
589 }
590
591 // Helper to print the import of the runtime support at the top of generated
592 // files. This currently only supports the runtime coming from a framework
593 // as defined by the official CocoaPod.
PrintFileRuntimePreamble(io::Printer * printer,const std::vector<string> & headers_to_import) const594 void FileGenerator::PrintFileRuntimePreamble(
595 io::Printer* printer, const std::vector<string>& headers_to_import) const {
596 printer->Print(
597 "// Generated by the protocol buffer compiler. DO NOT EDIT!\n"
598 "// source: $filename$\n"
599 "\n",
600 "filename", file_->name());
601 ImportWriter::PrintRuntimeImports(
602 printer, headers_to_import, options_.runtime_import_prefix, true);
603 printer->Print("\n");
604 }
605
606 } // namespace objectivec
607 } // namespace compiler
608 } // namespace protobuf
609 } // namespace google
610