• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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