blob: 4caecc98749b6deae5fe79a346929c35ea071a90 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// 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 <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.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 "absl/strings/string_view.h"
#include "google/protobuf/compiler/cpp/generator.h"
#include "google/protobuf/compiler/cpp/helpers.h"
#include "google/protobuf/compiler/cpp/names.h"
#include "google/protobuf/compiler/cpp/options.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/generated_enum_util.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", std::string(enum_->name())},
{"Enum_", ResolveKnownNameCollisions(enum_->name(),
enum_->containing_type() != nullptr
? NameContext::kMessage
: NameContext::kFile,
NameKind::kType)},
{"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())},
{"return_type", CppGenerator::GetResolvedSourceFeatures(*enum_)
.GetExtension(::pb::cpp)
.enum_name_uses_string_view()
? "::absl::string_view"
: "const ::std::string&"},
// TODO: Enable this everywhere.
{"nodiscard", options.opensource_runtime ? "[[nodiscard]]" : ""},
};
}
// 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);
sorted_unique_values_.reserve(enum_->value_count());
for (int i = 0; i < enum_->value_count(); ++i) {
sorted_unique_values_.push_back(enum_->value(i)->number());
}
// Sort and deduplicate
absl::c_sort(sorted_unique_values_);
sorted_unique_values_.erase(
std::unique(sorted_unique_values_.begin(), sorted_unique_values_.end()),
sorted_unique_values_.end());
}
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() ? "[[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 $extern const uint32_t $Msg_Enum$_internal_data_[];
inline constexpr $Msg_Enum$ $Msg_Enum_Enum_MIN$ =
static_cast<$Msg_Enum$>($kMin$);
inline constexpr $Msg_Enum$ $Msg_Enum_Enum_MAX$ =
static_cast<$Msg_Enum$>($kMax$);
)cc");
// Generate the inline `_IsValid` function choosing the best implementation
// for the values.
GenerateIsValid(p);
if (generate_array_size_) {
p->Emit({Sub("Msg_Enum_Enum_ARRAYSIZE",
absl::StrCat(p->LookupVar("Msg_Enum_"), enum_->name(),
"_ARRAYSIZE"))
.AnnotatedAs(enum_)},
R"cc(
inline constexpr int $Msg_Enum_Enum_ARRAYSIZE$ = $kMax$ + 1;
)cc");
}
if (has_reflection_) {
p->Emit(R"cc(
$nodiscard $$dllexport_decl $const $pb$::EnumDescriptor* $nonnull$
$Msg_Enum$_descriptor();
//~ ADL Hook
$nodiscard $inline auto ProtobufInternalGetEnumDescriptor($Msg_Enum$) {
return $Msg_Enum$_descriptor();
}
)cc");
} else {
p->Emit(R"cc(
$nodiscard $$return_type$ $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>
$nodiscard $$return_type$ $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 <>
$nodiscard $inline $return_type$ $Msg_Enum$_Name($Msg_Enum$ value) {
return $pbi$::NameOfDenseEnum<$Msg_Enum$_descriptor, $kMin$, $kMax$>(
static_cast<int>(value));
}
)cc");
}
} else {
p->Emit({{"static_assert", write_assert}}, R"cc(
template <typename T>
$nodiscard $$return_type$ $Msg_Enum$_Name(T value) {
$static_assert$;
return $pbi$::NameOfEnum($Msg_Enum$_descriptor(), value);
}
)cc");
}
if (has_reflection_) {
p->Emit(R"cc(
$nodiscard $inline bool $Msg_Enum$_Parse(
//~
::absl::string_view name, $Msg_Enum$* $nonnull$ value) {
return $pbi$::ParseNamedEnum<$Msg_Enum$>($Msg_Enum$_descriptor(), name,
value);
}
)cc");
} else {
p->Emit(R"cc(
$nodiscard $bool $Msg_Enum$_Parse(
//~
::absl::string_view name, $Msg_Enum$* $nonnull$ 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_) {
p->Emit(R"cc(
template <>
inline const EnumDescriptor* $nonnull$ GetEnumDescriptor<$::Msg_Enum$>() {
return $::Msg_Enum$_descriptor();
}
)cc");
} else {
p->Emit(R"cc(
template <>
struct internal::LiteEnumFuncs<$::Msg_Enum$> {
static constexpr bool kIsDefined = true;
static constexpr auto kParseFunc = $::Msg_Enum$_Parse;
static constexpr auto kNameFunc = $::Msg_Enum$_Name<int>;
};
)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);
const bool deprecated = value->options().deprecated();
p->Emit(
{
Sub("VALUE", EnumValueName(enum_->value(j))).AnnotatedAs(value),
{"DEPRECATED", deprecated ? "[[deprecated]]" : ""},
// The alias initializer references the deprecated enumerator, so
// wrap it to avoid triggering -Wdeprecated-declarations in code the
// user does not control. See protocolbuffers/protobuf#18205.
{"IGNORE_DEPRECATION_START",
deprecated ? "PROTOBUF_IGNORE_DEPRECATION_START" : ""},
{"IGNORE_DEPRECATION_STOP",
deprecated ? "PROTOBUF_IGNORE_DEPRECATION_STOP" : ""},
},
R"cc(
$IGNORE_DEPRECATION_START$
$DEPRECATED $static constexpr $Enum_$ $VALUE$ = $Msg_Enum$_$VALUE$;
$IGNORE_DEPRECATION_STOP$
)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(
$nodiscard $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"(
static inline const $pb$::EnumDescriptor* $nonnull$ $Enum$_descriptor() {
return $Msg_Enum$_descriptor();
}
)");
}
p->Emit(R"cc(
template <typename T>
$nodiscard $static inline $return_type$ $Enum$_Name(T value) {
return $Msg_Enum$_Name(value);
}
$nodiscard $static inline bool $Enum$_Parse(
//~
::absl::string_view name, $Enum_$* $nonnull$ value) {
return $Msg_Enum$_Parse(name, value);
}
)cc");
}
void EnumGenerator::GenerateIsValid(io::Printer* p) const {
auto v = p->WithVars(EnumVars(enum_, options_, limits_.min, limits_.max));
// For simple enums we skip the generic ValidateEnum call and use better
// codegen. It matches the speed of the previous switch-based codegen.
// For more complex enums we use the new algorithm with the encoded data.
if (sorted_unique_values_.front() +
static_cast<int64_t>(sorted_unique_values_.size()) - 1 ==
sorted_unique_values_.back()) {
// They are sequential. Do a simple range check.
p->Emit({{"min", sorted_unique_values_.front()},
{"max", sorted_unique_values_.back()}},
R"cc(
$nodiscard $inline bool $Msg_Enum$_IsValid(int value) {
return $min$ <= value && value <= $max$;
}
)cc");
} else if (sorted_unique_values_.front() >= 0 &&
sorted_unique_values_.back() < 64) {
// Not sequential, but they fit in a 64-bit bitmap.
uint64_t bitmap = 0;
for (int n : sorted_unique_values_) {
bitmap |= uint64_t{1} << n;
}
p->Emit({{"bitmap", bitmap}, {"max", sorted_unique_values_.back()}},
R"cc(
$nodiscard $inline bool $Msg_Enum$_IsValid(int value) {
return 0 <= value && value <= $max$ && (($bitmap$u >> value) & 1) != 0;
}
)cc");
} else {
// More complex struct. Use enum data structure for lookup.
p->Emit(
R"cc(
$nodiscard $inline bool $Msg_Enum$_IsValid(int value) {
return $pbi$::ValidateEnum(value, $Msg_Enum$_internal_data_);
}
)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(
$nodiscard $const $pb$::EnumDescriptor* $nonnull$
$Msg_Enum$_descriptor() {
$pbi$::AssignDescriptors(&$desc_table$);
return $file_level_enum_descriptors$[$idx$];
}
)cc");
}
// Always generate the data array, even on the simple cases because someone
// might be using it for TDP entries. If it is not used in the end, the linker
// will drop it.
p->Emit({{"encoded",
[&] {
for (uint32_t n :
google::protobuf::internal::GenerateEnumData(sorted_unique_values_)) {
p->Emit({{"n", n}}, "$n$u, ");
}
}}},
R"cc(
PROTOBUF_CONSTINIT const uint32_t $Msg_Enum$_internal_data_[] = {
$encoded$};
)cc");
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_stable_sort(offsets, [](const auto& a, const auto& b) {
return a.byte_offset < b.byte_offset;
});
std::vector<Offset> offsets_by_number = offsets;
absl::c_stable_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());
auto v = p->WithVars({
{"num_unique", number_to_canonical_name.size()},
});
p->Emit(
{
{"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 const char $Msg_Enum$_names[] = {
$names$,
};
static const $pbi$::EnumEntry $Msg_Enum$_entries[] = {
$entries$,
};
static const int $Msg_Enum$_entries_by_number[] = {
$entries_by_number$,
};
$nodiscard $bool $Msg_Enum$_Parse(::absl::string_view name,
$Msg_Enum$* $nonnull$ value) {
int int_value;
bool success = $pbi$::LookUpEnumValue(
$Msg_Enum$_entries, $num_declared$, name, &int_value);
if (success) {
*value = static_cast<$Msg_Enum$>(int_value);
}
return success;
}
)cc");
if (CppGenerator::GetResolvedSourceFeatures(*enum_)
.GetExtension(::pb::cpp)
.enum_name_uses_string_view()) {
p->Emit({}, R"cc(
$nodiscard $::absl::string_view $Msg_Enum$_Name($Msg_Enum$ value) {
int idx = $pbi$::LookUpEnumName($Msg_Enum$_entries,
$Msg_Enum$_entries_by_number,
$num_unique$, value);
return idx == -1
? ""
: $Msg_Enum$_entries[$Msg_Enum$_entries_by_number[idx]]
.name;
}
)cc");
} else {
p->Emit({}, R"cc(
static $pbi$::ExplicitlyConstructed<::std::string>
$Msg_Enum$_strings[$num_unique$] = {};
$nodiscard $const ::std::string& $Msg_Enum$_Name($Msg_Enum$ value) {
static const bool kDummy = $pbi$::InitializeEnumStrings(
$Msg_Enum$_entries, $Msg_Enum$_entries_by_number, $num_unique$,
$Msg_Enum$_strings);
(void)kDummy;
int idx = $pbi$::LookUpEnumName($Msg_Enum$_entries,
$Msg_Enum$_entries_by_number,
$num_unique$, value);
return idx == -1 ? $pbi$::GetEmptyString() : $Msg_Enum$_strings[idx].get();
}
)cc");
}
}
}
} // namespace cpp
} // namespace compiler
} // namespace protobuf
} // namespace google