• 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/enum.h"
9 
10 #include <cstddef>
11 #include <limits>
12 #include <string>
13 
14 #include "absl/container/flat_hash_set.h"
15 #include "absl/strings/escaping.h"
16 #include "absl/strings/str_cat.h"
17 #include "google/protobuf/compiler/objectivec/helpers.h"
18 #include "google/protobuf/compiler/objectivec/names.h"
19 #include "google/protobuf/compiler/objectivec/options.h"
20 #include "google/protobuf/compiler/objectivec/tf_decode_data.h"
21 #include "google/protobuf/descriptor.h"
22 #include "google/protobuf/io/printer.h"
23 
24 namespace google {
25 namespace protobuf {
26 namespace compiler {
27 namespace objectivec {
28 namespace {
SafelyPrintIntToCode(int v)29 std::string SafelyPrintIntToCode(int v) {
30   if (v == std::numeric_limits<int>::min()) {
31     // Some compilers try to parse -2147483648 as two tokens and then get spicy
32     // about the fact that +2147483648 cannot be represented as an int.
33     return absl::StrCat(v + 1, " - 1");
34   } else {
35     return absl::StrCat(v);
36   }
37 }
38 }  // namespace
39 
EnumGenerator(const EnumDescriptor * descriptor,const GenerationOptions & generation_options)40 EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor,
41                              const GenerationOptions& generation_options)
42     : descriptor_(descriptor),
43       generation_options_(generation_options),
44       name_(EnumName(descriptor_)) {
45   // Track the names for the enum values, and if an alias overlaps a base
46   // value, skip making a name for it. Likewise if two alias overlap, the
47   // first one wins.
48   // The one gap in this logic is if two base values overlap, but for that
49   // to happen you have to have "Foo" and "FOO" or "FOO_BAR" and "FooBar",
50   // and if an enum has that, it is already going to be confusing and a
51   // compile error is just fine.
52   // The values are still tracked to support the reflection apis and
53   // TextFormat handing since they are different there.
54   absl::flat_hash_set<std::string> value_names;
55 
56   for (int i = 0; i < descriptor_->value_count(); i++) {
57     const EnumValueDescriptor* value = descriptor_->value(i);
58     const EnumValueDescriptor* canonical_value =
59         descriptor_->FindValueByNumber(value->number());
60 
61     if (value == canonical_value) {
62       base_values_.push_back(value);
63       value_names.insert(EnumValueName(value));
64     } else {
65       if (!value_names.insert(EnumValueName(value)).second) {
66         alias_values_to_skip_.insert(value);
67       }
68     }
69     all_values_.push_back(value);
70   }
71 }
72 
GenerateHeader(io::Printer * printer) const73 void EnumGenerator::GenerateHeader(io::Printer* printer) const {
74   // Swift 5 included SE0192 "Handling Future Enum Cases"
75   //   https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
76   // Since a .proto file can get new values added to an enum at any time, they
77   // are effectively "non-frozen". Even with an EnumType::Open there is support
78   // for the unknown value, an edit to the file can always add a new value
79   // moving something from unknown to known. Since Swift is ABI stable, it also
80   // means a binary could contain Swift compiled against one version of the
81   // .pbobjc.h file, but finally linked against an enum with more cases. So the
82   // Swift code will always have to treat ObjC Proto Enums as "non-frozen". The
83   // default behavior in SE0192 is for all objc enums to be "non-frozen" unless
84   // marked as otherwise, so this means this generation doesn't have to bother
85   // with the `enum_extensibility` clang attribute, as the default will be what
86   // is needed.
87 
88   printer->Emit(
89       {
90           {"enum_name", name_},
91           {"enum_comments",
92            [&] {
93              EmitCommentsString(printer, generation_options_, descriptor_);
94            }},
95           {"enum_deprecated_attribute",
96            GetOptionalDeprecatedAttribute(descriptor_, descriptor_->file())},
97           {"maybe_unknown_value",
98            [&] {
99              if (descriptor_->is_closed()) return;
100 
101              // Include the unknown value.
102              printer->Emit(R"objc(
103                /**
104                 * Value used if any message's field encounters a value that is not defined
105                 * by this enum. The message will also have C functions to get/set the rawValue
106                 * of the field.
107                 **/
108                $enum_name$_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
109              )objc");
110            }},
111           {"enum_values",
112            [&] {
113              CommentStringFlags comment_flags = kCommentStringFlags_None;
114              for (const auto* v : all_values_) {
115                if (alias_values_to_skip_.contains(v)) continue;
116                printer->Emit(
117                    {
118                        {"name", EnumValueName(v)},
119                        {"comments",
120                         [&] {
121                           EmitCommentsString(printer, generation_options_, v,
122                                              comment_flags);
123                         }},
124                        {"deprecated_attribute",
125                         GetOptionalDeprecatedAttribute(v)},
126                        {"value", SafelyPrintIntToCode(v->number())},
127 
128                    },
129                    R"objc(
130                      $comments$
131                      $name$$ deprecated_attribute$ = $value$,
132                    )objc");
133                comment_flags = kCommentStringFlags_AddLeadingNewline;
134              }
135            }},
136       },
137       R"objc(
138         #pragma mark - Enum $enum_name$
139 
140         $enum_comments$
141         typedef$ enum_deprecated_attribute$ GPB_ENUM($enum_name$) {
142           $maybe_unknown_value$
143           $enum_values$
144         };
145 
146         GPBEnumDescriptor *$enum_name$_EnumDescriptor(void);
147 
148         /**
149          * Checks to see if the given value is defined by the enum or was not known at
150          * the time this source was generated.
151          **/
152         BOOL $enum_name$_IsValidValue(int32_t value);
153       )objc");
154   printer->Emit("\n");
155 }
156 
GenerateSource(io::Printer * printer) const157 void EnumGenerator::GenerateSource(io::Printer* printer) const {
158   // Note: For the TextFormat decode info, we can't use the enum value as
159   // the key because protocol buffer enums have 'allow_alias', which lets
160   // a value be used more than once. Instead, the index into the list of
161   // enum value descriptions is used. Note: start with -1 so the first one
162   // will be zero.
163   TextFormatDecodeData text_format_decode_data;
164   int enum_value_description_key = -1;
165   std::string text_blob;
166 
167   for (const auto* v : all_values_) {
168     ++enum_value_description_key;
169     std::string short_name(EnumValueShortName(v));
170     text_blob += short_name + '\0';
171     if (UnCamelCaseEnumShortName(short_name) != v->name()) {
172       text_format_decode_data.AddString(enum_value_description_key, short_name,
173                                         std::string(v->name()));
174     }
175   }
176 
177   printer->Emit(
178       {{"name", name_},
179        {"values_name_blob",
180         [&] {
181           static const int kBytesPerLine = 40;  // allow for escaping
182           for (size_t i = 0; i < text_blob.size(); i += kBytesPerLine) {
183             printer->Emit({{"data", EscapeTrigraphs(absl::CEscape(
184                                         text_blob.substr(i, kBytesPerLine)))},
185                            {"ending_semi",
186                             (i + kBytesPerLine) < text_blob.size() ? "" : ";"}},
187                           R"objc(
188                             "$data$"$ending_semi$
189                           )objc");
190           }
191         }},
192        {"values",
193         [&] {
194           for (const auto* v : all_values_) {
195             printer->Emit({{"value_name", EnumValueName(v)}},
196                           R"objc(
197                             $value_name$,
198                           )objc");
199           }
200         }},
201        {"maybe_extra_text_format_decl",
202         [&] {
203           if (text_format_decode_data.num_entries()) {
204             printer->Emit({{"extraTextFormatInfo",
205                             absl::CEscape(text_format_decode_data.Data())}},
206                           R"objc(
207                             static const char *extraTextFormatInfo = "$extraTextFormatInfo$";
208                           )objc");
209           }
210         }},
211        {"maybe_extraTextFormatInfo",
212         // Could not find a better way to get this extra line inserted and
213         // correctly formatted.
214         (text_format_decode_data.num_entries() == 0
215              ? ""
216              : "\n                              "
217                "extraTextFormatInfo:extraTextFormatInfo")},
218        {"enum_flags", descriptor_->is_closed()
219                           ? "GPBEnumDescriptorInitializationFlag_IsClosed"
220                           : "GPBEnumDescriptorInitializationFlag_None"},
221        {"enum_cases",
222         [&] {
223           for (const auto* v : base_values_) {
224             printer->Emit({{"case_name", EnumValueName(v)}},
225                           R"objc(
226                             case $case_name$:
227                           )objc");
228           }
229         }}},
230       R"objc(
231         #pragma mark - Enum $name$
232 
233         GPBEnumDescriptor *$name$_EnumDescriptor(void) {
234           static _Atomic(GPBEnumDescriptor*) descriptor = nil;
235           if (!descriptor) {
236             GPB_DEBUG_CHECK_RUNTIME_VERSIONS();
237             static const char *valueNames =
238                 $values_name_blob$
239             static const int32_t values[] = {
240                 $values$
241             };
242             $maybe_extra_text_format_decl$
243             GPBEnumDescriptor *worker =
244                 [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol($name$)
245                                                valueNames:valueNames
246                                                    values:values
247                                                     count:(uint32_t)(sizeof(values) / sizeof(int32_t))
248                                              enumVerifier:$name$_IsValidValue
249                                                     flags:$enum_flags$$maybe_extraTextFormatInfo$];
250             GPBEnumDescriptor *expected = nil;
251             if (!atomic_compare_exchange_strong(&descriptor, &expected, worker)) {
252               [worker release];
253             }
254           }
255           return descriptor;
256         }
257 
258         BOOL $name$_IsValidValue(int32_t value__) {
259           switch (value__) {
260             $enum_cases$
261               return YES;
262             default:
263               return NO;
264           }
265         }
266       )objc");
267   printer->Emit("\n");
268 }
269 
270 }  // namespace objectivec
271 }  // namespace compiler
272 }  // namespace protobuf
273 }  // namespace google
274