| // 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 |