| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // Author: kenton@google.com (Kenton Varda) |
| // Based on original Protocol Buffers design by |
| // Sanjay Ghemawat, Jeff Dean, and others. |
| |
| #include "google/protobuf/compiler/cpp/enum.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <limits> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "google/protobuf/descriptor.h" |
| #include "absl/container/btree_map.h" |
| #include "absl/container/btree_set.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/strings/str_cat.h" |
| #include "google/protobuf/compiler/cpp/helpers.h" |
| #include "google/protobuf/compiler/cpp/names.h" |
| |
| namespace google { |
| namespace protobuf { |
| namespace compiler { |
| namespace cpp { |
| namespace { |
| using Sub = ::google::protobuf::io::Printer::Sub; |
| |
| absl::flat_hash_map<absl::string_view, std::string> EnumVars( |
| const EnumDescriptor* enum_, const Options& options, |
| const EnumValueDescriptor* min, const EnumValueDescriptor* max) { |
| auto classname = ClassName(enum_, false); |
| return { |
| {"Enum", enum_->name()}, |
| {"Enum_", ResolveKeyword(enum_->name())}, |
| {"Msg_Enum", classname}, |
| {"::Msg_Enum", QualifiedClassName(enum_, options)}, |
| {"Msg_Enum_", |
| enum_->containing_type() == nullptr ? "" : absl::StrCat(classname, "_")}, |
| {"kMin", absl::StrCat(min->number())}, |
| {"kMax", absl::StrCat(max->number())}, |
| }; |
| } |
| |
| // The ARRAYSIZE constant is the max enum value plus 1. If the max enum value |
| // is kint32max, ARRAYSIZE will overflow. In such cases we should omit the |
| // generation of the ARRAYSIZE constant. |
| bool ShouldGenerateArraySize(const EnumDescriptor* descriptor) { |
| int32_t max_value = descriptor->value(0)->number(); |
| for (int i = 0; i < descriptor->value_count(); i++) { |
| if (descriptor->value(i)->number() > max_value) { |
| max_value = descriptor->value(i)->number(); |
| } |
| } |
| return max_value != std::numeric_limits<int32_t>::max(); |
| } |
| } // namespace |
| EnumGenerator::ValueLimits EnumGenerator::ValueLimits::FromEnum( |
| const EnumDescriptor* descriptor) { |
| const EnumValueDescriptor* min_desc = descriptor->value(0); |
| const EnumValueDescriptor* max_desc = descriptor->value(0); |
| |
| for (int i = 1; i < descriptor->value_count(); ++i) { |
| if (descriptor->value(i)->number() < min_desc->number()) { |
| min_desc = descriptor->value(i); |
| } |
| if (descriptor->value(i)->number() > max_desc->number()) { |
| max_desc = descriptor->value(i); |
| } |
| } |
| |
| return EnumGenerator::ValueLimits{min_desc, max_desc}; |
| } |
| |
| EnumGenerator::EnumGenerator(const EnumDescriptor* descriptor, |
| const Options& options) |
| : enum_(descriptor), |
| options_(options), |
| generate_array_size_(ShouldGenerateArraySize(descriptor)), |
| has_reflection_(HasDescriptorMethods(enum_->file(), options_)), |
| limits_(ValueLimits::FromEnum(enum_)) { |
| // The conditions here for what is "sparse" are not rigorously |
| // chosen. |
| size_t values_range = static_cast<size_t>(limits_.max->number()) - |
| static_cast<size_t>(limits_.min->number()); |
| size_t total_values = static_cast<size_t>(enum_->value_count()); |
| should_cache_ = has_reflection_ && |
| (values_range < 16u || values_range < total_values * 2u); |
| } |
| |
| void EnumGenerator::GenerateDefinition(io::Printer* p) { |
| auto v1 = p->WithVars(EnumVars(enum_, options_, limits_.min, limits_.max)); |
| |
| auto v2 = p->WithVars({ |
| Sub("Msg_Enum_Enum_MIN", |
| absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(), "_MIN")) |
| .AnnotatedAs(enum_), |
| Sub("Msg_Enum_Enum_MAX", |
| absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(), "_MAX")) |
| .AnnotatedAs(enum_), |
| }); |
| p->Emit( |
| { |
| {"values", |
| [&] { |
| for (int i = 0; i < enum_->value_count(); ++i) { |
| const auto* value = enum_->value(i); |
| p->Emit( |
| { |
| Sub("Msg_Enum_VALUE", |
| absl::StrCat(p->LookupVar("Msg_Enum_"), |
| EnumValueName(value))) |
| .AnnotatedAs(value), |
| {"kNumber", Int32ToString(value->number())}, |
| {"DEPRECATED", value->options().deprecated() |
| ? "PROTOBUF_DEPRECATED" |
| : ""}, |
| }, |
| R"cc( |
| $Msg_Enum_VALUE$$ DEPRECATED$ = $kNumber$, |
| )cc"); |
| } |
| }}, |
| // Only emit annotations for the $Msg_Enum$ used in the `enum` |
| // definition. |
| Sub("Msg_Enum_annotated", p->LookupVar("Msg_Enum")) |
| .AnnotatedAs(enum_), |
| {"open_enum_sentinels", |
| [&] { |
| if (enum_->is_closed()) { |
| return; |
| } |
| |
| // For open enum semantics: generate min and max sentinel values |
| // equal to INT32_MIN and INT32_MAX |
| p->Emit({{"Msg_Enum_Msg_Enum_", |
| absl::StrCat(p->LookupVar("Msg_Enum"), "_", |
| p->LookupVar("Msg_Enum_"))}}, |
| R"cc( |
| $Msg_Enum_Msg_Enum_$INT_MIN_SENTINEL_DO_NOT_USE_ = |
| std::numeric_limits<::int32_t>::min(), |
| $Msg_Enum_Msg_Enum_$INT_MAX_SENTINEL_DO_NOT_USE_ = |
| std::numeric_limits<::int32_t>::max(), |
| )cc"); |
| }}, |
| }, |
| R"cc( |
| enum $Msg_Enum_annotated$ : int { |
| $values$, |
| $open_enum_sentinels$, |
| }; |
| |
| $dllexport_decl $bool $Msg_Enum$_IsValid(int value); |
| constexpr $Msg_Enum$ $Msg_Enum_Enum_MIN$ = static_cast<$Msg_Enum$>($kMin$); |
| constexpr $Msg_Enum$ $Msg_Enum_Enum_MAX$ = static_cast<$Msg_Enum$>($kMax$); |
| )cc"); |
| |
| if (generate_array_size_) { |
| p->Emit({Sub("Msg_Enum_Enum_ARRAYSIZE", |
| absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(), |
| "_ARRAYSIZE")) |
| .AnnotatedAs(enum_)}, |
| R"cc( |
| constexpr int $Msg_Enum_Enum_ARRAYSIZE$ = $kMax$ + 1; |
| )cc"); |
| } |
| |
| if (has_reflection_) { |
| p->Emit(R"cc( |
| $dllexport_decl $const ::$proto_ns$::EnumDescriptor* |
| $Msg_Enum$_descriptor(); |
| )cc"); |
| } else { |
| p->Emit(R"cc( |
| const std::string& $Msg_Enum$_Name($Msg_Enum$ value); |
| )cc"); |
| } |
| |
| // There are three possible implementations of $Enum$_Name() and |
| // $Msg_Enum$_Parse(), depending on whether we are using a dense enum name |
| // cache or not, and whether or not we have reflection. Very little code is |
| // shared between the three, so it is split into three Emit() calls. |
| |
| // Can't use WithVars here, since callbacks can only be passed to Emit() |
| // directly. Because this includes $Enum$, it must be a callback. |
| auto write_assert = [&] { |
| p->Emit(R"cc( |
| static_assert(std::is_same<T, $Msg_Enum$>::value || |
| std::is_integral<T>::value, |
| "Incorrect type passed to $Enum$_Name()."); |
| )cc"); |
| }; |
| |
| if (should_cache_ || !has_reflection_) { |
| p->Emit({{"static_assert", write_assert}}, R"cc( |
| template <typename T> |
| const std::string& $Msg_Enum$_Name(T value) { |
| $static_assert$; |
| return $Msg_Enum$_Name(static_cast<$Msg_Enum$>(value)); |
| } |
| )cc"); |
| if (should_cache_) { |
| // Using the NameOfEnum routine can be slow, so we create a small |
| // cache of pointers to the std::string objects that reflection |
| // stores internally. This cache is a simple contiguous array of |
| // pointers, so if the enum values are sparse, it's not worth it. |
| p->Emit(R"cc( |
| template <> |
| inline const std::string& $Msg_Enum$_Name($Msg_Enum$ value) { |
| return ::$proto_ns$::internal::NameOfDenseEnum<$Msg_Enum$_descriptor, |
| $kMin$, $kMax$>( |
| static_cast<int>(value)); |
| } |
| )cc"); |
| } else { |
| p->Emit(R"cc( |
| const std::string& $Msg_Enum$_Name($Msg_Enum$ value); |
| )cc"); |
| } |
| } else { |
| p->Emit({{"static_assert", write_assert}}, R"cc( |
| template <typename T> |
| const std::string& $Msg_Enum$_Name(T value) { |
| $static_assert$; |
| return ::$proto_ns$::internal::NameOfEnum($Msg_Enum$_descriptor(), value); |
| } |
| )cc"); |
| } |
| |
| if (has_reflection_) { |
| p->Emit(R"cc( |
| inline bool $Msg_Enum$_Parse(absl::string_view name, $Msg_Enum$* value) { |
| return ::$proto_ns$::internal::ParseNamedEnum<$Msg_Enum$>( |
| $Msg_Enum$_descriptor(), name, value); |
| } |
| )cc"); |
| } else { |
| p->Emit(R"cc( |
| bool $Msg_Enum$_Parse(absl::string_view name, $Msg_Enum$* value); |
| )cc"); |
| } |
| } |
| |
| void EnumGenerator::GenerateGetEnumDescriptorSpecializations(io::Printer* p) { |
| auto v = p->WithVars(EnumVars(enum_, options_, limits_.min, limits_.max)); |
| |
| p->Emit(R"cc( |
| template <> |
| struct is_proto_enum<$::Msg_Enum$> : std::true_type {}; |
| )cc"); |
| if (!has_reflection_) { |
| return; |
| } |
| p->Emit(R"cc( |
| template <> |
| inline const EnumDescriptor* GetEnumDescriptor<$::Msg_Enum$>() { |
| return $::Msg_Enum$_descriptor(); |
| } |
| )cc"); |
| } |
| |
| void EnumGenerator::GenerateSymbolImports(io::Printer* p) const { |
| auto v = p->WithVars(EnumVars(enum_, options_, limits_.min, limits_.max)); |
| |
| p->Emit({Sub("Enum_", p->LookupVar("Enum_")).AnnotatedAs(enum_)}, R"cc( |
| using $Enum_$ = $Msg_Enum$; |
| )cc"); |
| |
| for (int j = 0; j < enum_->value_count(); ++j) { |
| const auto* value = enum_->value(j); |
| p->Emit( |
| { |
| Sub("VALUE", EnumValueName(enum_->value(j))).AnnotatedAs(value), |
| {"DEPRECATED", |
| value->options().deprecated() ? "PROTOBUF_DEPRECATED" : ""}, |
| }, |
| R"cc( |
| $DEPRECATED $static constexpr $Enum_$ $VALUE$ = $Msg_Enum$_$VALUE$; |
| )cc"); |
| } |
| |
| p->Emit( |
| { |
| Sub("Enum_MIN", absl::StrCat(enum_->name(), "_MIN")) |
| .AnnotatedAs(enum_), |
| Sub("Enum_MAX", absl::StrCat(enum_->name(), "_MAX")) |
| .AnnotatedAs(enum_), |
| }, |
| R"cc( |
| static inline bool $Enum$_IsValid(int value) { |
| return $Msg_Enum$_IsValid(value); |
| } |
| static constexpr $Enum_$ $Enum_MIN$ = $Msg_Enum$_$Enum$_MIN; |
| static constexpr $Enum_$ $Enum_MAX$ = $Msg_Enum$_$Enum$_MAX; |
| )cc"); |
| |
| if (generate_array_size_) { |
| p->Emit( |
| { |
| Sub("Enum_ARRAYSIZE", absl::StrCat(enum_->name(), "_ARRAYSIZE")) |
| .AnnotatedAs(enum_), |
| }, |
| R"cc( |
| static constexpr int $Enum_ARRAYSIZE$ = $Msg_Enum$_$Enum$_ARRAYSIZE; |
| )cc"); |
| } |
| |
| if (has_reflection_) { |
| p->Emit(R"cc( |
| static inline const ::$proto_ns$::EnumDescriptor* $Enum$_descriptor() { |
| return $Msg_Enum$_descriptor(); |
| } |
| )cc"); |
| } |
| |
| p->Emit(R"cc( |
| template <typename T> |
| static inline const std::string& $Enum$_Name(T value) { |
| return $Msg_Enum$_Name(value); |
| } |
| static inline bool $Enum$_Parse(absl::string_view name, $Enum_$* value) { |
| return $Msg_Enum$_Parse(name, value); |
| } |
| )cc"); |
| } |
| |
| void EnumGenerator::GenerateMethods(int idx, io::Printer* p) { |
| auto v = p->WithVars(EnumVars(enum_, options_, limits_.min, limits_.max)); |
| |
| if (has_reflection_) { |
| p->Emit({{"idx", idx}}, R"cc( |
| const ::$proto_ns$::EnumDescriptor* $Msg_Enum$_descriptor() { |
| ::$proto_ns$::internal::AssignDescriptors(&$desc_table$); |
| return $file_level_enum_descriptors$[$idx$]; |
| } |
| )cc"); |
| } |
| |
| p->Emit({{"cases", |
| [&] { |
| // Multiple values may have the same number. Make sure we only |
| // cover each number once by first constructing a set containing |
| // all valid numbers, then printing a case statement for each |
| // element. |
| |
| std::vector<int> numbers; |
| numbers.reserve(enum_->value_count()); |
| for (int i = 0; i < enum_->value_count(); ++i) { |
| numbers.push_back(enum_->value(i)->number()); |
| } |
| // Sort and deduplicate `numbers`. |
| absl::c_sort(numbers); |
| numbers.erase(std::unique(numbers.begin(), numbers.end()), |
| numbers.end()); |
| |
| for (int n : numbers) { |
| p->Emit({{"n", n}}, R"cc( |
| case $n$: |
| )cc"); |
| } |
| }}}, |
| R"( |
| bool $Msg_Enum$_IsValid(int value) { |
| switch (value) { |
| $cases$; |
| return true; |
| default: |
| return false; |
| } |
| } |
| )"); |
| |
| if (!has_reflection_) { |
| // In lite mode (where descriptors are unavailable), we generate separate |
| // tables for mapping between enum names and numbers. The _entries table |
| // contains the bulk of the data and is sorted by name, while |
| // _entries_by_number is sorted by number and just contains pointers into |
| // _entries. The two tables allow mapping from name to number and number to |
| // name, both in time logarithmic in the number of enum entries. This could |
| // probably be made faster, but for now the tables are intended to be simple |
| // and compact. |
| // |
| // Enums with allow_alias = true support multiple entries with the same |
| // numerical value. In cases where there are multiple names for the same |
| // number, we treat the first name appearing in the .proto file as the |
| // canonical one. |
| |
| absl::btree_map<std::string, int> name_to_number; |
| absl::flat_hash_map<int, std::string> number_to_canonical_name; |
| for (int i = 0; i < enum_->value_count(); ++i) { |
| const auto* value = enum_->value(i); |
| name_to_number.emplace(value->name(), value->number()); |
| |
| // The same number may appear with multiple names, so we use emplace() to |
| // let the first name win. |
| number_to_canonical_name.emplace(value->number(), value->name()); |
| } |
| |
| // Build the offset table for the strings table. |
| struct Offset { |
| int number; |
| size_t index, byte_offset, len; |
| }; |
| std::vector<Offset> offsets; |
| size_t index = 0; |
| size_t offset = 0; |
| for (const auto& e : name_to_number) { |
| offsets.push_back(Offset{e.second, index, offset, e.first.size()}); |
| ++index; |
| offset += e.first.size(); |
| } |
| absl::c_sort(offsets, [](const auto& a, const auto& b) { |
| return a.byte_offset < b.byte_offset; |
| }); |
| |
| std::vector<Offset> offsets_by_number = offsets; |
| absl::c_sort(offsets_by_number, [](const auto& a, const auto& b) { |
| return a.number < b.number; |
| }); |
| |
| offsets_by_number.erase( |
| std::unique( |
| offsets_by_number.begin(), offsets_by_number.end(), |
| [](const auto& a, const auto& b) { return a.number == b.number; }), |
| offsets_by_number.end()); |
| |
| p->Emit( |
| { |
| {"num_unique", number_to_canonical_name.size()}, |
| {"num_declared", enum_->value_count()}, |
| {"names", |
| // We concatenate all the names for a given enum into one big |
| // string literal. If instead we store an array of string |
| // literals, the linker seems to put all enum strings for a given |
| // .proto file in the same section, which hinders its ability to |
| // strip out unused strings. |
| [&] { |
| for (const auto& e : name_to_number) { |
| p->Emit({{"name", e.first}}, R"cc( |
| "$name$" |
| )cc"); |
| } |
| }}, |
| {"entries", |
| [&] { |
| for (const auto& offset : offsets) { |
| p->Emit({{"number", offset.number}, |
| {"offset", offset.byte_offset}, |
| {"len", offset.len}}, |
| R"cc( |
| {{&$Msg_Enum$_names[$offset$], $len$}, $number$}, |
| )cc"); |
| } |
| }}, |
| {"entries_by_number", |
| [&] { |
| for (const auto& offset : offsets_by_number) { |
| p->Emit({{"number", offset.number}, |
| {"index", offset.index}, |
| {"name", number_to_canonical_name[offset.number]}}, |
| R"cc( |
| $index$, // $number$ -> $name$ |
| )cc"); |
| } |
| }}, |
| }, |
| R"cc( |
| static ::$proto_ns$::internal::ExplicitlyConstructed<std::string> |
| $Msg_Enum$_strings[$num_unique$] = {}; |
| |
| static const char $Msg_Enum$_names[] = { |
| $names$, |
| }; |
| |
| static const ::$proto_ns$::internal::EnumEntry $Msg_Enum$_entries[] = |
| { |
| $entries$, |
| }; |
| |
| static const int $Msg_Enum$_entries_by_number[] = { |
| $entries_by_number$, |
| }; |
| |
| const std::string& $Msg_Enum$_Name($Msg_Enum$ value) { |
| static const bool kDummy = |
| ::$proto_ns$::internal::InitializeEnumStrings( |
| $Msg_Enum$_entries, $Msg_Enum$_entries_by_number, |
| $num_unique$, $Msg_Enum$_strings); |
| (void)kDummy; |
| |
| int idx = ::$proto_ns$::internal::LookUpEnumName( |
| $Msg_Enum$_entries, $Msg_Enum$_entries_by_number, $num_unique$, |
| value); |
| return idx == -1 ? ::$proto_ns$::internal::GetEmptyString() |
| : $Msg_Enum$_strings[idx].get(); |
| } |
| |
| bool $Msg_Enum$_Parse(absl::string_view name, $Msg_Enum$* value) { |
| int int_value; |
| bool success = ::$proto_ns$::internal::LookUpEnumValue( |
| $Msg_Enum$_entries, $num_declared$, name, &int_value); |
| if (success) { |
| *value = static_cast<$Msg_Enum$>(int_value); |
| } |
| return success; |
| } |
| )cc"); |
| } |
| |
| if (enum_->containing_type() != nullptr) { |
| // Before C++17, we must define the static constants which were |
| // declared in the header, to give the linker a place to put them. |
| // But MSVC++ pre-2015 and post-2017 (version 15.5+) insists that we not. |
| p->Emit( |
| { |
| {"Msg_", ClassName(enum_->containing_type(), false)}, |
| {"constexpr_storage", |
| [&] { |
| for (int i = 0; i < enum_->value_count(); i++) { |
| p->Emit({{"VALUE", EnumValueName(enum_->value(i))}}, |
| R"cc( |
| constexpr $Msg_Enum$ $Msg_$::$VALUE$; |
| )cc"); |
| } |
| }}, |
| {"array_size", |
| [&] { |
| if (generate_array_size_) { |
| p->Emit(R"cc( |
| constexpr int $Msg_$::$Enum$_ARRAYSIZE; |
| )cc"); |
| } |
| }}, |
| }, |
| R"( |
| #if (__cplusplus < 201703) && \ |
| (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) |
| |
| $constexpr_storage$; |
| constexpr $Msg_Enum$ $Msg_$::$Enum$_MIN; |
| constexpr $Msg_Enum$ $Msg_$::$Enum$_MAX; |
| $array_size$; |
| |
| #endif // (__cplusplus < 201703) && |
| // (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) |
| )"); |
| } |
| } |
| } // namespace cpp |
| } // namespace compiler |
| } // namespace protobuf |
| } // namespace google |