1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 #include <map>
32 #include <string>
33 
34 #include <google/protobuf/compiler/objectivec/objectivec_enum.h>
35 #include <google/protobuf/compiler/objectivec/objectivec_helpers.h>
36 #include <google/protobuf/io/printer.h>
37 #include <google/protobuf/stubs/strutil.h>
38 #include <algorithm> // std::find()
39 
40 namespace google {
41 namespace protobuf {
42 namespace compiler {
43 namespace objectivec {
44 
EnumGenerator(const EnumDescriptor * descriptor)45 EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor)
46     : descriptor_(descriptor),
47       name_(EnumName(descriptor_)) {
48   // Track the names for the enum values, and if an alias overlaps a base
49   // value, skip making a name for it. Likewise if two alias overlap, the
50   // first one wins.
51   // The one gap in this logic is if two base values overlap, but for that
52   // to happen you have to have "Foo" and "FOO" or "FOO_BAR" and "FooBar",
53   // and if an enum has that, it is already going to be confusing and a
54   // compile error is just fine.
55   // The values are still tracked to support the reflection apis and
56   // TextFormat handing since they are different there.
57   std::set<std::string> value_names;
58 
59   for (int i = 0; i < descriptor_->value_count(); i++) {
60     const EnumValueDescriptor* value = descriptor_->value(i);
61     const EnumValueDescriptor* canonical_value =
62         descriptor_->FindValueByNumber(value->number());
63 
64     if (value == canonical_value) {
65       base_values_.push_back(value);
66       value_names.insert(EnumValueName(value));
67     } else {
68       std::string value_name(EnumValueName(value));
69       if (value_names.find(value_name) != value_names.end()) {
70         alias_values_to_skip_.insert(value);
71       } else {
72         value_names.insert(value_name);
73       }
74     }
75     all_values_.push_back(value);
76   }
77 }
78 
~EnumGenerator()79 EnumGenerator::~EnumGenerator() {}
80 
GenerateHeader(io::Printer * printer)81 void EnumGenerator::GenerateHeader(io::Printer* printer) {
82   std::string enum_comments;
83   SourceLocation location;
84   if (descriptor_->GetSourceLocation(&location)) {
85     enum_comments = BuildCommentsString(location, true);
86   } else {
87     enum_comments = "";
88   }
89 
90   printer->Print(
91       "#pragma mark - Enum $name$\n"
92       "\n",
93       "name", name_);
94 
95   // Swift 5 included SE0192 "Handling Future Enum Cases"
96   //   https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
97   // Since a .proto file can get new values added to an enum at any time, they
98   // are effectively "non-frozen". Even in a proto3 syntax file where there is
99   // support for the unknown value, an edit to the file can always add a new
100   // value moving something from unknown to known. Since Swift is now ABI
101   // stable, it also means a binary could contain Swift compiled against one
102   // version of the .pbobjc.h file, but finally linked against an enum with
103   // more cases. So the Swift code will always have to treat ObjC Proto Enums
104   // as "non-frozen". The default behavior in SE0192 is for all objc enums to
105   // be "non-frozen" unless marked as otherwise, so this means this generation
106   // doesn't have to bother with the `enum_extensibility` attribute, as the
107   // default will be what is needed.
108 
109   printer->Print("$comments$typedef$deprecated_attribute$ GPB_ENUM($name$) {\n",
110                  "comments", enum_comments,
111                  "deprecated_attribute", GetOptionalDeprecatedAttribute(descriptor_, descriptor_->file()),
112                  "name", name_);
113   printer->Indent();
114 
115   if (HasPreservingUnknownEnumSemantics(descriptor_->file())) {
116     // Include the unknown value.
117     printer->Print(
118       "/**\n"
119       " * Value used if any message's field encounters a value that is not defined\n"
120       " * by this enum. The message will also have C functions to get/set the rawValue\n"
121       " * of the field.\n"
122       " **/\n"
123       "$name$_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,\n",
124       "name", name_);
125   }
126   for (int i = 0; i < all_values_.size(); i++) {
127     if (alias_values_to_skip_.find(all_values_[i]) != alias_values_to_skip_.end()) {
128       continue;
129     }
130     if (all_values_[i]->GetSourceLocation(&location)) {
131       std::string comments = BuildCommentsString(location, true).c_str();
132       if (comments.length() > 0) {
133         if (i > 0) {
134           printer->Print("\n");
135         }
136         printer->Print(comments.c_str());
137       }
138     }
139 
140     printer->Print(
141         "$name$$deprecated_attribute$ = $value$,\n",
142         "name", EnumValueName(all_values_[i]),
143         "deprecated_attribute", GetOptionalDeprecatedAttribute(all_values_[i]),
144         "value", StrCat(all_values_[i]->number()));
145   }
146   printer->Outdent();
147   printer->Print(
148       "};\n"
149       "\n"
150       "GPBEnumDescriptor *$name$_EnumDescriptor(void);\n"
151       "\n"
152       "/**\n"
153       " * Checks to see if the given value is defined by the enum or was not known at\n"
154       " * the time this source was generated.\n"
155       " **/\n"
156       "BOOL $name$_IsValidValue(int32_t value);\n"
157       "\n",
158       "name", name_);
159 }
160 
GenerateSource(io::Printer * printer)161 void EnumGenerator::GenerateSource(io::Printer* printer) {
162   printer->Print(
163       "#pragma mark - Enum $name$\n"
164       "\n",
165       "name", name_);
166 
167   // Note: For the TextFormat decode info, we can't use the enum value as
168   // the key because protocol buffer enums have 'allow_alias', which lets
169   // a value be used more than once. Instead, the index into the list of
170   // enum value descriptions is used. Note: start with -1 so the first one
171   // will be zero.
172   TextFormatDecodeData text_format_decode_data;
173   int enum_value_description_key = -1;
174   std::string text_blob;
175 
176   for (int i = 0; i < all_values_.size(); i++) {
177     ++enum_value_description_key;
178     std::string short_name(EnumValueShortName(all_values_[i]));
179     text_blob += short_name + '\0';
180     if (UnCamelCaseEnumShortName(short_name) != all_values_[i]->name()) {
181       text_format_decode_data.AddString(enum_value_description_key, short_name,
182                                         all_values_[i]->name());
183     }
184   }
185 
186   printer->Print(
187       "GPBEnumDescriptor *$name$_EnumDescriptor(void) {\n"
188       "  static _Atomic(GPBEnumDescriptor*) descriptor = nil;\n"
189       "  if (!descriptor) {\n",
190       "name", name_);
191 
192   static const int kBytesPerLine = 40;  // allow for escaping
193   printer->Print(
194       "    static const char *valueNames =");
195   for (int i = 0; i < text_blob.size(); i += kBytesPerLine) {
196     printer->Print(
197         "\n        \"$data$\"",
198         "data", EscapeTrigraphs(CEscape(text_blob.substr(i, kBytesPerLine))));
199   }
200   printer->Print(
201       ";\n"
202       "    static const int32_t values[] = {\n");
203   for (int i = 0; i < all_values_.size(); i++) {
204     printer->Print("        $name$,\n",  "name", EnumValueName(all_values_[i]));
205   }
206   printer->Print("    };\n");
207 
208   if (text_format_decode_data.num_entries() == 0) {
209     printer->Print(
210         "    GPBEnumDescriptor *worker =\n"
211         "        [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol($name$)\n"
212         "                                       valueNames:valueNames\n"
213         "                                           values:values\n"
214         "                                            count:(uint32_t)(sizeof(values) / sizeof(int32_t))\n"
215         "                                     enumVerifier:$name$_IsValidValue];\n",
216         "name", name_);
217     } else {
218       printer->Print(
219         "    static const char *extraTextFormatInfo = \"$extraTextFormatInfo$\";\n"
220         "    GPBEnumDescriptor *worker =\n"
221         "        [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol($name$)\n"
222         "                                       valueNames:valueNames\n"
223         "                                           values:values\n"
224         "                                            count:(uint32_t)(sizeof(values) / sizeof(int32_t))\n"
225         "                                     enumVerifier:$name$_IsValidValue\n"
226         "                              extraTextFormatInfo:extraTextFormatInfo];\n",
227         "name", name_,
228         "extraTextFormatInfo", CEscape(text_format_decode_data.Data()));
229     }
230     printer->Print(
231       "    GPBEnumDescriptor *expected = nil;\n"
232       "    if (!atomic_compare_exchange_strong(&descriptor, &expected, worker)) {\n"
233       "      [worker release];\n"
234       "    }\n"
235       "  }\n"
236       "  return descriptor;\n"
237       "}\n\n");
238 
239   printer->Print(
240       "BOOL $name$_IsValidValue(int32_t value__) {\n"
241       "  switch (value__) {\n",
242       "name", name_);
243 
244   for (int i = 0; i < base_values_.size(); i++) {
245     printer->Print(
246         "    case $name$:\n",
247         "name", EnumValueName(base_values_[i]));
248   }
249 
250   printer->Print(
251       "      return YES;\n"
252       "    default:\n"
253       "      return NO;\n"
254       "  }\n"
255       "}\n\n");
256 }
257 }  // namespace objectivec
258 }  // namespace compiler
259 }  // namespace protobuf
260 }  // namespace google
261