1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 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/objectivec/names.h"
9
10 #include <algorithm>
11 #include <cctype>
12 #include <cstdlib>
13 #include <iostream>
14 #include <ostream>
15 #include <string>
16 #include <vector>
17
18 #include "absl/container/flat_hash_map.h"
19 #include "absl/container/flat_hash_set.h"
20 #include "absl/log/absl_log.h"
21 #include "absl/strings/ascii.h"
22 #include "absl/strings/match.h"
23 #include "absl/strings/str_cat.h"
24 #include "absl/strings/str_split.h"
25 #include "absl/strings/string_view.h"
26 #include "absl/strings/strip.h"
27 #include "google/protobuf/compiler/code_generator.h"
28 #include "google/protobuf/compiler/objectivec/line_consumer.h"
29 #include "google/protobuf/compiler/objectivec/nsobject_methods.h"
30 #include "google/protobuf/descriptor.h"
31
32 // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
33 // error cases, so it seems to be ok to use as a back door for errors.
34
35 namespace google {
36 namespace protobuf {
37 namespace compiler {
38 namespace objectivec {
39
40 namespace {
41
BoolFromEnvVar(const char * env_var,bool default_value)42 bool BoolFromEnvVar(const char* env_var, bool default_value) {
43 const char* value = getenv(env_var);
44 if (value) {
45 return std::string("YES") == absl::AsciiStrToUpper(value);
46 }
47 return default_value;
48 }
49
50 class SimpleLineCollector : public LineConsumer {
51 public:
SimpleLineCollector(absl::flat_hash_set<std::string> * inout_set)52 explicit SimpleLineCollector(absl::flat_hash_set<std::string>* inout_set)
53 : set_(inout_set) {}
54
ConsumeLine(absl::string_view line,std::string * out_error)55 bool ConsumeLine(absl::string_view line, std::string* out_error) override {
56 set_->insert(std::string(line));
57 return true;
58 }
59
60 private:
61 absl::flat_hash_set<std::string>* set_;
62 };
63
64 class PackageToPrefixesCollector : public LineConsumer {
65 public:
PackageToPrefixesCollector(absl::string_view usage,absl::flat_hash_map<std::string,std::string> * inout_package_to_prefix_map)66 PackageToPrefixesCollector(absl::string_view usage,
67 absl::flat_hash_map<std::string, std::string>*
68 inout_package_to_prefix_map)
69 : usage_(usage), prefix_map_(inout_package_to_prefix_map) {}
70
71 bool ConsumeLine(absl::string_view line, std::string* out_error) override;
72
73 private:
74 const std::string usage_;
75 absl::flat_hash_map<std::string, std::string>* prefix_map_;
76 };
77
78 class PrefixModeStorage {
79 public:
80 PrefixModeStorage();
81
package_to_prefix_mappings_path() const82 absl::string_view package_to_prefix_mappings_path() const {
83 return package_to_prefix_mappings_path_;
84 }
set_package_to_prefix_mappings_path(absl::string_view path)85 void set_package_to_prefix_mappings_path(absl::string_view path) {
86 package_to_prefix_mappings_path_ = std::string(path);
87 package_to_prefix_map_.clear();
88 }
89
90 absl::string_view prefix_from_proto_package_mappings(
91 const FileDescriptor* file);
92
use_package_name() const93 bool use_package_name() const { return use_package_name_; }
set_use_package_name(bool on_or_off)94 void set_use_package_name(bool on_or_off) { use_package_name_ = on_or_off; }
95
exception_path() const96 absl::string_view exception_path() const { return exception_path_; }
set_exception_path(absl::string_view path)97 void set_exception_path(absl::string_view path) {
98 exception_path_ = std::string(path);
99 exceptions_.clear();
100 }
101
102 bool is_package_exempted(absl::string_view package);
103
104 // When using a proto package as the prefix, this should be added as the
105 // prefix in front of it.
forced_package_prefix() const106 absl::string_view forced_package_prefix() const { return forced_prefix_; }
set_forced_package_prefix(absl::string_view prefix)107 void set_forced_package_prefix(absl::string_view prefix) {
108 forced_prefix_ = std::string(prefix);
109 }
110
111 private:
112 bool use_package_name_;
113 absl::flat_hash_map<std::string, std::string> package_to_prefix_map_;
114 std::string package_to_prefix_mappings_path_;
115 std::string exception_path_;
116 std::string forced_prefix_;
117 absl::flat_hash_set<std::string> exceptions_;
118 };
119
PrefixModeStorage()120 PrefixModeStorage::PrefixModeStorage() {
121 // Even thought there are generation options, have an env back door since some
122 // of these helpers could be used in other plugins.
123
124 use_package_name_ = BoolFromEnvVar("GPB_OBJC_USE_PACKAGE_AS_PREFIX", false);
125
126 const char* exception_path =
127 getenv("GPB_OBJC_PACKAGE_PREFIX_EXCEPTIONS_PATH");
128 if (exception_path) {
129 exception_path_ = exception_path;
130 }
131
132 const char* prefix = getenv("GPB_OBJC_USE_PACKAGE_AS_PREFIX_PREFIX");
133 if (prefix) {
134 forced_prefix_ = prefix;
135 }
136 }
137
138 constexpr absl::string_view kNoPackagePrefix = "no_package:";
139
prefix_from_proto_package_mappings(const FileDescriptor * file)140 absl::string_view PrefixModeStorage::prefix_from_proto_package_mappings(
141 const FileDescriptor* file) {
142 if (!file) {
143 return "";
144 }
145
146 if (package_to_prefix_map_.empty() &&
147 !package_to_prefix_mappings_path_.empty()) {
148 std::string error_str;
149 // Re use the same collector as we use for expected_prefixes_path since the
150 // file format is the same.
151 PackageToPrefixesCollector collector("Package to prefixes",
152 &package_to_prefix_map_);
153 if (!ParseSimpleFile(package_to_prefix_mappings_path_, &collector,
154 &error_str)) {
155 if (error_str.empty()) {
156 error_str = absl::StrCat("protoc:0: warning: Failed to parse ",
157 "prefix to proto package mappings file: ",
158 package_to_prefix_mappings_path_);
159 }
160 std::cerr << error_str << std::endl;
161 std::cerr.flush();
162 package_to_prefix_map_.clear();
163 }
164 }
165
166 const absl::string_view package = file->package();
167 // For files without packages, the can be registered as "no_package:PATH",
168 // allowing the expected prefixes file.
169 const std::string lookup_key =
170 package.empty() ? absl::StrCat(kNoPackagePrefix, file->name())
171 : std::string(package);
172
173 auto prefix_lookup = package_to_prefix_map_.find(lookup_key);
174
175 if (prefix_lookup != package_to_prefix_map_.end()) {
176 return prefix_lookup->second;
177 }
178
179 return "";
180 }
181
is_package_exempted(absl::string_view package)182 bool PrefixModeStorage::is_package_exempted(absl::string_view package) {
183 if (exceptions_.empty() && !exception_path_.empty()) {
184 std::string error_str;
185 SimpleLineCollector collector(&exceptions_);
186 if (!ParseSimpleFile(exception_path_, &collector, &error_str)) {
187 if (error_str.empty()) {
188 error_str = std::string("protoc:0: warning: Failed to parse") +
189 std::string(" package prefix exceptions file: ") +
190 exception_path_;
191 }
192 std::cerr << error_str << std::endl;
193 std::cerr.flush();
194 exceptions_.clear();
195 }
196
197 // If the file was empty put something in it so it doesn't get reloaded over
198 // and over.
199 if (exceptions_.empty()) {
200 exceptions_.insert("<not a real package>");
201 }
202 }
203
204 return exceptions_.contains(package);
205 }
206
207 PrefixModeStorage& g_prefix_mode = *new PrefixModeStorage();
208
209 } // namespace
210
GetPackageToPrefixMappingsPath()211 absl::string_view GetPackageToPrefixMappingsPath() {
212 return g_prefix_mode.package_to_prefix_mappings_path();
213 }
214
SetPackageToPrefixMappingsPath(absl::string_view file_path)215 void SetPackageToPrefixMappingsPath(absl::string_view file_path) {
216 g_prefix_mode.set_package_to_prefix_mappings_path(file_path);
217 }
218
UseProtoPackageAsDefaultPrefix()219 bool UseProtoPackageAsDefaultPrefix() {
220 return g_prefix_mode.use_package_name();
221 }
222
SetUseProtoPackageAsDefaultPrefix(bool on_or_off)223 void SetUseProtoPackageAsDefaultPrefix(bool on_or_off) {
224 g_prefix_mode.set_use_package_name(on_or_off);
225 }
226
GetProtoPackagePrefixExceptionList()227 absl::string_view GetProtoPackagePrefixExceptionList() {
228 return g_prefix_mode.exception_path();
229 }
230
SetProtoPackagePrefixExceptionList(absl::string_view file_path)231 void SetProtoPackagePrefixExceptionList(absl::string_view file_path) {
232 g_prefix_mode.set_exception_path(file_path);
233 }
234
GetForcedPackagePrefix()235 absl::string_view GetForcedPackagePrefix() {
236 return g_prefix_mode.forced_package_prefix();
237 }
238
SetForcedPackagePrefix(absl::string_view prefix)239 void SetForcedPackagePrefix(absl::string_view prefix) {
240 g_prefix_mode.set_forced_package_prefix(prefix);
241 }
242
243 namespace {
244
245 const char* const kUpperSegmentsList[] = {"url", "http", "https"};
246
UpperSegments()247 const absl::flat_hash_set<absl::string_view>& UpperSegments() {
248 static const auto* words = [] {
249 auto* words = new absl::flat_hash_set<absl::string_view>();
250
251 for (const auto word : kUpperSegmentsList) {
252 words->emplace(word);
253 }
254 return words;
255 }();
256 return *words;
257 }
258
259 // Internal helper for name handing.
260 // Do not expose this outside of helpers, stick to having functions for specific
261 // cases (ClassName(), FieldName()), so there is always consistent suffix rules.
UnderscoresToCamelCase(absl::string_view input,bool first_capitalized)262 std::string UnderscoresToCamelCase(absl::string_view input,
263 bool first_capitalized) {
264 std::vector<std::string> values;
265 std::string current;
266
267 bool last_char_was_number = false;
268 bool last_char_was_lower = false;
269 bool last_char_was_upper = false;
270 for (int i = 0; i < input.size(); i++) {
271 char c = input[i];
272 if (absl::ascii_isdigit(c)) {
273 if (!last_char_was_number) {
274 values.push_back(current);
275 current = "";
276 }
277 current += c;
278 last_char_was_number = last_char_was_lower = last_char_was_upper = false;
279 last_char_was_number = true;
280 } else if (absl::ascii_islower(c)) {
281 // lowercase letter can follow a lowercase or uppercase letter
282 if (!last_char_was_lower && !last_char_was_upper) {
283 values.push_back(current);
284 current = "";
285 }
286 current += c; // already lower
287 last_char_was_number = last_char_was_lower = last_char_was_upper = false;
288 last_char_was_lower = true;
289 } else if (absl::ascii_isupper(c)) {
290 if (!last_char_was_upper) {
291 values.push_back(current);
292 current = "";
293 }
294 current += absl::ascii_tolower(c);
295 last_char_was_number = last_char_was_lower = last_char_was_upper = false;
296 last_char_was_upper = true;
297 } else {
298 last_char_was_number = last_char_was_lower = last_char_was_upper = false;
299 }
300 }
301 values.push_back(current);
302
303 std::string result;
304 bool first_segment_forces_upper = false;
305 for (auto& value : values) {
306 bool all_upper = UpperSegments().contains(value);
307 if (all_upper && (result.empty())) {
308 first_segment_forces_upper = true;
309 }
310 if (all_upper) {
311 absl::AsciiStrToUpper(&value);
312 } else {
313 value[0] = absl::ascii_toupper(value[0]);
314 }
315 result += value;
316 }
317 if ((!result.empty()) && !first_capitalized && !first_segment_forces_upper) {
318 result[0] = absl::ascii_tolower(result[0]);
319 }
320 return result;
321 }
322
323 const char* const kReservedWordList[] = {
324 // Note NSObject Methods:
325 // These are brought in from nsobject_methods.h that is generated
326 // using method_dump.sh. See kNSObjectMethods below.
327
328 // Objective-C "keywords" that aren't in C
329 // From
330 // http://stackoverflow.com/questions/1873630/reserved-keywords-in-objective-c
331 // with some others added on.
332 "id",
333 "_cmd",
334 "super",
335 "in",
336 "out",
337 "inout",
338 "bycopy",
339 "byref",
340 "oneway",
341 "self",
342 "instancetype",
343 "nullable",
344 "nonnull",
345 "nil",
346 "Nil",
347 "YES",
348 "NO",
349 "weak",
350
351 // C/C++ keywords (Incl C++ 0x11)
352 // From http://en.cppreference.com/w/cpp/keywords
353 "and",
354 "and_eq",
355 "alignas",
356 "alignof",
357 "asm",
358 "auto",
359 "bitand",
360 "bitor",
361 "bool",
362 "break",
363 "case",
364 "catch",
365 "char",
366 "char16_t",
367 "char32_t",
368 "class",
369 "compl",
370 "const",
371 "constexpr",
372 "const_cast",
373 "continue",
374 "decltype",
375 "default",
376 "delete",
377 "double",
378 "dynamic_cast",
379 "else",
380 "enum",
381 "explicit",
382 "export",
383 "extern ",
384 "false",
385 "float",
386 "for",
387 "friend",
388 "goto",
389 "if",
390 "inline",
391 "int",
392 "long",
393 "mutable",
394 "namespace",
395 "new",
396 "noexcept",
397 "not",
398 "not_eq",
399 "nullptr",
400 "operator",
401 "or",
402 "or_eq",
403 "private",
404 "protected",
405 "public",
406 "register",
407 "reinterpret_cast",
408 "return",
409 "short",
410 "signed",
411 "sizeof",
412 "static",
413 "static_assert",
414 "static_cast",
415 "struct",
416 "switch",
417 "template",
418 "this",
419 "thread_local",
420 "throw",
421 "true",
422 "try",
423 "typedef",
424 "typeid",
425 "typename",
426 "union",
427 "unsigned",
428 "using",
429 "virtual",
430 "void",
431 "volatile",
432 "wchar_t",
433 "while",
434 "xor",
435 "xor_eq",
436
437 // C99 keywords
438 // From
439 // http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fkeyw.htm
440 "restrict",
441
442 // GCC/Clang extension
443 "typeof",
444
445 // Not a keyword, but will break you
446 "NULL",
447
448 // C88+ specs call for these to be macros, so depending on what they are
449 // defined to be it can lead to odd errors for some Xcode/SDK versions.
450 "stdin",
451 "stdout",
452 "stderr",
453
454 // Objective-C Runtime typedefs
455 // From <obc/runtime.h>
456 "Category",
457 "Ivar",
458 "Method",
459 "Protocol",
460
461 // GPBMessage Methods
462 // Only need to add instance methods that may conflict with
463 // method declared in protos. The main cases are methods
464 // that take no arguments, or setFoo:/hasFoo: type methods.
465 "clear",
466 "data",
467 "delimitedData",
468 "descriptor",
469 "extensionRegistry",
470 "extensionsCurrentlySet",
471 "initialized",
472 "isInitialized",
473 "serializedSize",
474 "sortedExtensionsInUse",
475 "unknownFields",
476
477 // MacTypes.h names
478 "Fixed",
479 "Fract",
480 "Size",
481 "LogicalAddress",
482 "PhysicalAddress",
483 "ByteCount",
484 "ByteOffset",
485 "Duration",
486 "AbsoluteTime",
487 "OptionBits",
488 "ItemCount",
489 "PBVersion",
490 "ScriptCode",
491 "LangCode",
492 "RegionCode",
493 "OSType",
494 "ProcessSerialNumber",
495 "Point",
496 "Rect",
497 "FixedPoint",
498 "FixedRect",
499 "Style",
500 "StyleParameter",
501 "StyleField",
502 "TimeScale",
503 "TimeBase",
504 "TimeRecord",
505 };
506
ReservedWords()507 const absl::flat_hash_set<absl::string_view>& ReservedWords() {
508 static const auto* words = [] {
509 auto* words = new absl::flat_hash_set<absl::string_view>();
510
511 for (const auto word : kReservedWordList) {
512 words->emplace(word);
513 }
514 return words;
515 }();
516 return *words;
517 }
518
NSObjectMethods()519 const absl::flat_hash_set<absl::string_view>& NSObjectMethods() {
520 static const auto* words = [] {
521 auto* words = new absl::flat_hash_set<absl::string_view>();
522
523 for (const auto word : kNSObjectMethodsList) {
524 words->emplace(word);
525 }
526 return words;
527 }();
528 return *words;
529 }
530
531 // returns true is input starts with __ or _[A-Z] which are reserved identifiers
532 // in C/ C++. All calls should go through UnderscoresToCamelCase before getting
533 // here but this verifies and allows for future expansion if we decide to
534 // redefine what a reserved C identifier is (for example the GNU list
535 // https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html )
IsReservedCIdentifier(absl::string_view input)536 bool IsReservedCIdentifier(absl::string_view input) {
537 if (input.length() > 2) {
538 if (input.at(0) == '_') {
539 if (isupper(input.at(1)) || input.at(1) == '_') {
540 return true;
541 }
542 }
543 }
544 return false;
545 }
546
SanitizeNameForObjC(absl::string_view prefix,absl::string_view input,absl::string_view extension,std::string * out_suffix_added)547 std::string SanitizeNameForObjC(absl::string_view prefix,
548 absl::string_view input,
549 absl::string_view extension,
550 std::string* out_suffix_added) {
551 std::string sanitized;
552 // We add the prefix in the cases where the string is missing a prefix.
553 // We define "missing a prefix" as where 'input':
554 // a) Doesn't start with the prefix or
555 // b) Isn't equivalent to the prefix or
556 // c) Has the prefix, but the letter after the prefix is lowercase
557 if (absl::StartsWith(input, prefix)) {
558 if (input.length() == prefix.length() ||
559 !absl::ascii_isupper(input[prefix.length()])) {
560 sanitized = absl::StrCat(prefix, input);
561 } else {
562 sanitized = std::string(input);
563 }
564 } else {
565 sanitized = absl::StrCat(prefix, input);
566 }
567 if (IsReservedCIdentifier(sanitized) || ReservedWords().contains(sanitized) ||
568 NSObjectMethods().contains(sanitized)) {
569 if (out_suffix_added) *out_suffix_added = std::string(extension);
570 return absl::StrCat(sanitized, extension);
571 }
572 if (out_suffix_added) out_suffix_added->clear();
573 return sanitized;
574 }
575
NameFromFieldDescriptor(const FieldDescriptor * field)576 std::string NameFromFieldDescriptor(const FieldDescriptor* field) {
577 if (internal::cpp::IsGroupLike(*field)) {
578 return std::string(field->message_type()->name());
579 } else {
580 return std::string(field->name());
581 }
582 }
583
PathSplit(absl::string_view path,std::string * directory,std::string * basename)584 void PathSplit(absl::string_view path, std::string* directory,
585 std::string* basename) {
586 absl::string_view::size_type last_slash = path.rfind('/');
587 if (last_slash == absl::string_view::npos) {
588 if (directory) {
589 *directory = "";
590 }
591 if (basename) {
592 *basename = std::string(path);
593 }
594 } else {
595 if (directory) {
596 *directory = std::string(path.substr(0, last_slash));
597 }
598 if (basename) {
599 *basename = std::string(path.substr(last_slash + 1));
600 }
601 }
602 }
603
IsSpecialNamePrefix(absl::string_view name,const std::vector<std::string> & special_names)604 bool IsSpecialNamePrefix(absl::string_view name,
605 const std::vector<std::string>& special_names) {
606 for (const auto& special_name : special_names) {
607 const size_t length = special_name.length();
608 if (name.compare(0, length, special_name) == 0) {
609 if (name.length() > length) {
610 // If name is longer than the special_name that it matches the next
611 // character must be not lower case (newton vs newTon vs new_ton).
612 return !absl::ascii_islower(name[length]);
613 } else {
614 return true;
615 }
616 }
617 }
618 return false;
619 }
620
MaybeUnQuote(absl::string_view * input)621 void MaybeUnQuote(absl::string_view* input) {
622 if ((input->length() >= 2) &&
623 ((*input->data() == '\'' || *input->data() == '"')) &&
624 ((*input)[input->length() - 1] == *input->data())) {
625 input->remove_prefix(1);
626 input->remove_suffix(1);
627 }
628 }
629
630 } // namespace
631
IsRetainedName(absl::string_view name)632 bool IsRetainedName(absl::string_view name) {
633 // List of prefixes from
634 // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
635 static const std::vector<std::string>* retained_names =
636 new std::vector<std::string>({"new", "alloc", "copy", "mutableCopy"});
637 return IsSpecialNamePrefix(name, *retained_names);
638 }
639
IsInitName(absl::string_view name)640 bool IsInitName(absl::string_view name) {
641 static const std::vector<std::string>* init_names =
642 new std::vector<std::string>({"init"});
643 return IsSpecialNamePrefix(name, *init_names);
644 }
645
IsCreateName(absl::string_view name)646 bool IsCreateName(absl::string_view name) {
647 // List of segments from
648 // https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-103029
649 static const std::vector<std::string>* create_names =
650 new std::vector<std::string>({"Create", "Copy"});
651
652 for (const auto& create_name : *create_names) {
653 const size_t length = create_name.length();
654 size_t pos = name.find(create_name);
655 if (pos != std::string::npos) {
656 // The above docs don't actually call out anything about the characters
657 // before the special words. So it's not clear if something like
658 // "FOOCreate" would or would not match the "The Create Rule", but by not
659 // checking, and claiming it does match, then callers will annotate with
660 // `cf_returns_not_retained` which will ensure things work as desired.
661 //
662 // The footnote here is the docs do have a passing reference to "NoCopy",
663 // but again, not looking for that and just returning `true` will cause
664 // callers to annotate the api as not being a Create Rule function.
665
666 // If name is longer than the create_names[i] that it matches the next
667 // character must be not lower case (Copyright vs CopyFoo vs Copy_Foo).
668 if (name.length() > pos + length) {
669 return !absl::ascii_islower(name[pos + length]);
670 } else {
671 return true;
672 }
673 }
674 }
675 return false;
676 }
677
BaseFileName(const FileDescriptor * file)678 std::string BaseFileName(const FileDescriptor* file) {
679 std::string basename;
680 PathSplit(file->name(), nullptr, &basename);
681 return basename;
682 }
683
FileClassPrefix(const FileDescriptor * file)684 std::string FileClassPrefix(const FileDescriptor* file) {
685 // Always honor the file option.
686 if (file->options().has_objc_class_prefix()) {
687 return file->options().objc_class_prefix();
688 }
689
690 // If package prefix is specified in an prefix to proto mappings file then use
691 // that.
692 absl::string_view objc_class_prefix =
693 g_prefix_mode.prefix_from_proto_package_mappings(file);
694 if (!objc_class_prefix.empty()) {
695 return std::string(objc_class_prefix);
696 }
697
698 // If package prefix isn't enabled, done.
699 if (!g_prefix_mode.use_package_name()) {
700 return "";
701 }
702
703 // If the package is in the exceptions list, done.
704 if (g_prefix_mode.is_package_exempted(file->package())) {
705 return "";
706 }
707
708 // Transform the package into a prefix: use the dot segments as part,
709 // camelcase each one and then join them with underscores, and add an
710 // underscore at the end.
711 std::string result;
712 const std::vector<std::string> segments =
713 absl::StrSplit(file->package(), '.', absl::SkipEmpty());
714 for (const auto& segment : segments) {
715 const std::string part = UnderscoresToCamelCase(segment, true);
716 if (part.empty()) {
717 continue;
718 }
719 if (!result.empty()) {
720 result.append("_");
721 }
722 result.append(part);
723 }
724 if (!result.empty()) {
725 result.append("_");
726 }
727 return absl::StrCat(g_prefix_mode.forced_package_prefix(), result);
728 }
729
FilePath(const FileDescriptor * file)730 std::string FilePath(const FileDescriptor* file) {
731 std::string output;
732 std::string basename;
733 std::string directory;
734 PathSplit(file->name(), &directory, &basename);
735 if (!directory.empty()) {
736 output = absl::StrCat(directory, "/");
737 }
738 basename = StripProto(basename);
739
740 // CamelCase to be more ObjC friendly.
741 basename = UnderscoresToCamelCase(basename, true);
742
743 return absl::StrCat(output, basename);
744 }
745
FilePathBasename(const FileDescriptor * file)746 std::string FilePathBasename(const FileDescriptor* file) {
747 std::string output;
748 std::string basename;
749 std::string directory;
750 PathSplit(file->name(), &directory, &basename);
751 basename = StripProto(basename);
752
753 // CamelCase to be more ObjC friendly.
754 output = UnderscoresToCamelCase(basename, true);
755
756 return output;
757 }
758
FileClassName(const FileDescriptor * file)759 std::string FileClassName(const FileDescriptor* file) {
760 const std::string prefix = FileClassPrefix(file);
761 const std::string name = absl::StrCat(
762 UnderscoresToCamelCase(StripProto(BaseFileName(file)), true), "Root");
763 // There aren't really any reserved words that end in "Root", but playing
764 // it safe and checking.
765 return SanitizeNameForObjC(prefix, name, "_RootClass", nullptr);
766 }
767
ClassNameWorker(const Descriptor * descriptor)768 std::string ClassNameWorker(const Descriptor* descriptor) {
769 std::string name;
770 if (descriptor->containing_type() != nullptr) {
771 return absl::StrCat(ClassNameWorker(descriptor->containing_type()), "_",
772 descriptor->name());
773 }
774 return absl::StrCat(name, descriptor->name());
775 }
776
ClassNameWorker(const EnumDescriptor * descriptor)777 std::string ClassNameWorker(const EnumDescriptor* descriptor) {
778 std::string name;
779 if (descriptor->containing_type() != nullptr) {
780 return absl::StrCat(ClassNameWorker(descriptor->containing_type()), "_",
781 descriptor->name());
782 }
783 return absl::StrCat(name, descriptor->name());
784 }
785
ClassName(const Descriptor * descriptor)786 std::string ClassName(const Descriptor* descriptor) {
787 return ClassName(descriptor, nullptr);
788 }
789
ClassName(const Descriptor * descriptor,std::string * out_suffix_added)790 std::string ClassName(const Descriptor* descriptor,
791 std::string* out_suffix_added) {
792 // 1. Message names are used as is (style calls for CamelCase, trust it).
793 // 2. Check for reserved word at the very end and then suffix things.
794 const std::string prefix = FileClassPrefix(descriptor->file());
795 const std::string name = ClassNameWorker(descriptor);
796 return SanitizeNameForObjC(prefix, name, "_Class", out_suffix_added);
797 }
798
EnumName(const EnumDescriptor * descriptor)799 std::string EnumName(const EnumDescriptor* descriptor) {
800 // 1. Enum names are used as is (style calls for CamelCase, trust it).
801 // 2. Check for reserved word at the every end and then suffix things.
802 // message Fixed {
803 // message Size {...}
804 // enum Mumble {...}
805 // ...
806 // }
807 // yields Fixed_Class, Fixed_Size.
808 const std::string prefix = FileClassPrefix(descriptor->file());
809 const std::string name = ClassNameWorker(descriptor);
810 return SanitizeNameForObjC(prefix, name, "_Enum", nullptr);
811 }
812
EnumValueName(const EnumValueDescriptor * descriptor)813 std::string EnumValueName(const EnumValueDescriptor* descriptor) {
814 // Because of the Switch enum compatibility, the name on the enum has to have
815 // the suffix handing, so it slightly diverges from how nested classes work.
816 // enum Fixed {
817 // FOO = 1
818 // }
819 // yields Fixed_Enum and Fixed_Enum_Foo (not Fixed_Foo).
820 const std::string class_name = EnumName(descriptor->type());
821 const std::string value_str =
822 UnderscoresToCamelCase(descriptor->name(), true);
823 const std::string name = absl::StrCat(class_name, "_", value_str);
824 // There aren't really any reserved words with an underscore and a leading
825 // capital letter, but playing it safe and checking.
826 return SanitizeNameForObjC("", name, "_Value", nullptr);
827 }
828
EnumValueShortName(const EnumValueDescriptor * descriptor)829 std::string EnumValueShortName(const EnumValueDescriptor* descriptor) {
830 // Enum value names (EnumValueName above) are the enum name turned into
831 // a class name and then the value name is CamelCased and concatenated; the
832 // whole thing then gets sanitized for reserved words.
833 // The "short name" is intended to be the final leaf, the value name; but
834 // you can't simply send that off to sanitize as that could result in it
835 // getting modified when the full name didn't. For example enum
836 // "StorageModes" has a value "retain". So the full name is
837 // "StorageModes_Retain", but if we sanitize "retain" it would become
838 // "RetainValue".
839 // So the right way to get the short name is to take the full enum name
840 // and then strip off the enum name (leaving the value name and anything
841 // done by sanitize).
842 const std::string class_name = EnumName(descriptor->type());
843 const std::string long_name_prefix = absl::StrCat(class_name, "_");
844 const std::string long_name = EnumValueName(descriptor);
845 return std::string(absl::StripPrefix(long_name, long_name_prefix));
846 }
847
UnCamelCaseEnumShortName(absl::string_view name)848 std::string UnCamelCaseEnumShortName(absl::string_view name) {
849 std::string result;
850 for (int i = 0; i < name.size(); i++) {
851 char c = name[i];
852 if (i > 0 && absl::ascii_isupper(c)) {
853 result += '_';
854 }
855 result += absl::ascii_toupper(c);
856 }
857 return result;
858 }
859
ExtensionMethodName(const FieldDescriptor * descriptor)860 std::string ExtensionMethodName(const FieldDescriptor* descriptor) {
861 const std::string name = NameFromFieldDescriptor(descriptor);
862 const std::string result = UnderscoresToCamelCase(name, false);
863 return SanitizeNameForObjC("", result, "_Extension", nullptr);
864 }
865
FieldName(const FieldDescriptor * field)866 std::string FieldName(const FieldDescriptor* field) {
867 const std::string name = NameFromFieldDescriptor(field);
868 std::string result = UnderscoresToCamelCase(name, false);
869 if (field->is_repeated() && !field->is_map()) {
870 // Add "Array" before do check for reserved worlds.
871 absl::StrAppend(&result, "Array");
872 } else {
873 // If it wasn't repeated, but ends in "Array", force on the _p suffix.
874 if (absl::EndsWith(result, "Array")) {
875 absl::StrAppend(&result, "_p");
876 }
877 }
878 return SanitizeNameForObjC("", result, "_p", nullptr);
879 }
880
FieldNameCapitalized(const FieldDescriptor * field)881 std::string FieldNameCapitalized(const FieldDescriptor* field) {
882 // Want the same suffix handling, so upcase the first letter of the other
883 // name.
884 std::string result = FieldName(field);
885 if (!result.empty()) {
886 result[0] = absl::ascii_toupper(result[0]);
887 }
888 return result;
889 }
890
891 namespace {
892
893 enum class FragmentNameMode : int { kCommon, kMapKey, kObjCGenerics };
FragmentName(const FieldDescriptor * field,FragmentNameMode mode=FragmentNameMode::kCommon)894 std::string FragmentName(const FieldDescriptor* field,
895 FragmentNameMode mode = FragmentNameMode::kCommon) {
896 switch (field->type()) {
897 case FieldDescriptor::TYPE_INT32:
898 case FieldDescriptor::TYPE_SINT32:
899 case FieldDescriptor::TYPE_SFIXED32:
900 return "Int32";
901
902 case FieldDescriptor::TYPE_UINT32:
903 case FieldDescriptor::TYPE_FIXED32:
904 return "UInt32";
905
906 case FieldDescriptor::TYPE_INT64:
907 case FieldDescriptor::TYPE_SINT64:
908 case FieldDescriptor::TYPE_SFIXED64:
909 return "Int64";
910
911 case FieldDescriptor::TYPE_UINT64:
912 case FieldDescriptor::TYPE_FIXED64:
913 return "UInt64";
914
915 case FieldDescriptor::TYPE_FLOAT:
916 return "Float";
917
918 case FieldDescriptor::TYPE_DOUBLE:
919 return "Double";
920
921 case FieldDescriptor::TYPE_BOOL:
922 return "Bool";
923
924 case FieldDescriptor::TYPE_STRING: {
925 switch (mode) {
926 case FragmentNameMode::kCommon:
927 return "Object";
928 case FragmentNameMode::kMapKey:
929 return "String";
930 case FragmentNameMode::kObjCGenerics:
931 return "NSString*";
932 }
933 }
934
935 case FieldDescriptor::TYPE_BYTES:
936 return (mode == FragmentNameMode::kObjCGenerics ? "NSData*" : "Object");
937
938 case FieldDescriptor::TYPE_ENUM:
939 return "Enum";
940
941 case FieldDescriptor::TYPE_GROUP:
942 case FieldDescriptor::TYPE_MESSAGE:
943 return (mode == FragmentNameMode::kObjCGenerics
944 ? absl::StrCat(ClassName(field->message_type()), "*")
945 : "Object");
946 }
947
948 // Some compilers report reaching end of function even though all cases of
949 // the enum are handed in the switch.
950 ABSL_LOG(FATAL) << "Can't get here.";
951 }
952
FieldObjCTypeInternal(const FieldDescriptor * field,bool * out_is_ptr,std::string * out_generics)953 std::string FieldObjCTypeInternal(const FieldDescriptor* field,
954 bool* out_is_ptr, std::string* out_generics) {
955 if (field->is_map()) {
956 *out_is_ptr = true;
957 const FieldDescriptor* key_field = field->message_type()->map_key();
958 const FieldDescriptor* value_field = field->message_type()->map_value();
959
960 bool value_is_object;
961 switch (value_field->type()) {
962 case FieldDescriptor::TYPE_STRING:
963 case FieldDescriptor::TYPE_BYTES:
964 case FieldDescriptor::TYPE_GROUP:
965 case FieldDescriptor::TYPE_MESSAGE: {
966 value_is_object = true;
967 break;
968 }
969 default:
970 value_is_object = false;
971 break;
972 }
973
974 if (value_is_object && key_field->type() == FieldDescriptor::TYPE_STRING) {
975 if (out_generics) {
976 *out_generics = absl::StrCat(
977 "<NSString*, ",
978 FragmentName(value_field, FragmentNameMode::kObjCGenerics), ">");
979 }
980 return "NSMutableDictionary";
981 }
982
983 if (value_is_object && out_generics) {
984 *out_generics = absl::StrCat(
985 "<", FragmentName(value_field, FragmentNameMode::kObjCGenerics), ">");
986 }
987 return absl::StrCat("GPB",
988 FragmentName(key_field, FragmentNameMode::kMapKey),
989 FragmentName(value_field), "Dictionary");
990 }
991
992 if (field->is_repeated()) {
993 *out_is_ptr = true;
994
995 switch (field->type()) {
996 case FieldDescriptor::TYPE_STRING:
997 case FieldDescriptor::TYPE_BYTES:
998 case FieldDescriptor::TYPE_GROUP:
999 case FieldDescriptor::TYPE_MESSAGE: {
1000 if (out_generics) {
1001 *out_generics = absl::StrCat(
1002 "<", FragmentName(field, FragmentNameMode::kObjCGenerics), ">");
1003 }
1004 return "NSMutableArray";
1005 }
1006 default:
1007 return absl::StrCat("GPB", FragmentName(field), "Array");
1008 }
1009 }
1010
1011 // Single field
1012
1013 switch (field->type()) {
1014 case FieldDescriptor::TYPE_INT32:
1015 case FieldDescriptor::TYPE_SINT32:
1016 case FieldDescriptor::TYPE_SFIXED32: {
1017 *out_is_ptr = false;
1018 return "int32_t";
1019 }
1020
1021 case FieldDescriptor::TYPE_UINT32:
1022 case FieldDescriptor::TYPE_FIXED32: {
1023 *out_is_ptr = false;
1024 return "uint32_t";
1025 }
1026
1027 case FieldDescriptor::TYPE_INT64:
1028 case FieldDescriptor::TYPE_SINT64:
1029 case FieldDescriptor::TYPE_SFIXED64: {
1030 *out_is_ptr = false;
1031 return "int64_t";
1032 }
1033
1034 case FieldDescriptor::TYPE_UINT64:
1035 case FieldDescriptor::TYPE_FIXED64: {
1036 *out_is_ptr = false;
1037 return "uint64_t";
1038 }
1039
1040 case FieldDescriptor::TYPE_FLOAT: {
1041 *out_is_ptr = false;
1042 return "float";
1043 }
1044
1045 case FieldDescriptor::TYPE_DOUBLE: {
1046 *out_is_ptr = false;
1047 return "double";
1048 }
1049
1050 case FieldDescriptor::TYPE_BOOL: {
1051 *out_is_ptr = false;
1052 return "BOOL";
1053 }
1054
1055 case FieldDescriptor::TYPE_STRING: {
1056 *out_is_ptr = true;
1057 return "NSString";
1058 }
1059
1060 case FieldDescriptor::TYPE_BYTES: {
1061 *out_is_ptr = true;
1062 return "NSData";
1063 }
1064
1065 case FieldDescriptor::TYPE_ENUM: {
1066 *out_is_ptr = false;
1067 return EnumName(field->enum_type());
1068 }
1069
1070 case FieldDescriptor::TYPE_GROUP:
1071 case FieldDescriptor::TYPE_MESSAGE: {
1072 *out_is_ptr = true;
1073 return ClassName(field->message_type());
1074 }
1075 }
1076
1077 // Some compilers report reaching end of function even though all cases of
1078 // the enum are handed in the switch.
1079 ABSL_LOG(FATAL) << "Can't get here.";
1080 }
1081
1082 } // namespace
1083
FieldObjCType(const FieldDescriptor * field,FieldObjCTypeOptions options)1084 std::string FieldObjCType(const FieldDescriptor* field,
1085 FieldObjCTypeOptions options) {
1086 std::string generics;
1087 bool is_ptr;
1088 std::string base_type = FieldObjCTypeInternal(
1089 field, &is_ptr,
1090 ((options & kFieldObjCTypeOptions_OmitLightweightGenerics) != 0)
1091 ? nullptr
1092 : &generics);
1093
1094 if (!is_ptr) {
1095 if ((options & kFieldObjCTypeOptions_IncludeSpaceAfterBasicTypes) != 0) {
1096 return absl::StrCat(base_type, " ");
1097 }
1098 return base_type;
1099 }
1100
1101 if ((options & kFieldObjCTypeOptions_IncludeSpaceBeforeStar) != 0) {
1102 return absl::StrCat(base_type, generics, " *");
1103 }
1104 return absl::StrCat(base_type, generics, "*");
1105 }
1106
OneofEnumName(const OneofDescriptor * descriptor)1107 std::string OneofEnumName(const OneofDescriptor* descriptor) {
1108 const Descriptor* fieldDescriptor = descriptor->containing_type();
1109 std::string name = absl::StrCat(
1110 ClassName(fieldDescriptor), "_",
1111 UnderscoresToCamelCase(descriptor->name(), true), "_OneOfCase");
1112 // No sanitize needed because the OS never has names that end in _OneOfCase.
1113 return name;
1114 }
1115
OneofName(const OneofDescriptor * descriptor)1116 std::string OneofName(const OneofDescriptor* descriptor) {
1117 std::string name = UnderscoresToCamelCase(descriptor->name(), false);
1118 // No sanitize needed because it gets OneOfCase added and that shouldn't
1119 // ever conflict.
1120 return name;
1121 }
1122
OneofNameCapitalized(const OneofDescriptor * descriptor)1123 std::string OneofNameCapitalized(const OneofDescriptor* descriptor) {
1124 // Use the common handling and then up-case the first letter.
1125 std::string result = OneofName(descriptor);
1126 if (!result.empty()) {
1127 result[0] = absl::ascii_toupper(result[0]);
1128 }
1129 return result;
1130 }
1131
UnCamelCaseFieldName(absl::string_view name,const FieldDescriptor * field)1132 std::string UnCamelCaseFieldName(absl::string_view name,
1133 const FieldDescriptor* field) {
1134 absl::string_view worker(name);
1135 if (absl::EndsWith(worker, "_p")) {
1136 worker = absl::StripSuffix(worker, "_p");
1137 }
1138 if (field->is_repeated() && absl::EndsWith(worker, "Array")) {
1139 worker = absl::StripSuffix(worker, "Array");
1140 }
1141 if (internal::cpp::IsGroupLike(*field)) {
1142 if (!worker.empty()) {
1143 if (absl::ascii_islower(worker[0])) {
1144 std::string copy(worker);
1145 copy[0] = absl::ascii_toupper(worker[0]);
1146 return copy;
1147 }
1148 }
1149 return std::string(worker);
1150 } else {
1151 std::string result;
1152 for (int i = 0; i < worker.size(); i++) {
1153 char c = worker[i];
1154 if (absl::ascii_isupper(c)) {
1155 if (i > 0) {
1156 result += '_';
1157 }
1158 result += absl::ascii_tolower(c);
1159 } else {
1160 result += c;
1161 }
1162 }
1163 return result;
1164 }
1165 }
1166
1167 // Making these a generator option for folks that don't use CocoaPods, but do
1168 // want to put the library in a framework is an interesting question. The
1169 // problem is it means changing sources shipped with the library to actually
1170 // use a different value; so it isn't as simple as a option.
1171 const char* const ProtobufLibraryFrameworkName = "Protobuf";
1172
ProtobufFrameworkImportSymbol(absl::string_view framework_name)1173 std::string ProtobufFrameworkImportSymbol(absl::string_view framework_name) {
1174 // GPB_USE_[framework_name]_FRAMEWORK_IMPORTS
1175 return absl::StrCat("GPB_USE_", absl::AsciiStrToUpper(framework_name),
1176 "_FRAMEWORK_IMPORTS");
1177 }
1178
IsProtobufLibraryBundledProtoFile(const FileDescriptor * file)1179 bool IsProtobufLibraryBundledProtoFile(const FileDescriptor* file) {
1180 // We don't check the name prefix or proto package because some files
1181 // (descriptor.proto), aren't shipped generated by the library, so this
1182 // seems to be the safest way to only catch the ones shipped.
1183 const absl::string_view name = file->name();
1184 if (name == "google/protobuf/any.proto" ||
1185 name == "google/protobuf/api.proto" ||
1186 name == "google/protobuf/duration.proto" ||
1187 name == "google/protobuf/empty.proto" ||
1188 name == "google/protobuf/field_mask.proto" ||
1189 name == "google/protobuf/source_context.proto" ||
1190 name == "google/protobuf/struct.proto" ||
1191 name == "google/protobuf/timestamp.proto" ||
1192 name == "google/protobuf/type.proto" ||
1193 name == "google/protobuf/wrappers.proto") {
1194 return true;
1195 }
1196 return false;
1197 }
1198
1199 namespace {
1200
ConsumeLine(absl::string_view line,std::string * out_error)1201 bool PackageToPrefixesCollector::ConsumeLine(absl::string_view line,
1202 std::string* out_error) {
1203 int offset = line.find('=');
1204 if (offset == absl::string_view::npos) {
1205 *out_error =
1206 absl::StrCat(usage_, " file line without equal sign: '", line, "'.");
1207 return false;
1208 }
1209 absl::string_view package =
1210 absl::StripAsciiWhitespace(line.substr(0, offset));
1211 absl::string_view prefix =
1212 absl::StripAsciiWhitespace(line.substr(offset + 1));
1213 MaybeUnQuote(&prefix);
1214 // Don't really worry about error checking the package/prefix for
1215 // being valid. Assume the file is validated when it is created/edited.
1216 (*prefix_map_)[package] = std::string(prefix);
1217 return true;
1218 }
1219
LoadExpectedPackagePrefixes(absl::string_view expected_prefixes_path,absl::flat_hash_map<std::string,std::string> * prefix_map,std::string * out_error)1220 bool LoadExpectedPackagePrefixes(
1221 absl::string_view expected_prefixes_path,
1222 absl::flat_hash_map<std::string, std::string>* prefix_map,
1223 std::string* out_error) {
1224 if (expected_prefixes_path.empty()) {
1225 return true;
1226 }
1227
1228 PackageToPrefixesCollector collector("Expected prefixes", prefix_map);
1229 return ParseSimpleFile(expected_prefixes_path, &collector, out_error);
1230 }
1231
ValidateObjCClassPrefix(const FileDescriptor * file,absl::string_view expected_prefixes_path,const absl::flat_hash_map<std::string,std::string> & expected_package_prefixes,bool prefixes_must_be_registered,bool require_prefixes,std::string * out_error)1232 bool ValidateObjCClassPrefix(
1233 const FileDescriptor* file, absl::string_view expected_prefixes_path,
1234 const absl::flat_hash_map<std::string, std::string>&
1235 expected_package_prefixes,
1236 bool prefixes_must_be_registered, bool require_prefixes,
1237 std::string* out_error) {
1238 // Reminder: An explicit prefix option of "" is valid in case the default
1239 // prefixing is set to use the proto package and a file needs to be generated
1240 // without any prefix at all (for legacy reasons).
1241
1242 bool has_prefix = file->options().has_objc_class_prefix();
1243 bool have_expected_prefix_file = !expected_prefixes_path.empty();
1244
1245 const absl::string_view prefix = file->options().objc_class_prefix();
1246 const absl::string_view package = file->package();
1247 // For files without packages, the can be registered as "no_package:PATH",
1248 // allowing the expected prefixes file.
1249 const std::string lookup_key =
1250 package.empty() ? absl::StrCat(kNoPackagePrefix, file->name())
1251 : std::string(package);
1252
1253 // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
1254 // error cases, so it seems to be ok to use as a back door for warnings.
1255
1256 // Check: Error - See if there was an expected prefix for the package and
1257 // report if it doesn't match (wrong or missing).
1258 auto package_match = expected_package_prefixes.find(lookup_key);
1259 if (package_match != expected_package_prefixes.end()) {
1260 // There was an entry, and...
1261 if (has_prefix && package_match->second == prefix) {
1262 // ...it matches. All good, out of here!
1263 return true;
1264 } else {
1265 // ...it didn't match!
1266 *out_error =
1267 absl::StrCat("error: Expected 'option objc_class_prefix = \"",
1268 package_match->second, "\";'");
1269 if (!package.empty()) {
1270 absl::StrAppend(out_error, " for package '", package, "'");
1271 }
1272 absl::StrAppend(out_error, " in '", file->name(), "'");
1273 if (has_prefix) {
1274 absl::StrAppend(out_error, "; but found '", prefix, "' instead");
1275 }
1276 absl::StrAppend(out_error, ".");
1277 return false;
1278 }
1279 }
1280
1281 // If there was no prefix option, we're done at this point.
1282 if (!has_prefix) {
1283 if (require_prefixes) {
1284 *out_error = absl::StrCat("error: '", file->name(),
1285 "' does not have a required 'option"
1286 " objc_class_prefix'.");
1287 return false;
1288 }
1289 return true;
1290 }
1291
1292 // When the prefix is non empty, check it against the expected entries.
1293 if (!prefix.empty() && have_expected_prefix_file) {
1294 // For a non empty prefix, look for any other package that uses the prefix.
1295 std::string other_package_for_prefix;
1296 for (auto i = expected_package_prefixes.begin();
1297 i != expected_package_prefixes.end(); ++i) {
1298 if (i->second == prefix) {
1299 other_package_for_prefix = i->first;
1300 // Stop on the first real package listing, if it was a no_package file
1301 // specific entry, keep looking to try and find a package one.
1302 if (!absl::StartsWith(other_package_for_prefix, kNoPackagePrefix)) {
1303 break;
1304 }
1305 }
1306 }
1307
1308 // Check: Error - Make sure the prefix wasn't expected for a different
1309 // package (overlap is allowed, but it has to be listed as an expected
1310 // overlap).
1311 if (!other_package_for_prefix.empty()) {
1312 *out_error = absl::StrCat("error: Found 'option objc_class_prefix = \"",
1313 prefix, "\";' in '", file->name(),
1314 "'; that prefix is already used for ");
1315 if (absl::StartsWith(other_package_for_prefix, kNoPackagePrefix)) {
1316 absl::StrAppend(
1317 out_error, "file '",
1318 absl::StripPrefix(other_package_for_prefix, kNoPackagePrefix),
1319 "'.");
1320 } else {
1321 absl::StrAppend(out_error, "'package ", other_package_for_prefix,
1322 ";'.");
1323 }
1324 absl::StrAppend(out_error, " It can only be reused by adding '",
1325 lookup_key, " = ", prefix,
1326 "' to the expected prefixes file (",
1327 expected_prefixes_path, ").");
1328 return false; // Only report first usage of the prefix.
1329 }
1330 } // !prefix.empty() && have_expected_prefix_file
1331
1332 // Check: Warning - Make sure the prefix is is a reasonable value according
1333 // to Apple's rules (the checks above implicitly whitelist anything that
1334 // doesn't meet these rules).
1335 if (!prefix.empty() && !absl::ascii_isupper(prefix[0])) {
1336 std::cerr << "protoc:0: warning: Invalid 'option objc_class_prefix = \""
1337 << prefix << "\";' in '" << file->name() << "';"
1338 << " it should start with a capital letter." << std::endl;
1339 std::cerr.flush();
1340 }
1341 if (!prefix.empty() && prefix.length() < 3) {
1342 // Apple reserves 2 character prefixes for themselves. They do use some
1343 // 3 character prefixes, but they haven't updated the rules/docs.
1344 std::cerr << "protoc:0: warning: Invalid 'option objc_class_prefix = \""
1345 << prefix << "\";' in '" << file->name() << "';"
1346 << " Apple recommends they should be at least 3 characters long."
1347 << std::endl;
1348 std::cerr.flush();
1349 }
1350
1351 // Check: Error/Warning - If the given package/prefix pair wasn't expected,
1352 // issue a error/warning to added to the file.
1353 if (have_expected_prefix_file) {
1354 if (prefixes_must_be_registered) {
1355 *out_error = absl::StrCat(
1356 "error: '", file->name(), "' has 'option objc_class_prefix = \"",
1357 prefix, "\";', but it is not registered. Add '", lookup_key, " = ",
1358 (prefix.empty() ? "\"\"" : prefix),
1359 "' to the expected prefixes file (", expected_prefixes_path, ").");
1360 return false;
1361 }
1362
1363 std::cerr
1364 << "protoc:0: warning: Found unexpected 'option objc_class_prefix = \""
1365 << prefix << "\";' in '" << file->name() << "'; consider adding '"
1366 << lookup_key << " = " << (prefix.empty() ? "\"\"" : prefix)
1367 << "' to the expected prefixes file (" << expected_prefixes_path << ")."
1368 << std::endl;
1369 std::cerr.flush();
1370 }
1371
1372 return true;
1373 }
1374
1375 } // namespace
1376
Options()1377 Options::Options() {
1378 // While there are generator options, also support env variables to help with
1379 // build systems where it isn't as easy to hook in for add the generation
1380 // options when invoking protoc.
1381 const char* file_path = getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES");
1382 if (file_path) {
1383 expected_prefixes_path = file_path;
1384 }
1385 const char* suppressions =
1386 getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES_SUPPRESSIONS");
1387 if (suppressions) {
1388 expected_prefixes_suppressions =
1389 absl::StrSplit(suppressions, ';', absl::SkipEmpty());
1390 }
1391 prefixes_must_be_registered =
1392 BoolFromEnvVar("GPB_OBJC_PREFIXES_MUST_BE_REGISTERED", false);
1393 require_prefixes = BoolFromEnvVar("GPB_OBJC_REQUIRE_PREFIXES", false);
1394 }
1395
ValidateObjCClassPrefixes(const std::vector<const FileDescriptor * > & files,std::string * out_error)1396 bool ValidateObjCClassPrefixes(const std::vector<const FileDescriptor*>& files,
1397 std::string* out_error) {
1398 // Options's ctor load from the environment.
1399 Options options;
1400 return ValidateObjCClassPrefixes(files, options, out_error);
1401 }
1402
ValidateObjCClassPrefixes(const std::vector<const FileDescriptor * > & files,const Options & validation_options,std::string * out_error)1403 bool ValidateObjCClassPrefixes(const std::vector<const FileDescriptor*>& files,
1404 const Options& validation_options,
1405 std::string* out_error) {
1406 // Allow a '-' as the path for the expected prefixes to completely disable
1407 // even the most basic of checks.
1408 if (validation_options.expected_prefixes_path == "-") {
1409 return true;
1410 }
1411
1412 // Load the expected package prefixes, if available, to validate against.
1413 absl::flat_hash_map<std::string, std::string> expected_package_prefixes;
1414 if (!LoadExpectedPackagePrefixes(validation_options.expected_prefixes_path,
1415 &expected_package_prefixes, out_error)) {
1416 return false;
1417 }
1418
1419 for (auto file : files) {
1420 bool should_skip =
1421 (std::find(validation_options.expected_prefixes_suppressions.begin(),
1422 validation_options.expected_prefixes_suppressions.end(),
1423 file->name()) !=
1424 validation_options.expected_prefixes_suppressions.end());
1425 if (should_skip) {
1426 continue;
1427 }
1428
1429 bool is_valid =
1430 ValidateObjCClassPrefix(file, validation_options.expected_prefixes_path,
1431 expected_package_prefixes,
1432 validation_options.prefixes_must_be_registered,
1433 validation_options.require_prefixes, out_error);
1434 if (!is_valid) {
1435 return false;
1436 }
1437 }
1438 return true;
1439 }
1440
1441 } // namespace objectivec
1442 } // namespace compiler
1443 } // namespace protobuf
1444 } // namespace google
1445