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 = 30002;
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::set<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.insert("GPBRootObject.h");
217 headers.insert("GPBMessage.h");
218 headers.insert("GPBDescriptor.h");
219 } else {
220 headers.insert("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 is_bundled_proto_);
246 const string header_extension(kHeaderExtension);
247 for (int i = 0; i < file_->public_dependency_count(); i++) {
248 import_writer.AddFile(file_->public_dependency(i), header_extension);
249 }
250 import_writer.Print(printer);
251 }
252
253 // Note:
254 // deprecated-declarations suppression is only needed if some place in this
255 // proto file is something deprecated or if it references something from
256 // another file that is deprecated.
257 printer->Print(
258 "// @@protoc_insertion_point(imports)\n"
259 "\n"
260 "#pragma clang diagnostic push\n"
261 "#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n"
262 "\n"
263 "CF_EXTERN_C_BEGIN\n"
264 "\n");
265
266 std::set<string> fwd_decls;
267 for (const auto& generator : message_generators_) {
268 generator->DetermineForwardDeclarations(&fwd_decls);
269 }
270 for (std::set<string>::const_iterator i(fwd_decls.begin());
271 i != fwd_decls.end(); ++i) {
272 printer->Print("$value$;\n", "value", *i);
273 }
274 if (fwd_decls.begin() != fwd_decls.end()) {
275 printer->Print("\n");
276 }
277
278 printer->Print(
279 "NS_ASSUME_NONNULL_BEGIN\n"
280 "\n");
281
282 // need to write out all enums first
283 for (const auto& generator : enum_generators_) {
284 generator->GenerateHeader(printer);
285 }
286
287 for (const auto& generator : message_generators_) {
288 generator->GenerateEnumHeader(printer);
289 }
290
291 // For extensions to chain together, the Root gets created even if there
292 // are no extensions.
293 printer->Print(
294 "#pragma mark - $root_class_name$\n"
295 "\n"
296 "/**\n"
297 " * Exposes the extension registry for this file.\n"
298 " *\n"
299 " * The base class provides:\n"
300 " * @code\n"
301 " * + (GPBExtensionRegistry *)extensionRegistry;\n"
302 " * @endcode\n"
303 " * which is a @c GPBExtensionRegistry that includes all the extensions defined by\n"
304 " * this file and all files that it depends on.\n"
305 " **/\n"
306 "@interface $root_class_name$ : GPBRootObject\n"
307 "@end\n"
308 "\n",
309 "root_class_name", root_class_name_);
310
311 if (extension_generators_.size() > 0) {
312 // The dynamic methods block is only needed if there are extensions.
313 printer->Print(
314 "@interface $root_class_name$ (DynamicMethods)\n",
315 "root_class_name", root_class_name_);
316
317 for (const auto& generator : extension_generators_) {
318 generator->GenerateMembersHeader(printer);
319 }
320
321 printer->Print("@end\n\n");
322 } // extension_generators_.size() > 0
323
324 for (const auto& generator : message_generators_) {
325 generator->GenerateMessageHeader(printer);
326 }
327
328 printer->Print(
329 "NS_ASSUME_NONNULL_END\n"
330 "\n"
331 "CF_EXTERN_C_END\n"
332 "\n"
333 "#pragma clang diagnostic pop\n"
334 "\n"
335 "// @@protoc_insertion_point(global_scope)\n");
336 }
337
GenerateSource(io::Printer * printer)338 void FileGenerator::GenerateSource(io::Printer *printer) {
339 // #import the runtime support.
340 std::set<string> headers;
341 headers.insert("GPBProtocolBuffers_RuntimeSupport.h");
342 PrintFileRuntimePreamble(printer, headers);
343
344 // Enums use atomic in the generated code, so add the system import as needed.
345 if (FileContainsEnums(file_)) {
346 printer->Print(
347 "#import <stdatomic.h>\n"
348 "\n");
349 }
350
351 std::vector<const FileDescriptor*> deps_with_extensions;
352 CollectMinimalFileDepsContainingExtensions(file_, &deps_with_extensions);
353
354 {
355 ImportWriter import_writer(
356 options_.generate_for_named_framework,
357 options_.named_framework_to_proto_path_mappings_path,
358 is_bundled_proto_);
359 const string header_extension(kHeaderExtension);
360
361 // #import the header for this proto file.
362 import_writer.AddFile(file_, header_extension);
363
364 // #import the headers for anything that a plain dependency of this proto
365 // file (that means they were just an include, not a "public" include).
366 std::set<string> public_import_names;
367 for (int i = 0; i < file_->public_dependency_count(); i++) {
368 public_import_names.insert(file_->public_dependency(i)->name());
369 }
370 for (int i = 0; i < file_->dependency_count(); i++) {
371 const FileDescriptor *dep = file_->dependency(i);
372 bool public_import = (public_import_names.count(dep->name()) != 0);
373 if (!public_import) {
374 import_writer.AddFile(dep, header_extension);
375 }
376 }
377
378 // If any indirect dependency provided extensions, it needs to be directly
379 // imported so it can get merged into the root's extensions registry.
380 // See the Note by CollectMinimalFileDepsContainingExtensions before
381 // changing this.
382 for (std::vector<const FileDescriptor *>::iterator iter =
383 deps_with_extensions.begin();
384 iter != deps_with_extensions.end(); ++iter) {
385 if (!IsDirectDependency(*iter, file_)) {
386 import_writer.AddFile(*iter, header_extension);
387 }
388 }
389
390 import_writer.Print(printer);
391 }
392
393 bool includes_oneof = false;
394 for (const auto& generator : message_generators_) {
395 if (generator->IncludesOneOfDefinition()) {
396 includes_oneof = true;
397 break;
398 }
399 }
400
401 // Note:
402 // deprecated-declarations suppression is only needed if some place in this
403 // proto file is something deprecated or if it references something from
404 // another file that is deprecated.
405 printer->Print(
406 "// @@protoc_insertion_point(imports)\n"
407 "\n"
408 "#pragma clang diagnostic push\n"
409 "#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n");
410 if (includes_oneof) {
411 // The generated code for oneof's uses direct ivar access, suppress the
412 // warning incase developer turn that on in the context they compile the
413 // generated code.
414 printer->Print(
415 "#pragma clang diagnostic ignored \"-Wdirect-ivar-access\"\n");
416 }
417
418 printer->Print(
419 "\n"
420 "#pragma mark - $root_class_name$\n"
421 "\n"
422 "@implementation $root_class_name$\n\n",
423 "root_class_name", root_class_name_);
424
425 const bool file_contains_extensions = FileContainsExtensions(file_);
426
427 // If there were any extensions or this file has any dependencies, output
428 // a registry to override to create the file specific registry.
429 if (file_contains_extensions || !deps_with_extensions.empty()) {
430 printer->Print(
431 "+ (GPBExtensionRegistry*)extensionRegistry {\n"
432 " // This is called by +initialize so there is no need to worry\n"
433 " // about thread safety and initialization of registry.\n"
434 " static GPBExtensionRegistry* registry = nil;\n"
435 " if (!registry) {\n"
436 " GPB_DEBUG_CHECK_RUNTIME_VERSIONS();\n"
437 " registry = [[GPBExtensionRegistry alloc] init];\n");
438
439 printer->Indent();
440 printer->Indent();
441
442 if (file_contains_extensions) {
443 printer->Print(
444 "static GPBExtensionDescription descriptions[] = {\n");
445 printer->Indent();
446 for (const auto& generator : extension_generators_) {
447 generator->GenerateStaticVariablesInitialization(printer);
448 }
449 for (const auto& generator : message_generators_) {
450 generator->GenerateStaticVariablesInitialization(printer);
451 }
452 printer->Outdent();
453 printer->Print(
454 "};\n"
455 "for (size_t i = 0; i < sizeof(descriptions) / sizeof(descriptions[0]); ++i) {\n"
456 " GPBExtensionDescriptor *extension =\n"
457 " [[GPBExtensionDescriptor alloc] initWithExtensionDescription:&descriptions[i]];\n"
458 " [registry addExtension:extension];\n"
459 " [self globallyRegisterExtension:extension];\n"
460 " [extension release];\n"
461 "}\n");
462 }
463
464 if (deps_with_extensions.empty()) {
465 printer->Print(
466 "// None of the imports (direct or indirect) defined extensions, so no need to add\n"
467 "// them to this registry.\n");
468 } else {
469 printer->Print(
470 "// Merge in the imports (direct or indirect) that defined extensions.\n");
471 for (std::vector<const FileDescriptor *>::iterator iter =
472 deps_with_extensions.begin();
473 iter != deps_with_extensions.end(); ++iter) {
474 const string root_class_name(FileClassName((*iter)));
475 printer->Print(
476 "[registry addExtensions:[$dependency$ extensionRegistry]];\n",
477 "dependency", root_class_name);
478 }
479 }
480
481 printer->Outdent();
482 printer->Outdent();
483
484 printer->Print(
485 " }\n"
486 " return registry;\n"
487 "}\n");
488 } else {
489 if (file_->dependency_count() > 0) {
490 printer->Print(
491 "// No extensions in the file and none of the imports (direct or indirect)\n"
492 "// defined extensions, so no need to generate +extensionRegistry.\n");
493 } else {
494 printer->Print(
495 "// No extensions in the file and no imports, so no need to generate\n"
496 "// +extensionRegistry.\n");
497 }
498 }
499
500 printer->Print("\n@end\n\n");
501
502 // File descriptor only needed if there are messages to use it.
503 if (message_generators_.size() > 0) {
504 std::map<string, string> vars;
505 vars["root_class_name"] = root_class_name_;
506 vars["package"] = file_->package();
507 vars["objc_prefix"] = FileClassPrefix(file_);
508 switch (file_->syntax()) {
509 case FileDescriptor::SYNTAX_UNKNOWN:
510 vars["syntax"] = "GPBFileSyntaxUnknown";
511 break;
512 case FileDescriptor::SYNTAX_PROTO2:
513 vars["syntax"] = "GPBFileSyntaxProto2";
514 break;
515 case FileDescriptor::SYNTAX_PROTO3:
516 vars["syntax"] = "GPBFileSyntaxProto3";
517 break;
518 }
519 printer->Print(vars,
520 "#pragma mark - $root_class_name$_FileDescriptor\n"
521 "\n"
522 "static GPBFileDescriptor *$root_class_name$_FileDescriptor(void) {\n"
523 " // This is called by +initialize so there is no need to worry\n"
524 " // about thread safety of the singleton.\n"
525 " static GPBFileDescriptor *descriptor = NULL;\n"
526 " if (!descriptor) {\n"
527 " GPB_DEBUG_CHECK_RUNTIME_VERSIONS();\n");
528 if (vars["objc_prefix"].size() > 0) {
529 printer->Print(
530 vars,
531 " descriptor = [[GPBFileDescriptor alloc] initWithPackage:@\"$package$\"\n"
532 " objcPrefix:@\"$objc_prefix$\"\n"
533 " syntax:$syntax$];\n");
534 } else {
535 printer->Print(
536 vars,
537 " descriptor = [[GPBFileDescriptor alloc] initWithPackage:@\"$package$\"\n"
538 " syntax:$syntax$];\n");
539 }
540 printer->Print(
541 " }\n"
542 " return descriptor;\n"
543 "}\n"
544 "\n");
545 }
546
547 for (const auto& generator : enum_generators_) {
548 generator->GenerateSource(printer);
549 }
550 for (const auto& generator : message_generators_) {
551 generator->GenerateSource(printer);
552 }
553
554 printer->Print(
555 "\n"
556 "#pragma clang diagnostic pop\n"
557 "\n"
558 "// @@protoc_insertion_point(global_scope)\n");
559 }
560
561 // Helper to print the import of the runtime support at the top of generated
562 // files. This currently only supports the runtime coming from a framework
563 // as defined by the official CocoaPod.
PrintFileRuntimePreamble(io::Printer * printer,const std::set<string> & headers_to_import) const564 void FileGenerator::PrintFileRuntimePreamble(
565 io::Printer* printer, const std::set<string>& headers_to_import) const {
566 printer->Print(
567 "// Generated by the protocol buffer compiler. DO NOT EDIT!\n"
568 "// source: $filename$\n"
569 "\n",
570 "filename", file_->name());
571
572 const string framework_name(ProtobufLibraryFrameworkName);
573 const string cpp_symbol(ProtobufFrameworkImportSymbol(framework_name));
574
575 printer->Print(
576 "// This CPP symbol can be defined to use imports that match up to the framework\n"
577 "// imports needed when using CocoaPods.\n"
578 "#if !defined($cpp_symbol$)\n"
579 " #define $cpp_symbol$ 0\n"
580 "#endif\n"
581 "\n"
582 "#if $cpp_symbol$\n",
583 "cpp_symbol", cpp_symbol);
584
585
586 for (std::set<string>::const_iterator iter = headers_to_import.begin();
587 iter != headers_to_import.end(); ++iter) {
588 printer->Print(
589 " #import <$framework_name$/$header$>\n",
590 "header", *iter,
591 "framework_name", framework_name);
592 }
593
594 printer->Print(
595 "#else\n");
596
597 for (std::set<string>::const_iterator iter = headers_to_import.begin();
598 iter != headers_to_import.end(); ++iter) {
599 printer->Print(
600 " #import \"$header$\"\n",
601 "header", *iter);
602 }
603
604 printer->Print(
605 "#endif\n"
606 "\n");
607 }
608
609 } // namespace objectivec
610 } // namespace compiler
611 } // namespace protobuf
612 } // namespace google
613