blob: 7d03aa56e4807bc74e73663bbe808744c1679a8f [file] [log] [blame]
// 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 {
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({
{"Msg_Enum_Enum_MIN",
absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(), "_MIN"), enum_},
{"Msg_Enum_Enum_MAX",
absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(), "_MAX"), enum_},
});
p->Emit(
{
{"values",
[&] {
for (int i = 0; i < enum_->value_count(); ++i) {
const auto* value = enum_->value(i);
p->Emit(
{
{
"Msg_Enum_VALUE",
absl::StrCat(p->LookupVar("Msg_Enum_"),
EnumValueName(value)),
value,
},
{"kNumber", Int32ToString(value->number())},
{"DEPRECATED", value->options().deprecated()
? "PROTOBUF_DEPRECATED_ENUM"
: ""},
},
R"cc(
$Msg_Enum_VALUE$$ DEPRECATED$ = $kNumber$,
)cc");
}
}},
// Only emit annotations for the $Msg_Enum$ used in the `enum`
// definition.
{"Msg_Enum_annotated", p->LookupVar("Msg_Enum"), 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(
{{"Msg_Enum_Enum_ARRAYSIZE",
absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(), "_ARRAYSIZE"),
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));
{
auto a = p->WithVars({{"Enum_annotated", p->LookupVar("Enum_"), enum_}});
p->Emit(R"cc(
using $Enum_annotated$ = $Msg_Enum$;
)cc");
}
for (int j = 0; j < enum_->value_count(); ++j) {
const auto* value = enum_->value(j);
p->Emit(
{
{"VALUE", EnumValueName(enum_->value(j)), value},
{"DEPRECATED",
value->options().deprecated() ? "PROTOBUF_DEPRECATED_ENUM" : ""},
},
R"cc(
$DEPRECATED $static constexpr $Enum_$ $VALUE$ = $Msg_Enum$_$VALUE$;
)cc");
}
p->Emit(
{
{"Enum_MIN", absl::StrCat(enum_->name(), "_MIN"), enum_},
{"Enum_MAX", absl::StrCat(enum_->name(), "_MAX"), 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(
{{"Enum_ARRAYSIZE", absl::StrCat(enum_->name(), "_ARRAYSIZE"), 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