Implement 32-bit offset-based pointers for descriptor types.

Almost all the memory for the descriptors of a .proto file are allocated in a single flat allocation that is limited in size to 2GiB. As such, we can change crosslinking pointers to be 32-bit offset pointers.

This significantly reduces the size of the descriptor objects.

PiperOrigin-RevId: 958511623
This commit is contained in:
Samuel Benzaquen 2026-08-03 12:17:43 -07:00 committed by Copybara-Service
parent 51dedb854d
commit 4e7b3d8142
7 changed files with 553 additions and 179 deletions

View file

@ -111,6 +111,7 @@ google/protobuf/metadata.h
google/protobuf/metadata_lite.h
google/protobuf/micro_string.h
google/protobuf/naming_style.h
google/protobuf/offset_ptr.h
google/protobuf/option_interpreter.h
google/protobuf/os_macros_restore.inc
google/protobuf/os_macros_undef.inc

View file

@ -726,6 +726,7 @@ cc_library(
"inlined_string_field.cc",
"map.cc",
"message_lite.cc",
"offset_ptr.cc",
"parse_context.cc",
"raw_ptr.cc",
"repeated_field.cc",
@ -759,6 +760,7 @@ cc_library(
"map_type_handler.h",
"message_lite.h",
"metadata_lite.h",
"offset_ptr.h",
"parse_context.h",
"raw_ptr.h",
"repeated_field.h",
@ -988,6 +990,17 @@ cc_test(
],
)
cc_test(
name = "offset_ptr_test",
srcs = ["offset_ptr_test.cc"],
deps = [
":cc_test_protos",
":protobuf_lite",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)
# This target exposes the headers for the protobuf runtime, and additionally
# depends on the C++ well-known types and some other miscellaneous utilities.
# The purpose is to preserve compatibility with projects that do not yet comply

View file

@ -88,6 +88,7 @@
#include "google/protobuf/message.h"
#include "google/protobuf/message_lite.h"
#include "google/protobuf/naming_style.h"
#include "google/protobuf/offset_ptr.h"
#include "google/protobuf/option_interpreter.h"
#include "google/protobuf/parse_context.h"
#include "google/protobuf/port.h"
@ -421,7 +422,7 @@ class FlatAllocatorImpl {
// TODO: Remove the NULL terminators to save memory and simplify
// the code.
absl::optional<internal::DescriptorNames> CreateDescriptorNames(
absl::optional<internal::DescriptorNames::Input> CreateDescriptorNames(
std::initializer_list<absl::string_view> bytes,
std::initializer_list<size_t> sizes) {
for (size_t size : sizes) {
@ -439,7 +440,7 @@ class FlatAllocatorImpl {
memcpy(out, b.data(), b.size());
out += b.size();
}
auto res = internal::DescriptorNames(out);
auto res = internal::DescriptorNames::Input{out};
for (size_t size : sizes) {
uint16_t size16 = static_cast<uint16_t>(size);
memcpy(out, &size16, sizeof(size16));
@ -452,7 +453,7 @@ class FlatAllocatorImpl {
PlanArray<char>(internal::DescriptorNames::AllocationSizeForSimpleNames(
full_name_size));
}
absl::optional<internal::DescriptorNames> AllocateEntityNames(
absl::optional<internal::DescriptorNames::Input> AllocateEntityNames(
absl::string_view scope, absl::string_view name) {
static constexpr absl::string_view kNullChar("\0", 1);
if (scope.empty()) {
@ -465,7 +466,7 @@ class FlatAllocatorImpl {
}
}
internal::DescriptorNames AllocatePlaceholderNames(
internal::DescriptorNames::Input AllocatePlaceholderNames(
absl::string_view full_name, size_t name_size) {
static constexpr absl::string_view kNullChar("\0", 1);
auto out = CreateDescriptorNames({full_name, kNullChar},
@ -533,7 +534,7 @@ class FlatAllocatorImpl {
PlanArray<char>(total_bytes);
}
absl::optional<internal::DescriptorNames> AllocateFieldNames(
absl::optional<internal::DescriptorNames::Input> AllocateFieldNames(
const absl::string_view name, const absl::string_view scope,
const std::string* opt_json_name) {
ABSL_CHECK(has_allocated());
@ -856,38 +857,11 @@ struct ParentNameQueryBase {
}
};
// A 32-bit "offset" based pointer used for hash tables below.
// The base pointer is the base of the FlatAllocation, which is the same for
// the objects in a single FileDescriptorTables (except the ones allocated later
// on demand, like unknown_enum_values_by_number_).
// This halves the size of the tables in 64-bit builds.
template <typename T>
struct OffsetT {
OffsetT(T* ptr, absl::string_view flat_buffer) {
ptrdiff_t diff = reinterpret_cast<const char*>(ptr) -
reinterpret_cast<const char*>(flat_buffer.data());
// Verify the pointer is actually in bounds of the buffer.
ABSL_DCHECK(static_cast<const void*>(ptr) >= flat_buffer.data() &&
static_cast<const void*>(ptr) <
flat_buffer.data() + flat_buffer.size());
ABSL_DCHECK_GE(diff, 0);
ABSL_DCHECK_LE(diff, std::numeric_limits<uint32_t>::max());
value = static_cast<uint32_t>(diff);
}
T* Resolve(const void* base_ptr) const {
return const_cast<T*>(reinterpret_cast<const T*>(
reinterpret_cast<const char*>(base_ptr) + value));
}
uint32_t value;
};
template <typename T>
const T& ResolveSymbol(const T& v, const void*) {
return v;
}
Symbol ResolveSymbol(OffsetT<const internal::SymbolBase> v,
Symbol ResolveSymbol(internal::BasePointer<const internal::SymbolBase, false> v,
const void* base_ptr) {
return Symbol(v.Resolve(base_ptr));
}
@ -916,7 +890,8 @@ struct SymbolByParentHash {
const void* base_ptr;
size_t operator()(OffsetT<const internal::SymbolBase> v) const {
size_t operator()(
internal::BasePointer<const internal::SymbolBase, false> v) const {
return (*this)(Symbol(v.Resolve(base_ptr)));
}
@ -930,8 +905,9 @@ struct SymbolByParentEq {
const void* base_ptr;
bool operator()(OffsetT<const internal::SymbolBase> offset,
const ParentNameFieldQuery& query) const {
bool operator()(
internal::BasePointer<const internal::SymbolBase, false> offset,
const ParentNameFieldQuery& query) const {
Symbol symbol = ResolveSymbol(offset, base_ptr);
const FieldDescriptor* field = symbol.field_descriptor();
return field != nullptr && !field->is_extension() &&
@ -945,9 +921,9 @@ struct SymbolByParentEq {
ResolveSymbol(b, base_ptr).parent_name_key();
}
};
using SymbolsByParentSet =
absl::flat_hash_set<OffsetT<const internal::SymbolBase>, SymbolByParentHash,
SymbolByParentEq>;
using SymbolsByParentSet = absl::flat_hash_set<
internal::BasePointer<const internal::SymbolBase, false>,
SymbolByParentHash, SymbolByParentEq>;
template <typename Projection>
struct ProjectedHash {
@ -1059,8 +1035,8 @@ using EnumValuesByNumberSet =
ParentNumberEq>;
template <typename T>
std::pair<const void*, int> ObjectToParentNumber(OffsetT<T> offset,
const void* base_ptr) {
std::pair<const void*, int> ObjectToParentNumber(
internal::BasePointer<T, false> offset, const void* base_ptr) {
return ObjectToParentNumber(offset.Resolve(base_ptr));
}
@ -1088,11 +1064,11 @@ struct ParentNumberEqOffsetPtr {
};
using EnumValuesByNumberSetOffsetPtr =
absl::flat_hash_set<OffsetT<const EnumValueDescriptor>,
absl::flat_hash_set<internal::BasePointer<const EnumValueDescriptor, false>,
ParentNumberHashOffsetPtr, ParentNumberEqOffsetPtr>;
using FieldsByNumberSet =
absl::flat_hash_set<OffsetT<const FieldDescriptor>,
absl::flat_hash_set<internal::BasePointer<const FieldDescriptor, false>,
ParentNumberHashOffsetPtr, ParentNumberEqOffsetPtr>;
// This is a map rather than a hash-map, since we use it to iterate
@ -1313,7 +1289,8 @@ class FileDescriptorTables {
const FileDescriptorTables* tables);
void FieldsByCamelcaseNamesLazyInitInternal() const;
Symbol Resolve(OffsetT<const internal::SymbolBase> v) const {
Symbol Resolve(
internal::BasePointer<const internal::SymbolBase, false> v) const {
return Symbol(v.Resolve(flat_buffer_.data()));
}
@ -1931,7 +1908,7 @@ FileDescriptorTables::FindEnumValueByNumberCreatingIfUnknown(
absl::StrCat(parent->full_name(), ".", enum_value_name));
result->number_ = number;
result->type_ = parent;
result->options_ = &EnumValueOptions::default_instance();
result->options_ = nullptr;
unknown_enum_values_by_number_.insert(result);
return result;
}
@ -1973,7 +1950,8 @@ bool FileDescriptorTables::AddAliasUnderParent(const void* parent,
ABSL_DCHECK_EQ(name, symbol.parent_name_key().second);
ABSL_DCHECK_EQ(parent, symbol.parent_name_key().first);
return symbols_by_parent_
.insert(OffsetT<const internal::SymbolBase>(symbol.ptr(), flat_buffer_))
.insert(internal::BasePointer<const internal::SymbolBase, false>(
symbol.ptr(), flat_buffer_.data()))
.second;
}
@ -2002,7 +1980,8 @@ bool FileDescriptorTables::AddFieldByNumber(FieldDescriptor* field) {
}
return fields_by_number_
.insert(OffsetT<const FieldDescriptor>(field, flat_buffer_))
.insert(internal::BasePointer<const FieldDescriptor, false>(
field, flat_buffer_.data()))
.second;
}
@ -2014,7 +1993,8 @@ bool FileDescriptorTables::AddEnumValueByNumber(EnumValueDescriptor* value) {
static_cast<int64_t>(base) + value->type()->sequential_value_limit_)
return true;
return enum_values_by_number_
.insert(OffsetT<const EnumValueDescriptor>(value, flat_buffer_))
.insert(internal::BasePointer<const EnumValueDescriptor, false>(
value, flat_buffer_.data()))
.second;
}
@ -2864,6 +2844,18 @@ bool DescriptorPool::TryFindExtensionInFallbackDatabase(
// ===================================================================
#define PROTOBUF_DEFINE_OPTIONS_ACCESSOR(CLASS, TYPE) \
const TYPE& CLASS::options() const { return *options_; }
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(Descriptor, MessageOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(FieldDescriptor, FieldOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(OneofDescriptor, OneofOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(EnumDescriptor, EnumOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(EnumValueDescriptor, EnumValueOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(ServiceDescriptor, ServiceOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(MethodDescriptor, MethodOptions)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(FileDescriptor, FileOptions)
bool FieldDescriptor::is_map_message_type() const {
return message_type()->options().map_entry();
}
@ -3436,9 +3428,10 @@ std::string FileDescriptor::DebugStringWithOptions(
comment_printer.AddPreComment(&contents);
absl::flat_hash_set<int> public_dependencies(
public_dependencies_, public_dependencies_ + public_dependency_count_);
public_dependencies_.get(),
public_dependencies_ + public_dependency_count_);
absl::flat_hash_set<int> weak_dependencies(
weak_dependencies_, weak_dependencies_ + weak_dependency_count_);
weak_dependencies_.get(), weak_dependencies_ + weak_dependency_count_);
for (int i = 0; i < dependency_count(); i++) {
if (public_dependencies.contains(i)) {
@ -4755,10 +4748,10 @@ Symbol DescriptorPool::NewPlaceholderWithMutexHeld(
EnumDescriptor* placeholder_enum = &placeholder_file->enum_types_[0];
memset(static_cast<void*>(placeholder_enum), 0, sizeof(*placeholder_enum));
placeholder_enum->all_names_ = alloc.AllocatePlaceholderNames(
placeholder_full_name, placeholder_name.size());
placeholder_enum->all_names_.SetPayload(alloc.AllocatePlaceholderNames(
placeholder_full_name, placeholder_name.size()));
placeholder_enum->file_ = placeholder_file;
placeholder_enum->options_ = &EnumOptions::default_instance();
placeholder_enum->options_ = nullptr;
placeholder_enum->proto_features_ = &FeatureSet::default_instance();
placeholder_enum->merged_features_ = &FeatureSet::default_instance();
placeholder_enum->is_placeholder_ = true;
@ -4783,7 +4776,7 @@ Symbol DescriptorPool::NewPlaceholderWithMutexHeld(
placeholder_value->number_ = 0;
placeholder_value->type_ = placeholder_enum;
placeholder_value->options_ = &EnumValueOptions::default_instance();
placeholder_value->options_ = nullptr;
return Symbol(placeholder_enum);
} else {
@ -4794,10 +4787,10 @@ Symbol DescriptorPool::NewPlaceholderWithMutexHeld(
memset(static_cast<void*>(placeholder_message), 0,
sizeof(*placeholder_message));
placeholder_message->all_names_ = alloc.AllocatePlaceholderNames(
placeholder_full_name, placeholder_name.size());
placeholder_message->all_names_.SetPayload(alloc.AllocatePlaceholderNames(
placeholder_full_name, placeholder_name.size()));
placeholder_message->file_ = placeholder_file;
placeholder_message->options_ = &MessageOptions::default_instance();
placeholder_message->options_ = nullptr;
placeholder_message->proto_features_ = &FeatureSet::default_instance();
placeholder_message->merged_features_ = &FeatureSet::default_instance();
placeholder_message->is_placeholder_ = true;
@ -4844,7 +4837,7 @@ FileDescriptor* DescriptorPool::NewPlaceholderFileWithMutexHeld(
placeholder->name_ = alloc.AllocateStrings(name);
placeholder->package_ = &internal::GetEmptyString();
placeholder->pool_ = this;
placeholder->options_ = &FileOptions::default_instance();
placeholder->options_ = nullptr;
placeholder->proto_features_ = &FeatureSet::default_instance();
placeholder->merged_features_ = &FeatureSet::default_instance();
placeholder->tables_ = &FileDescriptorTables::GetEmptyInstance();
@ -5207,20 +5200,20 @@ void internal::DescriptorBuilder::PostProcessFieldFeatures(
if (field.options_->has_ctype()) {
field.legacy_proto_ctype_ = field.options_->ctype();
const_cast<FieldOptions*>( // NOLINT(google3-runtime-proto-const-cast)
field.options_)
field.options_.get())
->clear_ctype();
}
}
// A common pattern: We want to convert a repeated field in the descriptor
// to an array of values, calling some method to build each value.
#define BUILD_ARRAY(INPUT, OUTPUT, NAME, METHOD, PARENT) \
OUTPUT->NAME##_count_ = INPUT.NAME##_size(); \
OUTPUT->NAME##s_ = alloc.AllocateArray< \
typename std::remove_pointer<decltype(OUTPUT->NAME##s_)>::type>( \
INPUT.NAME##_size()); \
for (int i = 0; i < INPUT.NAME##_size(); i++) { \
METHOD(INPUT.NAME(i), PARENT, OUTPUT->NAME##s_ + i, alloc); \
#define BUILD_ARRAY(INPUT, OUTPUT, NAME, METHOD, PARENT) \
OUTPUT->NAME##_count_ = INPUT.NAME##_size(); \
OUTPUT->NAME##s_ = \
alloc.AllocateArray<decltype(OUTPUT->NAME##s_)::value_type>( \
INPUT.NAME##_size()); \
for (int i = 0; i < INPUT.NAME##_size(); i++) { \
METHOD(INPUT.NAME(i), PARENT, OUTPUT->NAME##s_ + i, alloc); \
}
PROTOBUF_NOINLINE void internal::DescriptorBuilder::AddRecursiveImportError(
@ -5840,9 +5833,8 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
auto cleanup = DisableTracking();
internal::VisitDescriptors(
*result, proto, [&](const auto& descriptor, const auto& proto) {
using OptionsT =
typename std::remove_const<typename std::remove_pointer<
decltype(descriptor.options_)>::type>::type;
using OptionsT = std::remove_const_t<
std::decay_t<decltype(*descriptor.options_)>>;
using DescriptorT =
typename std::remove_const<typename std::remove_reference<
decltype(descriptor)>::type>::type;
@ -5850,7 +5842,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
ResolveFeatures(
proto, const_cast<DescriptorT*>(&descriptor),
const_cast< // NOLINT(google3-runtime-proto-const-cast)
OptionsT*>(descriptor.options_),
OptionsT*>(+descriptor.options_),
alloc);
});
}
@ -6002,7 +5994,8 @@ void internal::DescriptorBuilder::BuildMessage(const DescriptorProto& proto,
internal::FlatAllocator& alloc) {
const absl::string_view scope =
(parent == nullptr) ? file_->package() : parent->full_name();
result->all_names_ = AllocateNameStrings(scope, proto.name(), proto, alloc);
result->all_names_.SetPayload(
AllocateNameStrings(scope, proto.name(), proto, alloc));
ValidateSymbolName(proto.name(), result->full_name(), proto);
result->file_ = file_;
@ -6046,7 +6039,6 @@ void internal::DescriptorBuilder::BuildMessage(const DescriptorProto& proto,
if (recursion_depth_ <= 0) {
AddError(result->full_name(), proto, DescriptorPool::ErrorCollector::OTHER,
"Reached maximum recursion limit for nested messages.");
result->nested_types_ = nullptr;
result->nested_type_count_ = 0;
return;
}
@ -6287,12 +6279,13 @@ void internal::DescriptorBuilder::BuildFieldOrExtension(
if (auto names = alloc.AllocateFieldNames(
proto.name(), scope,
proto.has_json_name() ? &proto.json_name() : nullptr)) {
result->all_names_ = *names;
result->all_names_.SetPayload(*names);
} else {
AddError(
scope.empty() ? proto.name() : absl::StrCat(scope, ".", proto.name()),
proto, DescriptorPool::ErrorCollector::NAME, "Name too long.");
result->all_names_ = alloc.AllocateEntityNames("", "unknown").value();
result->all_names_.SetPayload(
alloc.AllocateEntityNames("", "unknown").value());
}
ValidateSymbolName(proto.name(), result->full_name(), proto);
@ -6637,15 +6630,14 @@ void internal::DescriptorBuilder::BuildOneof(const OneofDescriptorProto& proto,
Descriptor* parent,
OneofDescriptor* result,
internal::FlatAllocator& alloc) {
result->all_names_ =
AllocateNameStrings(parent->full_name(), proto.name(), proto, alloc);
result->all_names_.SetPayload(
AllocateNameStrings(parent->full_name(), proto.name(), proto, alloc));
ValidateSymbolName(proto.name(), result->full_name(), proto);
result->containing_type_ = parent;
// We need to fill these in later.
result->field_count_ = 0;
result->fields_ = nullptr;
// Copy options.
AllocateOptions(proto, result, OneofDescriptorProto::kOptionsFieldNumber,
@ -6813,7 +6805,8 @@ void internal::DescriptorBuilder::BuildEnum(const EnumDescriptorProto& proto,
const absl::string_view scope =
(parent == nullptr) ? file_->package() : parent->full_name();
result->all_names_ = AllocateNameStrings(scope, proto.name(), proto, alloc);
result->all_names_.SetPayload(
AllocateNameStrings(scope, proto.name(), proto, alloc));
ValidateSymbolName(proto.name(), result->full_name(), proto);
result->file_ = file_;
result->containing_type_ = parent;
@ -6995,8 +6988,8 @@ void internal::DescriptorBuilder::BuildEnumValue(
void internal::DescriptorBuilder::BuildService(
const ServiceDescriptorProto& proto, const void* /* dummy */,
ServiceDescriptor* result, internal::FlatAllocator& alloc) {
result->all_names_ =
AllocateNameStrings(file_->package(), proto.name(), proto, alloc);
result->all_names_.SetPayload(
AllocateNameStrings(file_->package(), proto.name(), proto, alloc));
result->file_ = file_;
ValidateSymbolName(proto.name(), result->full_name(), proto);
@ -7014,8 +7007,8 @@ void internal::DescriptorBuilder::BuildMethod(
const MethodDescriptorProto& proto, const ServiceDescriptor* parent,
MethodDescriptor* result, internal::FlatAllocator& alloc) {
result->service_ = parent;
result->all_names_ =
AllocateNameStrings(parent->full_name(), proto.name(), proto, alloc);
result->all_names_.SetPayload(
AllocateNameStrings(parent->full_name(), proto.name(), proto, alloc));
ValidateSymbolName(proto.name(), result->full_name(), proto);

View file

@ -58,6 +58,7 @@
#include "absl/synchronization/mutex.h"
#include "google/protobuf/descriptor_lite.h" // IWYU pragma: export
#include "google/protobuf/extension_set.h"
#include "google/protobuf/offset_ptr.h"
#include "google/protobuf/port.h"
// Must be included last.
@ -267,7 +268,18 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED DescriptorNames {
public:
// Uninitialized, to support `= default` of descriptor types.
DescriptorNames() = default;
explicit DescriptorNames(const char* payload) : payload_(payload) {}
// We can't construct temporaries because of the offset_ptr, so avoid this.
#ifndef SWIG
DescriptorNames(const DescriptorNames&) = delete;
DescriptorNames& operator=(const DescriptorNames&) = delete;
#endif
struct Input {
const char* value;
};
void SetPayload(Input value) { payload_ = value.value; }
// The full name is just before `payload_`, and the name is the suffix of it.
// We don't need a special offset for them.
@ -309,7 +321,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED DescriptorNames {
return absl::string_view(payload_ - offset, size);
}
const char* payload_;
internal::NonnullOffsetPtr<const char> payload_;
};
class FlatAllocator;
@ -822,25 +834,20 @@ class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
// sequentially numbered fields in a message.
uint16_t sequential_field_limit_;
int field_count_;
internal::DescriptorNames all_names_;
const FileDescriptor* file_;
const Descriptor* containing_type_;
const MessageOptions* options_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
// These arrays are separated from their sizes to minimize padding on 64-bit.
FieldDescriptor* fields_;
OneofDescriptor* oneof_decls_;
Descriptor* nested_types_;
EnumDescriptor* enum_types_;
ExtensionRange* extension_ranges_;
FieldDescriptor* extensions_;
ReservedRange* reserved_ranges_;
const std::string** reserved_names_;
internal::NonnullOffsetPtr<const FileDescriptor> file_;
internal::NullableOffsetPtr<const Descriptor> containing_type_;
internal::OffsetProtoPtr<const MessageOptions> options_;
internal::NonnullOffsetPtr<FieldDescriptor> fields_;
internal::NonnullOffsetPtr<OneofDescriptor> oneof_decls_;
internal::NonnullOffsetPtr<Descriptor> nested_types_;
internal::NonnullOffsetPtr<EnumDescriptor> enum_types_;
internal::NonnullOffsetPtr<ExtensionRange> extension_ranges_;
internal::NonnullOffsetPtr<FieldDescriptor> extensions_;
internal::NonnullOffsetPtr<ReservedRange> reserved_ranges_;
internal::NonnullOffsetPtr<const std::string*> reserved_names_;
int field_count_;
int oneof_decl_count_;
int real_oneof_decl_count_;
int nested_type_count_;
@ -850,6 +857,9 @@ class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
int reserved_range_count_;
int reserved_name_count_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
// IMPORTANT: If you add a new field, make sure to search for all instances
// of Allocate<Descriptor>() and AllocateArray<Descriptor>() in descriptor.cc
// and update them to initialize the field.
@ -867,7 +877,8 @@ class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
friend class SymbolChecker;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(Descriptor, 160);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(Descriptor, 112);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(Descriptor::ExtensionRange, 40);
// Describes a single field of a message. To get the descriptor for a given
// field, first get the Descriptor for the message in which it is defined,
@ -1273,6 +1284,9 @@ class PROTOBUF_EXPORT FieldDescriptor : private internal::SymbolBase,
CppRepeatedType CalculateCppRepeatedType() const;
static void TypeOnceInit(const FieldDescriptor* to_init);
void InternalTypeOnceInit() const;
bool has_default_value_ : 1;
bool proto3_optional_ : 1;
// Whether the user has specified the json_name field option in the .proto
@ -1308,25 +1322,32 @@ class PROTOBUF_EXPORT FieldDescriptor : private internal::SymbolBase,
// and its indices above.
int number_;
internal::DescriptorNames all_names_;
const FileDescriptor* file_;
internal::NonnullOffsetPtr<const FileDescriptor> file_;
union {
internal::NonnullOffsetPtr<const OneofDescriptor> containing_oneof;
internal::NullableOffsetPtr<const Descriptor> extension_scope;
} scope_;
internal::OffsetProtoPtr<const FieldOptions> options_;
// The once_flag is followed by a NUL terminated string for the type name and
// enum default value (or empty string if no default enum).
// Allocated separately, so no OffsetPtr.
absl::once_flag* type_once_;
static void TypeOnceInit(const FieldDescriptor* to_init);
void InternalTypeOnceInit() const;
// Extensions' containing type can be from a different file, so no OffsetPtr.
const Descriptor* containing_type_;
union {
const OneofDescriptor* containing_oneof;
const Descriptor* extension_scope;
} scope_;
union {
// Types can be from different files so no OffsetPtr.
mutable const Descriptor* message_type;
mutable const EnumDescriptor* enum_type;
} type_descriptor_;
const FieldOptions* options_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
// IMPORTANT: If you add a new field, make sure to search for all instances
// of Allocate<FieldDescriptor>() and AllocateArray<FieldDescriptor>() in
// descriptor.cc and update them to initialize the field.
@ -1362,7 +1383,7 @@ class PROTOBUF_EXPORT FieldDescriptor : private internal::SymbolBase,
friend class OneofDescriptor;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(FieldDescriptor, 88);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(FieldDescriptor, 72);
// Describes a oneof defined in a message type.
class PROTOBUF_EXPORT OneofDescriptor : private internal::SymbolBase {
@ -1451,11 +1472,12 @@ class PROTOBUF_EXPORT OneofDescriptor : private internal::SymbolBase {
int field_count_;
internal::DescriptorNames all_names_;
const Descriptor* containing_type_;
const OneofOptions* options_;
internal::NonnullOffsetPtr<const Descriptor> containing_type_;
internal::OffsetProtoPtr<const OneofOptions> options_;
internal::NonnullOffsetPtr<const FieldDescriptor> fields_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
const FieldDescriptor* fields_;
// IMPORTANT: If you add a new field, make sure to search for all instances
// of Allocate<OneofDescriptor>() and AllocateArray<OneofDescriptor>()
@ -1469,7 +1491,7 @@ class PROTOBUF_EXPORT OneofDescriptor : private internal::SymbolBase {
friend class Reflection;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(OneofDescriptor, 56);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(OneofDescriptor, 40);
// Describes an enum type defined in a .proto file. To get the EnumDescriptor
// for a generated enum type, call TypeName_descriptor(). Use DescriptorPool
@ -1663,20 +1685,20 @@ class PROTOBUF_EXPORT EnumDescriptor : private internal::SymbolBase {
// sequentially numbered labels in an enum.
int16_t sequential_value_limit_;
int value_count_;
internal::DescriptorNames all_names_;
const FileDescriptor* file_;
const Descriptor* containing_type_;
const EnumOptions* options_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
EnumValueDescriptor* values_;
internal::NonnullOffsetPtr<const FileDescriptor> file_;
internal::NullableOffsetPtr<const Descriptor> containing_type_;
internal::OffsetProtoPtr<const EnumOptions> options_;
internal::NonnullOffsetPtr<EnumValueDescriptor> values_;
internal::NonnullOffsetPtr<EnumDescriptor::ReservedRange> reserved_ranges_;
internal::NonnullOffsetPtr<const std::string*> reserved_names_;
int value_count_;
int reserved_range_count_;
int reserved_name_count_;
EnumDescriptor::ReservedRange* reserved_ranges_;
const std::string** reserved_names_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
// IMPORTANT: If you add a new field, make sure to search for all instances
// of Allocate<EnumDescriptor>() and AllocateArray<EnumDescriptor>() in
@ -1694,7 +1716,7 @@ class PROTOBUF_EXPORT EnumDescriptor : private internal::SymbolBase {
friend class Reflection;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(EnumDescriptor, 88);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(EnumDescriptor, 64);
// Describes an individual enum constant of a particular type. To get the
// EnumValueDescriptor for a given enum value, first get the EnumDescriptor
@ -1789,9 +1811,11 @@ class PROTOBUF_EXPORT EnumValueDescriptor : private internal::SymbolBaseN<0>,
// We keep the old-style std::string payload to support `NameOfEnumAsString`
// Once we start migrating Enum_Name functions to string_view we can switch
// this too.
const std::string* all_names_;
internal::NonnullOffsetPtr<const std::string> all_names_;
internal::OffsetProtoPtr<const EnumValueOptions> options_;
// Type can be from a different allocation when creating enums via
// `FindEnumValueByNumberCreatingIfUnknown`.
const EnumDescriptor* type_;
const EnumValueOptions* options_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
// IMPORTANT: If you add a new field, make sure to search for all instances
@ -1807,7 +1831,7 @@ class PROTOBUF_EXPORT EnumValueDescriptor : private internal::SymbolBaseN<0>,
friend class Reflection;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(EnumValueDescriptor, 48);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(EnumValueDescriptor, 40);
// Describes an RPC service. Use DescriptorPool to construct your own
// descriptors.
@ -1897,12 +1921,14 @@ class PROTOBUF_EXPORT ServiceDescriptor : private internal::SymbolBase {
void GetLocationPath(std::vector<int>* output) const;
internal::DescriptorNames all_names_;
const FileDescriptor* file_;
const ServiceOptions* options_;
internal::NonnullOffsetPtr<const FileDescriptor> file_;
internal::OffsetProtoPtr<const ServiceOptions> options_;
internal::NonnullOffsetPtr<MethodDescriptor> methods_;
int method_count_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
MethodDescriptor* methods_;
int method_count_;
// IMPORTANT: If you add a new field, make sure to search for all instances
// of Allocate<ServiceDescriptor>() and AllocateArray<ServiceDescriptor>() in
// descriptor.cc and update them to initialize the field.
@ -1914,7 +1940,7 @@ class PROTOBUF_EXPORT ServiceDescriptor : private internal::SymbolBase {
friend class MethodDescriptor;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(ServiceDescriptor, 64);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(ServiceDescriptor, 40);
// Describes an individual service method. To obtain a MethodDescriptor given
// a service, first get its ServiceDescriptor, then call
@ -2007,10 +2033,13 @@ class PROTOBUF_EXPORT MethodDescriptor : private internal::SymbolBase {
bool client_streaming_;
bool server_streaming_;
internal::DescriptorNames all_names_;
const ServiceDescriptor* service_;
internal::NonnullOffsetPtr<const ServiceDescriptor> service_;
internal::OffsetProtoPtr<const MethodOptions> options_;
mutable internal::LazyDescriptor input_type_;
mutable internal::LazyDescriptor output_type_;
const MethodOptions* options_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
// IMPORTANT: If you add a new field, make sure to search for all instances
@ -2023,7 +2052,7 @@ class PROTOBUF_EXPORT MethodDescriptor : private internal::SymbolBase {
friend class ServiceDescriptor;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(MethodDescriptor, 80);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(MethodDescriptor, 64);
// Describes a whole .proto file. To get the FileDescriptor for a compiled-in
// file, get the descriptor for something defined in that file and call
@ -2199,19 +2228,6 @@ class PROTOBUF_EXPORT FileDescriptor : private internal::SymbolBase {
friend class FileDescriptorLegacy;
typedef FileOptions OptionsType;
bool is_placeholder_;
// Indicates the FileDescriptor is completed building. Used to verify
// that type accessor functions that can possibly build a dependent file
// aren't called during the process of building the file.
bool finished_building_;
// This one is here to fill the padding.
int extension_count_;
const std::string* name_;
const std::string* package_;
const DescriptorPool* pool_;
Edition edition_;
// Returns edition of this file. For legacy proto2/proto3 files, special
// EDITION_PROTO2 and EDITION_PROTO3 values are used.
Edition edition() const;
@ -2223,12 +2239,16 @@ class PROTOBUF_EXPORT FileDescriptor : private internal::SymbolBase {
const FeatureSet& features() const { return *merged_features_; }
friend class internal::InternalFeatureHelper;
// dependencies_once_ contain a once_flag followed by N NUL terminated
// strings. Dependencies that do not need to be loaded will be empty. ie just
// {'\0'}
absl::once_flag* dependencies_once_;
static void DependenciesOnceInit(const FileDescriptor* to_init);
void InternalDependenciesOnceInit() const;
bool is_placeholder_;
// Indicates the FileDescriptor is completed building. Used to verify
// that type accessor functions that can possibly build a dependent file
// aren't called during the process of building the file.
bool finished_building_;
// This one is here to fill the padding.
int extension_count_;
internal::NonnullOffsetPtr<const std::string> name_;
Edition edition_;
// These are arranged to minimize padding on 64-bit.
int dependency_count_;
@ -2239,21 +2259,34 @@ class PROTOBUF_EXPORT FileDescriptor : private internal::SymbolBase {
int enum_type_count_;
int service_count_;
mutable const FileDescriptor** dependencies_;
int* public_dependencies_;
int* weak_dependencies_;
absl::string_view* option_dependencies_;
internal::NonnullOffsetPtr<const FileDescriptor*> dependencies_;
internal::NonnullOffsetPtr<int> public_dependencies_;
internal::NonnullOffsetPtr<int> weak_dependencies_;
internal::NonnullOffsetPtr<absl::string_view> option_dependencies_;
internal::NonnullOffsetPtr<Descriptor> message_types_;
internal::NonnullOffsetPtr<EnumDescriptor> enum_types_;
internal::NonnullOffsetPtr<ServiceDescriptor> services_;
internal::NonnullOffsetPtr<FieldDescriptor> extensions_;
internal::OffsetProtoPtr<const FileOptions> options_;
internal::OffsetProtoPtr<const SourceCodeInfo> source_code_info_;
const std::string* package_;
// dependencies_once_ contain a once_flag followed by N NUL terminated
// strings. Dependencies that do not need to be loaded will be empty. ie just
// {'\0'}
absl::once_flag* dependencies_once_;
static void DependenciesOnceInit(const FileDescriptor* to_init);
void InternalDependenciesOnceInit() const;
const DescriptorPool* pool_;
Descriptor* message_types_;
EnumDescriptor* enum_types_;
ServiceDescriptor* services_;
FieldDescriptor* extensions_;
const FileOptions* options_;
const FeatureSet* proto_features_;
const FeatureSet* merged_features_;
const FileDescriptorTables* tables_;
const SourceCodeInfo* source_code_info_;
// IMPORTANT: If you add a new field, make sure to search for all instances
// of Allocate<FileDescriptor>() and AllocateArray<FileDescriptor>() in
@ -2272,7 +2305,7 @@ class PROTOBUF_EXPORT FileDescriptor : private internal::SymbolBase {
friend class ServiceDescriptor;
};
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(FileDescriptor, 184);
PROTOBUF_INTERNAL_CHECK_CLASS_SIZE(FileDescriptor, 136);
#ifndef SWIG
enum class ExtDeclEnforcementLevel : uint8_t {
@ -2903,9 +2936,6 @@ class PROTOBUF_EXPORT DescriptorPool {
return FIELD##s_ + index; \
}
#define PROTOBUF_DEFINE_OPTIONS_ACCESSOR(CLASS, TYPE) \
inline const TYPE& CLASS::options() const { return *options_; }
PROTOBUF_DEFINE_NAME_ACCESSOR(Descriptor)
PROTOBUF_DEFINE_ACCESSOR(Descriptor, file, const FileDescriptor*)
PROTOBUF_DEFINE_ACCESSOR(Descriptor, containing_type, const Descriptor*)
@ -2936,7 +2966,6 @@ PROTOBUF_DEFINE_ARRAY_ACCESSOR(Descriptor, reserved_range,
const Descriptor::ReservedRange*)
PROTOBUF_DEFINE_ACCESSOR(Descriptor, reserved_name_count, int)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(Descriptor, MessageOptions)
PROTOBUF_DEFINE_ACCESSOR(Descriptor, is_placeholder, bool)
PROTOBUF_DEFINE_NAME_ACCESSOR(FieldDescriptor)
@ -2944,7 +2973,6 @@ PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, file, const FileDescriptor*)
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, number, int)
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, is_extension, bool)
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, containing_type, const Descriptor*)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(FieldDescriptor, FieldOptions)
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, has_default_value, bool)
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, has_json_name, bool)
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, default_value_int32_t, int32_t)
@ -2960,7 +2988,6 @@ PROTOBUF_DEFINE_NAME_ACCESSOR(OneofDescriptor)
PROTOBUF_DEFINE_ACCESSOR(OneofDescriptor, containing_type, const Descriptor*)
PROTOBUF_DEFINE_ACCESSOR(OneofDescriptor, field_count, int)
PROTOBUF_DEFINE_ARRAY_ACCESSOR(OneofDescriptor, field, const FieldDescriptor*)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(OneofDescriptor, OneofOptions)
PROTOBUF_DEFINE_NAME_ACCESSOR(EnumDescriptor)
PROTOBUF_DEFINE_ACCESSOR(EnumDescriptor, file, const FileDescriptor*)
@ -2968,7 +2995,6 @@ PROTOBUF_DEFINE_ACCESSOR(EnumDescriptor, containing_type, const Descriptor*)
PROTOBUF_DEFINE_ACCESSOR(EnumDescriptor, value_count, int)
PROTOBUF_DEFINE_ARRAY_ACCESSOR(EnumDescriptor, value,
const EnumValueDescriptor*)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(EnumDescriptor, EnumOptions)
PROTOBUF_DEFINE_ACCESSOR(EnumDescriptor, is_placeholder, bool)
PROTOBUF_DEFINE_ACCESSOR(EnumDescriptor, reserved_range_count, int)
PROTOBUF_DEFINE_ARRAY_ACCESSOR(EnumDescriptor, reserved_range,
@ -2983,18 +3009,15 @@ inline absl::string_view EnumValueDescriptor::full_name() const {
}
PROTOBUF_DEFINE_ACCESSOR(EnumValueDescriptor, number, int)
PROTOBUF_DEFINE_ACCESSOR(EnumValueDescriptor, type, const EnumDescriptor*)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(EnumValueDescriptor, EnumValueOptions)
PROTOBUF_DEFINE_NAME_ACCESSOR(ServiceDescriptor)
PROTOBUF_DEFINE_ACCESSOR(ServiceDescriptor, file, const FileDescriptor*)
PROTOBUF_DEFINE_ACCESSOR(ServiceDescriptor, method_count, int)
PROTOBUF_DEFINE_ARRAY_ACCESSOR(ServiceDescriptor, method,
const MethodDescriptor*)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(ServiceDescriptor, ServiceOptions)
PROTOBUF_DEFINE_NAME_ACCESSOR(MethodDescriptor)
PROTOBUF_DEFINE_ACCESSOR(MethodDescriptor, service, const ServiceDescriptor*)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(MethodDescriptor, MethodOptions)
PROTOBUF_DEFINE_ACCESSOR(MethodDescriptor, client_streaming, bool)
PROTOBUF_DEFINE_ACCESSOR(MethodDescriptor, server_streaming, bool)
@ -3009,7 +3032,6 @@ PROTOBUF_DEFINE_ACCESSOR(FileDescriptor, message_type_count, int)
PROTOBUF_DEFINE_ACCESSOR(FileDescriptor, enum_type_count, int)
PROTOBUF_DEFINE_ACCESSOR(FileDescriptor, service_count, int)
PROTOBUF_DEFINE_ACCESSOR(FileDescriptor, extension_count, int)
PROTOBUF_DEFINE_OPTIONS_ACCESSOR(FileDescriptor, FileOptions)
PROTOBUF_DEFINE_ACCESSOR(FileDescriptor, is_placeholder, bool)
PROTOBUF_DEFINE_ARRAY_ACCESSOR(FileDescriptor, message_type, const Descriptor*)
@ -3085,7 +3107,7 @@ inline absl::string_view FieldDescriptor::json_name() const {
inline const OneofDescriptor* FieldDescriptor::containing_oneof() const {
if (is_oneof_) {
auto* res = scope_.containing_oneof;
const OneofDescriptor* res = scope_.containing_oneof;
PROTOBUF_ASSUME(res != nullptr);
return res;
}

View file

@ -0,0 +1,36 @@
// 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
#include "google/protobuf/offset_ptr.h"
#include <cstddef>
#include "absl/log/absl_log.h"
namespace google {
namespace protobuf {
namespace internal {
void BasePointerInvalidSelfReference() noexcept {
ABSL_LOG(FATAL) << "Nullable base pointer can't self reference.";
}
void BasePointerNonnullFailure() noexcept {
ABSL_LOG(FATAL)
<< "Non-nullable base pointer constructed with a null pointer.";
}
void BasePointerOverflow(const void* ptr, const void* base) noexcept {
ptrdiff_t diff =
reinterpret_cast<const char*>(ptr) - reinterpret_cast<const char*>(base);
ABSL_LOG(FATAL) << "Pointer out of scope for offset pointer: ptr=" << ptr
<< " base=" << base << " diff=" << diff;
}
} // namespace internal
} // namespace protobuf
} // namespace google

View file

@ -0,0 +1,177 @@
// 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
#ifndef GOOGLE_PROTOBUF_OFFSET_PTR_H__
#define GOOGLE_PROTOBUF_OFFSET_PTR_H__
#include <cstddef>
#include <cstdint>
#include "absl/base/optimization.h"
// Must be included last.
#include "google/protobuf/port_def.inc"
namespace google {
namespace protobuf {
namespace internal {
[[noreturn]] PROTOBUF_PRESERVE_ALL PROTOBUF_EXPORT void BasePointerOverflow(
const void* ptr, const void* base) noexcept;
[[noreturn]] PROTOBUF_PRESERVE_ALL PROTOBUF_EXPORT void
BasePointerInvalidSelfReference() noexcept;
[[noreturn]] PROTOBUF_PRESERVE_ALL PROTOBUF_EXPORT void
BasePointerNonnullFailure() noexcept;
// Offset based pointer-like class.
// It encodes its data relative to a `base` pointer.
// The caller must provide this pointer and it must be the same base pointer
// pass to `Resolve`.
// The offset is encoded in 32-bit and the caller must guarantee that.
//
// Is kAllowNull is false, then nullptr is not a valid input and will terminate
// the program. However, such mode is faster. The caller should choose the
// appropriate setting for the pointer in question.
template <typename T, bool kAllowNull>
class BasePointer {
// We must use 0 as the null pointer because some of these are initialized via
// memset.
static constexpr int32_t kNullOffset = 0;
public:
// Uninitialized.
BasePointer() = default;
// Trivial copy/assign.
BasePointer(const BasePointer&) = default;
BasePointer& operator=(const BasePointer&) = default;
BasePointer(T* ptr, const void* base) {
if constexpr (kAllowNull) {
if (ptr == nullptr) {
offset_ = kNullOffset;
return;
}
if (ABSL_PREDICT_FALSE(ptr == base)) {
BasePointerInvalidSelfReference();
}
} else {
if (ABSL_PREDICT_FALSE(ptr == nullptr)) {
BasePointerNonnullFailure();
}
}
ptrdiff_t diff = reinterpret_cast<const char*>(ptr) -
reinterpret_cast<const char*>(base);
if (ABSL_PREDICT_FALSE(static_cast<int32_t>(diff) != diff)) {
BasePointerOverflow(ptr, base);
}
offset_ = static_cast<int32_t>(diff);
}
// `base` must be the same `base` pointer as the one passed to the
// constructor.
T* Resolve(const void* base) const {
if constexpr (kAllowNull) {
if (offset_ == kNullOffset) return nullptr;
}
T* out = const_cast<T*>(reinterpret_cast<const T*>(
reinterpret_cast<const char*>(base) + offset_));
PROTOBUF_ASSUME(out != nullptr);
return out;
}
private:
int32_t offset_;
};
// Offset based pointer class.
// It uses its own address as the base pointer, which simplifies its use but
// restricts the input pointer to be in the same slab of memory as the
// `OffsetPtr` instance itself.
// kAllowNull follows the semantics of BasePointer.
template <typename T, bool kAllowNull>
class OffsetPtr {
public:
using value_type = T;
OffsetPtr() = default;
// Bit copy is wrong because we are relative to `this`.
OffsetPtr(const OffsetPtr&) = delete;
OffsetPtr& operator=(const OffsetPtr&) = delete;
// We can't have a conversion constructor because it would allow for
// temporaries to be made, which breaks the invariant of maximum distance.
T* get() const { return ptr_.Resolve(this); }
operator T*() const { return get(); } // NOLINT
T* operator->() const { return get(); }
OffsetPtr& operator=(T* value) {
ptr_ = BasePointer<T, kAllowNull>(value, this);
return *this;
}
OffsetPtr& operator=(std::nullptr_t) {
static_assert(kAllowNull, "Can't accept null.");
return *this = static_cast<T*>(nullptr);
}
private:
BasePointer<T, kAllowNull> ptr_;
};
template <typename T>
using NullableOffsetPtr = OffsetPtr<T, true>;
template <typename T>
using NonnullOffsetPtr = OffsetPtr<T, false>;
// Same as OffsetPtr, with a special case &T::default_instance.
// The pointer can be set to `&T::default_instance()` even though it is outside
// the range. It is handled specially.
// Null inputs are equivalent to the default instance.
template <typename T>
class OffsetProtoPtr {
public:
using value_type = T;
OffsetProtoPtr() = default;
// Bit copy is wrong because we are relative to `this`.
OffsetProtoPtr(const OffsetProtoPtr&) = delete;
OffsetProtoPtr& operator=(const OffsetProtoPtr&) = delete;
// We can't have a conversion constructor because it would allow for
// temporaries to be made, which breaks the invariant of maximum distance.
T* get() const {
T* value = ptr_.Resolve(this);
return value == nullptr ? &T::default_instance() : value;
}
operator T*() const { return get(); } // NOLINT
T* operator->() const { return get(); }
OffsetProtoPtr& operator=(T* value) {
ptr_ = BasePointer<T, true>(
value == &T::default_instance() ? nullptr : value, this);
return *this;
}
private:
BasePointer<T, true> ptr_;
};
} // namespace internal
} // namespace protobuf
} // namespace google
#include "google/protobuf/port_undef.inc"
#endif // GOOGLE_PROTOBUF_OFFSET_PTR_H__

View file

@ -0,0 +1,132 @@
// 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
#include "google/protobuf/offset_ptr.h"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <gtest/gtest.h>
#include "google/protobuf/unittest.pb.h"
namespace google {
namespace protobuf {
namespace internal {
namespace {
int array[10];
TEST(BasePtrTest, Basic) {
BasePointer<int, false> b(&array[5], &array);
EXPECT_EQ(&array[5], b.Resolve(&array));
}
TEST(BasePtrTest, Copyable) {
EXPECT_TRUE(
(std::is_trivially_copy_constructible_v<BasePointer<int, false>>));
EXPECT_TRUE((std::is_trivially_copy_assignable_v<BasePointer<int, false>>));
EXPECT_TRUE((std::is_trivially_destructible_v<BasePointer<int, false>>));
BasePointer<int, false> b(&array[7], &array);
EXPECT_EQ(&array[7], b.Resolve(&array));
auto b2 = b;
EXPECT_EQ(&array[7], b2.Resolve(&array));
}
TEST(BasePtrTest, BaseCanBeOnEitherSide) {
EXPECT_EQ(&array[7],
(BasePointer<int, false>(&array[7], &array[3])).Resolve(&array[3]));
EXPECT_EQ(&array[4],
(BasePointer<int, false>(&array[4], &array[7])).Resolve(&array[7]));
}
TEST(BasePtrTest, BaseCanBeSameAsPointer) {
int var = 0;
EXPECT_EQ(&var, (BasePointer<int, false>(&var, &var)).Resolve(&var));
}
TEST(BasePtrTest, NullIsAllowed) {
bool dummy = false;
BasePointer<int, true> b(nullptr, &dummy);
EXPECT_EQ(nullptr, b.Resolve(&dummy));
int p = 0;
EXPECT_DEATH((BasePointer<int, true>(&p, &p)),
"Nullable base pointer can't self reference");
EXPECT_DEATH((BasePointer<int, false>(nullptr, &dummy)),
"Non-nullable base pointer constructed with a null pointer");
}
TEST(BasePtrTest, OutOfScopeFails) {
// For this one we manufacture pointers to make sure they trigger out of scope
// failures.
if (sizeof(void*) == 4) {
GTEST_SKIP() << "We need 64-bit to have out-of-scope pointers.";
}
void* base =
reinterpret_cast<void*>(static_cast<uintptr_t>(0x123457890abcdef));
const auto make_ptr = [&](ptrdiff_t diff) {
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base) + diff);
};
using T = BasePointer<void, false>;
// INT_MAX is fine.
EXPECT_EQ(T(make_ptr(std::numeric_limits<int>::max()), base).Resolve(base),
make_ptr(std::numeric_limits<int>::max()));
// INT_MAX+1 is too much.
EXPECT_DEATH(
T(make_ptr(ptrdiff_t{std::numeric_limits<int>::max()} + 1), base),
"Pointer out of scope for offset pointer");
// INT_MIN is fine.
EXPECT_EQ(T(make_ptr(std::numeric_limits<int>::min()), base).Resolve(base),
make_ptr(std::numeric_limits<int>::min()));
// INT_MIN-1 is too much.
EXPECT_DEATH(
T(make_ptr(ptrdiff_t{std::numeric_limits<int>::min()} - 1), base),
"Pointer out of scope for offset pointer");
}
TEST(OffsetPtrTest, Basic) {
struct Object {
int array[10];
OffsetPtr<int, false> ptr;
} object{};
object.ptr = object.array + 3;
EXPECT_EQ(object.ptr, object.array + 3);
}
TEST(OffsetProtoPtr, Basic) {
using P = proto2_unittest::TestAllTypes;
OffsetProtoPtr<const P> p;
p = nullptr;
EXPECT_EQ(&P::default_instance(), p);
P msg;
p = &msg;
EXPECT_EQ(&msg, p);
p = &P::default_instance();
EXPECT_EQ(&P::default_instance(), p);
}
} // namespace
} // namespace internal
} // namespace protobuf
} // namespace google