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