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// Author: kenton@google.com (Kenton Varda)
32// Based on original Protocol Buffers design by
33// Sanjay Ghemawat, Jeff Dean, and others.
34
35#include <google/protobuf/compiler/cpp/enum.h>
36
37#include <cstdint>
38#include <limits>
39#include <map>
40
41#include <google/protobuf/io/printer.h>
42#include <google/protobuf/stubs/strutil.h>
43#include <google/protobuf/compiler/cpp/helpers.h>
44#include <google/protobuf/compiler/cpp/names.h>
45
46namespace google {
47namespace protobuf {
48namespace compiler {
49namespace cpp {
50
51namespace {
52// The GOOGLE_ARRAYSIZE constant is the max enum value plus 1. If the max enum value
53// is kint32max, GOOGLE_ARRAYSIZE will overflow. In such cases we should omit the
54// generation of the GOOGLE_ARRAYSIZE constant.
55bool ShouldGenerateArraySize(const EnumDescriptor* descriptor) {
56 int32_t max_value = descriptor->value(index: 0)->number();
57 for (int i = 0; i < descriptor->value_count(); i++) {
58 if (descriptor->value(index: i)->number() > max_value) {
59 max_value = descriptor->value(index: i)->number();
60 }
61 }
62 return max_value != std::numeric_limits<int32_t>::max();
63}
64
65// Returns the number of unique numeric enum values. This is less than
66// descriptor->value_count() when there are aliased values.
67int CountUniqueValues(const EnumDescriptor* descriptor) {
68 std::set<int> values;
69 for (int i = 0; i < descriptor->value_count(); ++i) {
70 values.insert(x: descriptor->value(index: i)->number());
71 }
72 return values.size();
73}
74
75} // namespace
76
77EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor,
78 const std::map<std::string, std::string>& vars,
79 const Options& options)
80 : descriptor_(descriptor),
81 classname_(ClassName(descriptor, qualified: false)),
82 options_(options),
83 generate_array_size_(ShouldGenerateArraySize(descriptor)),
84 variables_(vars) {
85 variables_["classname"] = classname_;
86 variables_["classtype"] = QualifiedClassName(d: descriptor_, options);
87 variables_["short_name"] = descriptor_->name();
88 variables_["nested_name"] = descriptor_->name();
89 variables_["resolved_name"] = ResolveKeyword(name: descriptor_->name());
90 variables_["prefix"] =
91 (descriptor_->containing_type() == nullptr) ? "" : classname_ + "_";
92}
93
94EnumGenerator::~EnumGenerator() {}
95
96void EnumGenerator::GenerateDefinition(io::Printer* printer) {
97 Formatter format(printer, variables_);
98 format("enum ${1$$classname$$}$ : int {\n", descriptor_);
99 format.Indent();
100
101 const EnumValueDescriptor* min_value = descriptor_->value(index: 0);
102 const EnumValueDescriptor* max_value = descriptor_->value(index: 0);
103
104 for (int i = 0; i < descriptor_->value_count(); i++) {
105 auto format_value = format;
106 format_value.Set(key: "name", value: EnumValueName(enum_value: descriptor_->value(index: i)));
107 // In C++, an value of -2147483648 gets interpreted as the negative of
108 // 2147483648, and since 2147483648 can't fit in an integer, this produces a
109 // compiler warning. This works around that issue.
110 format_value.Set(key: "number", value: Int32ToString(number: descriptor_->value(index: i)->number()));
111 format_value.Set(key: "deprecation",
112 value: DeprecatedAttribute(options_, d: descriptor_->value(index: i)));
113
114 if (i > 0) format_value(",\n");
115 format_value("${1$$prefix$$name$$}$ $deprecation$= $number$",
116 descriptor_->value(index: i));
117
118 if (descriptor_->value(index: i)->number() < min_value->number()) {
119 min_value = descriptor_->value(index: i);
120 }
121 if (descriptor_->value(index: i)->number() > max_value->number()) {
122 max_value = descriptor_->value(index: i);
123 }
124 }
125
126 if (descriptor_->file()->syntax() == FileDescriptor::SYNTAX_PROTO3) {
127 // For new enum semantics: generate min and max sentinel values equal to
128 // INT32_MIN and INT32_MAX
129 if (descriptor_->value_count() > 0) format(",\n");
130 format(
131 "$classname$_$prefix$INT_MIN_SENTINEL_DO_NOT_USE_ = "
132 "std::numeric_limits<$int32$>::min(),\n"
133 "$classname$_$prefix$INT_MAX_SENTINEL_DO_NOT_USE_ = "
134 "std::numeric_limits<$int32$>::max()");
135 }
136
137 format.Outdent();
138 format("\n};\n");
139
140 format(
141 "$dllexport_decl $bool $classname$_IsValid(int value);\n"
142 "constexpr $classname$ ${1$$prefix$$short_name$_MIN$}$ = "
143 "$prefix$$2$;\n"
144 "constexpr $classname$ ${1$$prefix$$short_name$_MAX$}$ = "
145 "$prefix$$3$;\n",
146 descriptor_, EnumValueName(enum_value: min_value), EnumValueName(enum_value: max_value));
147
148 if (generate_array_size_) {
149 format(
150 "constexpr int ${1$$prefix$$short_name$_ARRAYSIZE$}$ = "
151 "$prefix$$short_name$_MAX + 1;\n\n",
152 descriptor_);
153 }
154
155 if (HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
156 format(
157 "$dllexport_decl $const ::$proto_ns$::EnumDescriptor* "
158 "$classname$_descriptor();\n");
159 }
160
161 // The _Name and _Parse functions. The lite implementation is table-based, so
162 // we make sure to keep the tables hidden in the .cc file.
163 if (!HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
164 format("const std::string& $classname$_Name($classname$ value);\n");
165 }
166 // The _Name() function accepts the enum type itself but also any integral
167 // type.
168 format(
169 "template<typename T>\n"
170 "inline const std::string& $classname$_Name(T enum_t_value) {\n"
171 " static_assert(::std::is_same<T, $classname$>::value ||\n"
172 " ::std::is_integral<T>::value,\n"
173 " \"Incorrect type passed to function $classname$_Name.\");\n");
174 if (HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
175 format(
176 " return ::$proto_ns$::internal::NameOfEnum(\n"
177 " $classname$_descriptor(), enum_t_value);\n");
178 } else {
179 format(
180 " return $classname$_Name(static_cast<$classname$>(enum_t_value));\n");
181 }
182 format("}\n");
183
184 if (HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
185 format(
186 "inline bool $classname$_Parse(\n"
187 " ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, $classname$* "
188 "value) "
189 "{\n"
190 " return ::$proto_ns$::internal::ParseNamedEnum<$classname$>(\n"
191 " $classname$_descriptor(), name, value);\n"
192 "}\n");
193 } else {
194 format(
195 "bool $classname$_Parse(\n"
196 " ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, $classname$* "
197 "value);\n");
198 }
199}
200
201void EnumGenerator::GenerateGetEnumDescriptorSpecializations(
202 io::Printer* printer) {
203 Formatter format(printer, variables_);
204 format(
205 "template <> struct is_proto_enum< $classtype$> : ::std::true_type "
206 "{};\n");
207 if (HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
208 format(
209 "template <>\n"
210 "inline const EnumDescriptor* GetEnumDescriptor< $classtype$>() {\n"
211 " return $classtype$_descriptor();\n"
212 "}\n");
213 }
214}
215
216void EnumGenerator::GenerateSymbolImports(io::Printer* printer) const {
217 Formatter format(printer, variables_);
218 format("typedef $classname$ $resolved_name$;\n");
219
220 for (int j = 0; j < descriptor_->value_count(); j++) {
221 std::string deprecated_attr =
222 DeprecatedAttribute(options_, d: descriptor_->value(index: j));
223 format(
224 "$1$static constexpr $resolved_name$ ${2$$3$$}$ =\n"
225 " $classname$_$3$;\n",
226 deprecated_attr, descriptor_->value(index: j),
227 EnumValueName(enum_value: descriptor_->value(index: j)));
228 }
229
230 format(
231 "static inline bool $nested_name$_IsValid(int value) {\n"
232 " return $classname$_IsValid(value);\n"
233 "}\n"
234 "static constexpr $resolved_name$ ${1$$nested_name$_MIN$}$ =\n"
235 " $classname$_$nested_name$_MIN;\n"
236 "static constexpr $resolved_name$ ${1$$nested_name$_MAX$}$ =\n"
237 " $classname$_$nested_name$_MAX;\n",
238 descriptor_);
239 if (generate_array_size_) {
240 format(
241 "static constexpr int ${1$$nested_name$_ARRAYSIZE$}$ =\n"
242 " $classname$_$nested_name$_ARRAYSIZE;\n",
243 descriptor_);
244 }
245
246 if (HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
247 format(
248 "static inline const ::$proto_ns$::EnumDescriptor*\n"
249 "$nested_name$_descriptor() {\n"
250 " return $classname$_descriptor();\n"
251 "}\n");
252 }
253
254 format(
255 "template<typename T>\n"
256 "static inline const std::string& $nested_name$_Name(T enum_t_value) {\n"
257 " static_assert(::std::is_same<T, $resolved_name$>::value ||\n"
258 " ::std::is_integral<T>::value,\n"
259 " \"Incorrect type passed to function $nested_name$_Name.\");\n"
260 " return $classname$_Name(enum_t_value);\n"
261 "}\n");
262 format(
263 "static inline bool "
264 "$nested_name$_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name,\n"
265 " $resolved_name$* value) {\n"
266 " return $classname$_Parse(name, value);\n"
267 "}\n");
268}
269
270void EnumGenerator::GenerateMethods(int idx, io::Printer* printer) {
271 Formatter format(printer, variables_);
272 if (HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
273 format(
274 "const ::$proto_ns$::EnumDescriptor* $classname$_descriptor() {\n"
275 " ::$proto_ns$::internal::AssignDescriptors(&$desc_table$);\n"
276 " return $file_level_enum_descriptors$[$1$];\n"
277 "}\n",
278 idx);
279 }
280
281 format(
282 "bool $classname$_IsValid(int value) {\n"
283 " switch (value) {\n");
284
285 // Multiple values may have the same number. Make sure we only cover
286 // each number once by first constructing a set containing all valid
287 // numbers, then printing a case statement for each element.
288
289 std::set<int> numbers;
290 for (int j = 0; j < descriptor_->value_count(); j++) {
291 const EnumValueDescriptor* value = descriptor_->value(index: j);
292 numbers.insert(x: value->number());
293 }
294
295 for (std::set<int>::iterator iter = numbers.begin(); iter != numbers.end();
296 ++iter) {
297 format(" case $1$:\n", Int32ToString(number: *iter));
298 }
299
300 format(
301 " return true;\n"
302 " default:\n"
303 " return false;\n"
304 " }\n"
305 "}\n"
306 "\n");
307
308 if (!HasDescriptorMethods(file: descriptor_->file(), options: options_)) {
309 // In lite mode (where descriptors are unavailable), we generate separate
310 // tables for mapping between enum names and numbers. The _entries table
311 // contains the bulk of the data and is sorted by name, while
312 // _entries_by_number is sorted by number and just contains pointers into
313 // _entries. The two tables allow mapping from name to number and number to
314 // name, both in time logarithmic in the number of enum entries. This could
315 // probably be made faster, but for now the tables are intended to be simple
316 // and compact.
317 //
318 // Enums with allow_alias = true support multiple entries with the same
319 // numerical value. In cases where there are multiple names for the same
320 // number, we treat the first name appearing in the .proto file as the
321 // canonical one.
322 std::map<std::string, int> name_to_number;
323 std::map<int, std::string> number_to_canonical_name;
324 for (int i = 0; i < descriptor_->value_count(); i++) {
325 const EnumValueDescriptor* value = descriptor_->value(index: i);
326 name_to_number.emplace(args: value->name(), args: value->number());
327 // The same number may appear with multiple names, so we use emplace() to
328 // let the first name win.
329 number_to_canonical_name.emplace(args: value->number(), args: value->name());
330 }
331
332 format(
333 "static ::$proto_ns$::internal::ExplicitlyConstructed<std::string> "
334 "$classname$_strings[$1$] = {};\n\n",
335 CountUniqueValues(descriptor: descriptor_));
336
337 // We concatenate all the names for a given enum into one big string
338 // literal. If instead we store an array of string literals, the linker
339 // seems to put all enum strings for a given .proto file in the same
340 // section, which hinders its ability to strip out unused strings.
341 format("static const char $classname$_names[] =");
342 for (const auto& p : name_to_number) {
343 format("\n \"$1$\"", p.first);
344 }
345 format(";\n\n");
346
347 format(
348 "static const ::$proto_ns$::internal::EnumEntry $classname$_entries[] "
349 "= {\n");
350 int i = 0;
351 std::map<int, int> number_to_index;
352 int data_index = 0;
353 for (const auto& p : name_to_number) {
354 format(" { {$classname$_names + $1$, $2$}, $3$ },\n", data_index,
355 p.first.size(), p.second);
356 if (number_to_canonical_name[p.second] == p.first) {
357 number_to_index.emplace(args: p.second, args&: i);
358 }
359 ++i;
360 data_index += p.first.size();
361 }
362
363 format(
364 "};\n"
365 "\n"
366 "static const int $classname$_entries_by_number[] = {\n");
367 for (const auto& p : number_to_index) {
368 format(" $1$, // $2$ -> $3$\n", p.second, p.first,
369 number_to_canonical_name[p.first]);
370 }
371 format(
372 "};\n"
373 "\n");
374
375 format(
376 "const std::string& $classname$_Name(\n"
377 " $classname$ value) {\n"
378 " static const bool dummy =\n"
379 " ::$proto_ns$::internal::InitializeEnumStrings(\n"
380 " $classname$_entries,\n"
381 " $classname$_entries_by_number,\n"
382 " $1$, $classname$_strings);\n"
383 " (void) dummy;\n"
384 " int idx = ::$proto_ns$::internal::LookUpEnumName(\n"
385 " $classname$_entries,\n"
386 " $classname$_entries_by_number,\n"
387 " $1$, value);\n"
388 " return idx == -1 ? ::$proto_ns$::internal::GetEmptyString() :\n"
389 " $classname$_strings[idx].get();\n"
390 "}\n",
391 CountUniqueValues(descriptor: descriptor_));
392 format(
393 "bool $classname$_Parse(\n"
394 " ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, $classname$* "
395 "value) "
396 "{\n"
397 " int int_value;\n"
398 " bool success = ::$proto_ns$::internal::LookUpEnumValue(\n"
399 " $classname$_entries, $1$, name, &int_value);\n"
400 " if (success) {\n"
401 " *value = static_cast<$classname$>(int_value);\n"
402 " }\n"
403 " return success;\n"
404 "}\n",
405 descriptor_->value_count());
406 }
407
408 if (descriptor_->containing_type() != nullptr) {
409 std::string parent = ClassName(descriptor: descriptor_->containing_type(), qualified: false);
410 // Before C++17, we must define the static constants which were
411 // declared in the header, to give the linker a place to put them.
412 // But MSVC++ pre-2015 and post-2017 (version 15.5+) insists that we not.
413 format(
414 "#if (__cplusplus < 201703) && "
415 "(!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))\n");
416
417 for (int i = 0; i < descriptor_->value_count(); i++) {
418 format("constexpr $classname$ $1$::$2$;\n", parent,
419 EnumValueName(enum_value: descriptor_->value(index: i)));
420 }
421 format(
422 "constexpr $classname$ $1$::$nested_name$_MIN;\n"
423 "constexpr $classname$ $1$::$nested_name$_MAX;\n",
424 parent);
425 if (generate_array_size_) {
426 format("constexpr int $1$::$nested_name$_ARRAYSIZE;\n", parent);
427 }
428
429 format(
430 "#endif // (__cplusplus < 201703) && "
431 "(!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))\n");
432 }
433}
434
435} // namespace cpp
436} // namespace compiler
437} // namespace protobuf
438} // namespace google
439