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
40namespace google {
41namespace protobuf {
42namespace compiler {
43namespace objectivec {
44
45EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor)
46 : descriptor_(descriptor),
47 name_(EnumName(descriptor: 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(index: i);
61 const EnumValueDescriptor* canonical_value =
62 descriptor_->FindValueByNumber(number: value->number());
63
64 if (value == canonical_value) {
65 base_values_.push_back(x: value);
66 value_names.insert(x: EnumValueName(descriptor: value));
67 } else {
68 std::string value_name(EnumValueName(descriptor: value));
69 if (value_names.find(x: value_name) != value_names.end()) {
70 alias_values_to_skip_.insert(x: value);
71 } else {
72 value_names.insert(x: value_name);
73 }
74 }
75 all_values_.push_back(x: value);
76 }
77}
78
79EnumGenerator::~EnumGenerator() {}
80
81void EnumGenerator::GenerateHeader(io::Printer* printer) {
82 std::string enum_comments;
83 SourceLocation location;
84 if (descriptor_->GetSourceLocation(out_location: &location)) {
85 enum_comments = BuildCommentsString(location, prefer_single_line: true);
86 } else {
87 enum_comments = "";
88 }
89
90 printer->Print(
91 text: "#pragma mark - Enum $name$\n"
92 "\n",
93 args: "name", args: 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(text: "$comments$typedef$deprecated_attribute$ GPB_ENUM($name$) {\n",
110 args: "comments", args: enum_comments,
111 args: "deprecated_attribute", args: GetOptionalDeprecatedAttribute(descriptor: descriptor_, file: descriptor_->file()),
112 args: "name", args: name_);
113 printer->Indent();
114
115 if (HasPreservingUnknownEnumSemantics(file: descriptor_->file())) {
116 // Include the unknown value.
117 printer->Print(
118 text: "/**\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 args: "name", args: name_);
125 }
126 for (int i = 0; i < all_values_.size(); i++) {
127 if (alias_values_to_skip_.find(x: all_values_[i]) != alias_values_to_skip_.end()) {
128 continue;
129 }
130 if (all_values_[i]->GetSourceLocation(out_location: &location)) {
131 std::string comments = BuildCommentsString(location, prefer_single_line: true).c_str();
132 if (comments.length() > 0) {
133 if (i > 0) {
134 printer->Print(text: "\n");
135 }
136 printer->Print(text: comments.c_str());
137 }
138 }
139
140 printer->Print(
141 text: "$name$$deprecated_attribute$ = $value$,\n",
142 args: "name", args: EnumValueName(descriptor: all_values_[i]),
143 args: "deprecated_attribute", args: GetOptionalDeprecatedAttribute(descriptor: all_values_[i]),
144 args: "value", args: StrCat(a: all_values_[i]->number()));
145 }
146 printer->Outdent();
147 printer->Print(
148 text: "};\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 args: "name", args: name_);
159}
160
161void EnumGenerator::GenerateSource(io::Printer* printer) {
162 printer->Print(
163 text: "#pragma mark - Enum $name$\n"
164 "\n",
165 args: "name", args: 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(descriptor: all_values_[i]));
179 text_blob += short_name + '\0';
180 if (UnCamelCaseEnumShortName(name: short_name) != all_values_[i]->name()) {
181 text_format_decode_data.AddString(key: enum_value_description_key, input_for_decode: short_name,
182 desired_output: all_values_[i]->name());
183 }
184 }
185
186 printer->Print(
187 text: "GPBEnumDescriptor *$name$_EnumDescriptor(void) {\n"
188 " static _Atomic(GPBEnumDescriptor*) descriptor = nil;\n"
189 " if (!descriptor) {\n",
190 args: "name", args: name_);
191
192 static const int kBytesPerLine = 40; // allow for escaping
193 printer->Print(
194 text: " static const char *valueNames =");
195 for (int i = 0; i < text_blob.size(); i += kBytesPerLine) {
196 printer->Print(
197 text: "\n \"$data$\"",
198 args: "data", args: EscapeTrigraphs(to_escape: CEscape(src: text_blob.substr(pos: i, n: kBytesPerLine))));
199 }
200 printer->Print(
201 text: ";\n"
202 " static const int32_t values[] = {\n");
203 for (int i = 0; i < all_values_.size(); i++) {
204 printer->Print(text: " $name$,\n", args: "name", args: EnumValueName(descriptor: all_values_[i]));
205 }
206 printer->Print(text: " };\n");
207
208 if (text_format_decode_data.num_entries() == 0) {
209 printer->Print(
210 text: " 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 args: "name", args: name_);
217 } else {
218 printer->Print(
219 text: " 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 args: "name", args: name_,
228 args: "extraTextFormatInfo", args: CEscape(src: text_format_decode_data.Data()));
229 }
230 printer->Print(
231 text: " 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 text: "BOOL $name$_IsValidValue(int32_t value__) {\n"
241 " switch (value__) {\n",
242 args: "name", args: name_);
243
244 for (int i = 0; i < base_values_.size(); i++) {
245 printer->Print(
246 text: " case $name$:\n",
247 args: "name", args: EnumValueName(descriptor: base_values_[i]));
248 }
249
250 printer->Print(
251 text: " 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