blob: f16425a761a126ab9da3c3bb00cb35e35b969dd6 [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC. 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
#include "hpb_generator/gen_messages.h"
#include <cstddef>
#include <string>
#include <vector>
#include "google/protobuf/descriptor.pb.h"
#include "absl/numeric/bits.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "hpb_generator/context.h"
#include "hpb_generator/gen_accessors.h"
#include "hpb_generator/gen_enums.h"
#include "hpb_generator/gen_extensions.h"
#include "hpb_generator/gen_utils.h"
#include "hpb_generator/names.h"
#include "google/protobuf/descriptor.h"
#include "upb_generator/c/names.h"
#include "upb_generator/minitable/names.h"
namespace google {
namespace protobuf {
namespace hpb_generator {
using Sub = google::protobuf::io::Printer::Sub;
void WriteModelAccessDeclaration(const google::protobuf::Descriptor* descriptor,
Context& ctx);
void WriteModelPublicDeclaration(
const google::protobuf::Descriptor* descriptor,
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts,
const std::vector<const google::protobuf::EnumDescriptor*>& file_enums, Context& ctx);
void WriteExtensionIdentifiersInClassHeader(
const google::protobuf::Descriptor* message,
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts, Context& ctx);
void WriteModelProxyDeclaration(const google::protobuf::Descriptor* descriptor,
Context& ctx);
void WriteModelCProxyDeclaration(const google::protobuf::Descriptor* descriptor,
Context& ctx);
void WriteDefaultInstanceHeader(const google::protobuf::Descriptor* message,
Context& ctx);
void WriteDefaultInstanceDefinitionHeader(const google::protobuf::Descriptor* message,
Context& ctx);
void WriteUsingEnumsInHeader(
const google::protobuf::Descriptor* message,
const std::vector<const google::protobuf::EnumDescriptor*>& file_enums, Context& ctx);
// Writes message class declarations into .hpb.h.
//
// For each proto Foo, FooAccess and FooProxy/FooCProxy are generated
// that are exposed to users as Foo , Ptr<Foo> and Ptr<const Foo>.
void WriteMessageClassDeclarations(
const google::protobuf::Descriptor* descriptor,
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts,
const std::vector<const google::protobuf::EnumDescriptor*>& file_enums,
Context& ctx) {
if (IsMapEntryMessage(descriptor)) {
// Skip map entry generation. Low level accessors for maps are
// generated that don't require a separate map type.
return;
}
// Forward declaration of Proto Class for GCC handling of free friend method.
ctx.Emit(
{
Sub("class_name", ClassName(descriptor)),
Sub("model_access",
[&] { WriteModelAccessDeclaration(descriptor, ctx); })
.WithSuffix(";"),
Sub("public_decl",
[&] {
WriteModelPublicDeclaration(descriptor, file_exts, file_enums,
ctx);
})
.WithSuffix(";"),
Sub("cproxy_decl",
[&] { WriteModelCProxyDeclaration(descriptor, ctx); })
.WithSuffix(";"),
Sub("proxy_decl",
[&] { WriteModelProxyDeclaration(descriptor, ctx); })
.WithSuffix(";"),
Sub("default_instance",
[&] { WriteDefaultInstanceDefinitionHeader(descriptor, ctx); })
.WithSuffix(";"),
},
R"cc(
class $class_name$;
namespace internal {
$model_access$;
} // namespace internal
$public_decl$;
namespace internal {
$cproxy_decl$;
$proxy_decl$;
} // namespace internal
$default_instance$;
)cc");
}
void WriteModelAccessDeclaration(const google::protobuf::Descriptor* descriptor,
Context& ctx) {
ctx.Emit({Sub("class_name", ClassName(descriptor)),
Sub("qualified_class_name", QualifiedClassName(descriptor)),
Sub("upb_msg_name",
upb::generator::CApiMessageType(descriptor->full_name())),
Sub("field_accessors",
[&] { WriteFieldAccessorsInHeader(descriptor, ctx); })
.WithSuffix(";"),
Sub("oneof_accessors",
[&] { WriteOneofAccessorsInHeader(descriptor, ctx); })
.WithSuffix(";")},
R"cc(
class $class_name$Access {
public:
$class_name$Access() {}
$class_name$Access($upb_msg_name$* msg, upb_Arena* arena)
: msg_(msg), arena_(arena) {
assert(arena != nullptr);
} // NOLINT
$class_name$Access(const $upb_msg_name$* msg, upb_Arena* arena)
: msg_(const_cast<$upb_msg_name$*>(msg)), arena_(arena) {
assert(arena != nullptr);
} // NOLINT
$field_accessors$;
$oneof_accessors$;
private:
friend class $qualified_class_name$;
friend class $class_name$Proxy;
friend class $class_name$CProxy;
friend struct ::hpb::internal::PrivateAccess;
$upb_msg_name$* msg_;
upb_Arena* arena_;
};
)cc");
}
std::string UnderscoresToCamelCase(absl::string_view input,
bool cap_next_letter) {
std::string result;
for (size_t i = 0; i < input.size(); i++) {
if (absl::ascii_islower(input[i])) {
if (cap_next_letter) {
result += absl::ascii_toupper(input[i]);
} else {
result += input[i];
}
cap_next_letter = false;
} else if (absl::ascii_isupper(input[i])) {
// Capital letters are left as-is.
result += input[i];
cap_next_letter = false;
} else if (absl::ascii_isdigit(input[i])) {
result += input[i];
cap_next_letter = true;
} else {
cap_next_letter = true;
}
}
return result;
}
std::string FieldConstantName(const google::protobuf::FieldDescriptor* field) {
std::string field_name = UnderscoresToCamelCase(field->name(), true);
std::string result = absl::StrCat("k", field_name, "FieldNumber");
if (!field->is_extension() &&
field->containing_type()->FindFieldByCamelcaseName(
field->camelcase_name()) != field) {
// This field's camelcase name is not unique, add field number to make it
// unique.
absl::StrAppend(&result, "_", field->number());
}
return result;
}
void WriteConstFieldNumbers(Context& ctx,
const google::protobuf::Descriptor* descriptor) {
for (auto field : FieldRange(descriptor)) {
ctx.Emit({{"name", FieldConstantName(field)}, {"number", field->number()}},
"static constexpr ::uint32_t $name$ = $number$;\n");
}
ctx.Emit("\n\n");
}
void WriteModelPublicDeclaration(
const google::protobuf::Descriptor* descriptor,
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts,
const std::vector<const google::protobuf::EnumDescriptor*>& file_enums,
Context& ctx) {
ctx.Emit({{"class_name", ClassName(descriptor)},
{"qualified_class_name", QualifiedClassName(descriptor)}},
R"cc(
class $class_name$ final : private internal::$class_name$Access {
public:
using Access = internal::$class_name$Access;
using Proxy = internal::$class_name$Proxy;
using CProxy = internal::$class_name$CProxy;
$class_name$();
$class_name$(const $class_name$& from);
$class_name$& operator=(const $qualified_class_name$& from);
$class_name$(const CProxy& from);
$class_name$(const Proxy& from);
$class_name$& operator=(const CProxy& from);
$class_name$($class_name$&& m)
: Access(std::exchange(m.msg_, nullptr),
std::exchange(m.arena_, nullptr)),
owned_arena_(std::move(m.owned_arena_)) {}
$class_name$& operator=($class_name$&& m) {
msg_ = std::exchange(m.msg_, nullptr);
arena_ = std::exchange(m.arena_, nullptr);
owned_arena_ = std::move(m.owned_arena_);
return *this;
}
)cc");
WriteUsingAccessorsInHeader(descriptor, MessageClassType::kMessage, ctx);
WriteUsingEnumsInHeader(descriptor, file_enums, ctx);
WriteDefaultInstanceHeader(descriptor, ctx);
WriteExtensionIdentifiersInClassHeader(descriptor, file_exts, ctx);
if (descriptor->extension_range_count()) {
// for typetrait checking
ctx.Emit({{"class_name", ClassName(descriptor)}},
"using ExtendableType = $class_name$;\n");
}
// Note: free function friends that are templates such as ::hpb::Parse
// require explicit <$2> type parameter in declaration to be able to compile
// with gcc otherwise the compiler will fail with
// "has not been declared within namespace" error. Even though there is a
// namespace qualifier, cross namespace matching fails.
ctx.Emit(
R"cc(
static const upb_MiniTable* minitable();
)cc");
ctx.Emit("\n");
WriteConstFieldNumbers(ctx, descriptor);
ctx.Emit({{"class_name", ClassName(descriptor)},
{"c_api_msg_type",
upb::generator::CApiMessageType(descriptor->full_name())}},
R"cc(
private:
const upb_Message* msg() const { return UPB_UPCAST(msg_); }
upb_Message* msg() { return UPB_UPCAST(msg_); }
upb_Arena* arena() const { return arena_; }
$class_name$(upb_Message* msg, upb_Arena* arena) : $class_name$Access() {
msg_ = ($c_api_msg_type$*)msg;
arena_ = ::hpb::interop::upb::UnwrapArena(owned_arena_);
upb_Arena_Fuse(arena_, arena);
}
::hpb::Arena owned_arena_;
friend struct ::hpb::internal::PrivateAccess;
friend Proxy;
friend CProxy;
)cc");
ctx.Emit("};\n\n");
}
void WriteModelProxyDeclaration(const google::protobuf::Descriptor* descriptor,
Context& ctx) {
// Foo::Proxy.
ctx.Emit({{"class_name", ClassName(descriptor)}},
R"cc(
class $class_name$Proxy final
: private internal::$class_name$Access {
public:
$class_name$Proxy() = delete;
$class_name$Proxy(const $class_name$Proxy& m)
: internal::$class_name$Access() {
msg_ = m.msg_;
arena_ = m.arena_;
}
$class_name$Proxy($class_name$* m) : internal::$class_name$Access() {
msg_ = m->msg_;
arena_ = m->arena_;
}
$class_name$Proxy operator=(const $class_name$Proxy& m) {
msg_ = m.msg_;
arena_ = m.arena_;
return *this;
}
)cc");
WriteUsingAccessorsInHeader(descriptor, MessageClassType::kMessageProxy, ctx);
ctx.Emit("\n");
ctx.Emit(
{{"class_name", ClassName(descriptor)},
{"c_api_msg_type",
upb::generator::CApiMessageType(descriptor->full_name())},
{"qualified_class_name", QualifiedClassName(descriptor)}},
R"cc(
private:
upb_Message* msg() const { return UPB_UPCAST(msg_); }
upb_Arena* arena() const { return arena_; }
$class_name$Proxy(upb_Message* msg, upb_Arena* arena)
: internal::$class_name$Access(($c_api_msg_type$*)msg, arena) {}
friend $class_name$::Proxy(
::hpb::CreateMessage<$class_name$>(::hpb::Arena& arena));
friend $class_name$::Proxy(hpb::interop::upb::MakeHandle<$class_name$>(
upb_Message*, upb_Arena*));
friend struct ::hpb::internal::PrivateAccess;
friend class RepeatedFieldProxy;
friend class $class_name$CProxy;
friend class $class_name$Access;
friend class ::hpb::Ptr<$class_name$>;
friend class ::hpb::Ptr<const $class_name$>;
static const upb_MiniTable* minitable() { return $class_name$::minitable(); }
friend const upb_MiniTable* ::hpb::interop::upb::GetMiniTable<
$class_name$Proxy>(const $class_name$Proxy* message);
friend const upb_MiniTable* ::hpb::interop::upb::GetMiniTable<
$class_name$Proxy>(::hpb::Ptr<$class_name$Proxy> message);
friend upb_Arena* hpb::interop::upb::GetArena<$qualified_class_name$>(
$qualified_class_name$* message);
friend upb_Arena* hpb::interop::upb::GetArena<$qualified_class_name$>(
::hpb::Ptr<$qualified_class_name$> message);
static void Rebind($class_name$Proxy& lhs, const $class_name$Proxy& rhs) {
lhs.msg_ = rhs.msg_;
lhs.arena_ = rhs.arena_;
}
)cc");
ctx.Emit("};\n\n");
}
void WriteModelCProxyDeclaration(const google::protobuf::Descriptor* descriptor,
Context& ctx) {
// Foo::CProxy.
ctx.Emit({{"class_name", ClassName(descriptor)}},
R"cc(
class $class_name$CProxy final
: private internal::$class_name$Access {
public:
$class_name$CProxy() = delete;
$class_name$CProxy(const $class_name$* m)
: internal::$class_name$Access(
m->msg_, hpb::interop::upb::GetArena(m)) {}
$class_name$CProxy($class_name$Proxy m);
)cc");
WriteUsingAccessorsInHeader(descriptor, MessageClassType::kMessageCProxy,
ctx);
ctx.Emit({{"class_name", ClassName(descriptor)},
{"c_api_msg_type",
upb::generator::CApiMessageType(descriptor->full_name())}},
R"cc(
private:
using AsNonConst = $class_name$Proxy;
const upb_Message* msg() const { return UPB_UPCAST(msg_); }
upb_Arena* arena() const { return arena_; }
$class_name$CProxy(const upb_Message* msg, upb_Arena* arena)
: internal::$class_name$Access(($c_api_msg_type$*)msg,
arena){};
friend struct ::hpb::internal::PrivateAccess;
friend class RepeatedFieldProxy;
friend class ::hpb::Ptr<$class_name$>;
friend class ::hpb::Ptr<const $class_name$>;
static const upb_MiniTable* minitable() { return $class_name$::minitable(); }
friend const upb_MiniTable* ::hpb::interop::upb::GetMiniTable<
$class_name$CProxy>(const $class_name$CProxy* message);
friend const upb_MiniTable* ::hpb::interop::upb::GetMiniTable<
$class_name$CProxy>(::hpb::Ptr<$class_name$CProxy> message);
static void Rebind($class_name$CProxy& lhs, const $class_name$CProxy& rhs) {
lhs.msg_ = rhs.msg_;
lhs.arena_ = rhs.arena_;
}
)cc");
ctx.Emit("};\n\n");
}
void WriteDefaultInstanceHeader(const google::protobuf::Descriptor* message,
Context& ctx) {
if (message->options().map_entry()) {
return;
}
ctx.Emit({{"class_name", ClassName(message)}},
R"cc(
static ::hpb::Ptr<const $class_name$> default_instance();
)cc");
}
void WriteDefaultInstanceDefinitionHeader(const google::protobuf::Descriptor* message,
Context& ctx) {
if (message->options().map_entry()) {
return;
}
ctx.Emit(
{{"class_name", ClassName(message)},
{"size_class",
// Use log2 size class of message size to reduce the number of default
// instances created.
absl::bit_ceil(static_cast<size_t>(ctx.GetLayoutSize(message)))}},
R"cc(
inline ::hpb::Ptr<const $class_name$> $class_name$::default_instance() {
return ::hpb::interop::upb::MakeCHandle<$class_name$>(
::hpb::internal::backend::upb::DefaultInstance<
$size_class$>::msg(),
::hpb::internal::backend::upb::DefaultInstance<
$size_class$>::arena());
}
)cc");
}
void WriteMessageImplementation(
const google::protobuf::Descriptor* descriptor,
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts,
Context& ctx) {
bool message_is_map_entry = descriptor->options().map_entry();
if (!message_is_map_entry) {
// Constructor.
ctx.Emit(
{{"class_name", ClassName(descriptor)},
{"c_api_msg_type",
upb::generator::CApiMessageType(descriptor->full_name())},
{"minitable_var",
::upb::generator::MiniTableMessageVarName(descriptor->full_name())},
{"qualified_class_name", QualifiedClassName(descriptor)}},
R"cc(
$class_name$::$class_name$() : $class_name$Access() {
arena_ = ::hpb::interop::upb::UnwrapArena(owned_arena_);
msg_ = $c_api_msg_type$_new(arena_);
}
$class_name$::$class_name$(const $class_name$& from) : $class_name$Access() {
arena_ = ::hpb::interop::upb::UnwrapArena(owned_arena_);
msg_ = ($c_api_msg_type$*)::hpb::internal::DeepClone(
UPB_UPCAST(from.msg_), &$minitable_var$, arena_);
}
$class_name$::$class_name$(const CProxy& from) : $class_name$Access() {
arena_ = ::hpb::interop::upb::UnwrapArena(owned_arena_);
msg_ = ($c_api_msg_type$*)::hpb::internal::DeepClone(
::hpb::interop::upb::GetMessage(&from), &$minitable_var$,
arena_);
}
$class_name$::$class_name$(const Proxy& from)
: $class_name$(static_cast<const CProxy&>(from)) {}
internal::$class_name$CProxy::$class_name$CProxy($class_name$Proxy m)
: $class_name$Access() {
arena_ = m.arena_;
msg_ = ($c_api_msg_type$*)::hpb::interop::upb::GetMessage(&m);
}
$class_name$& $class_name$::operator=(const $qualified_class_name$& from) {
arena_ = ::hpb::interop::upb::UnwrapArena(owned_arena_);
msg_ = ($c_api_msg_type$*)::hpb::internal::DeepClone(
UPB_UPCAST(from.msg_), &$minitable_var$, arena_);
return *this;
}
$class_name$& $class_name$::operator=(const CProxy& from) {
arena_ = ::hpb::interop::upb::UnwrapArena(owned_arena_);
msg_ = ($c_api_msg_type$*)::hpb::internal::DeepClone(
::hpb::interop::upb::GetMessage(&from), &$minitable_var$,
arena_);
return *this;
}
)cc");
ctx.Emit("\n");
// Minitable
ctx.Emit({{"class_name", ClassName(descriptor)},
{"minitable_var", ::upb::generator::MiniTableMessageVarName(
descriptor->full_name())}},
R"cc(
const upb_MiniTable* $class_name$::minitable() {
return &$minitable_var$;
}
)cc");
ctx.Emit("\n");
}
WriteAccessorsInSource(descriptor, ctx);
}
void WriteExtensionIdentifiersInClassHeader(
const google::protobuf::Descriptor* message,
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts,
Context& ctx) {
for (auto* ext : file_exts) {
if (ext->extension_scope() &&
ext->extension_scope()->full_name() == message->full_name()) {
WriteExtensionIdentifierHeader(ext, ctx);
}
}
}
void WriteUsingEnumsInHeader(
const google::protobuf::Descriptor* message,
const std::vector<const google::protobuf::EnumDescriptor*>& file_enums,
Context& ctx) {
for (auto* enum_descriptor : file_enums) {
std::string enum_type_name = EnumTypeName(enum_descriptor);
std::string enum_resolved_type_name =
enum_descriptor->file()->package().empty() &&
enum_descriptor->containing_type() == nullptr
? absl::StrCat(kNoPackageNamePrefix,
ToCIdent(enum_descriptor->name()))
: enum_type_name;
if (enum_descriptor->containing_type() == nullptr ||
enum_descriptor->containing_type()->full_name() !=
message->full_name()) {
continue;
}
ctx.Emit({{"enum_name", enum_descriptor->name()}}, "using $enum_name$");
if (enum_descriptor->options().deprecated()) {
ctx.Emit({{"enum_name", enum_descriptor->name()}},
" ABSL_DEPRECATED(\"Proto enum $enum_name$\")");
}
ctx.Emit({{"enum_resolved_type_name", enum_resolved_type_name}},
" = $enum_resolved_type_name$;\n");
int value_count = enum_descriptor->value_count();
for (int i = 0; i < value_count; i++) {
ctx.Emit({{"enum_name", enum_descriptor->name()},
{"enum_value_name", enum_descriptor->value(i)->name()}},
"static constexpr $enum_name$ $enum_value_name$");
if (enum_descriptor->options().deprecated() ||
enum_descriptor->value(i)->options().deprecated()) {
ctx.Emit({{"enum_value_name", enum_descriptor->value(i)->name()}},
" ABSL_DEPRECATED(\"Proto enum value $enum_value_name$\") ");
}
ctx.Emit({{"enum_value_symbol",
EnumValueSymbolInNameSpace(enum_descriptor,
enum_descriptor->value(i))}},
" = $enum_value_symbol$;\n");
}
}
}
} // namespace hpb_generator
} // namespace protobuf
} // namespace google