From 63b7b25289447313ab6e79191fa1733748dca0da Mon Sep 17 00:00:00 2001 From: Derek Bailey Date: Tue, 9 May 2023 09:16:30 -0700 Subject: [PATCH] FlatBuffers 64 for C++ (#7935) * First working hack of adding 64-bit. Don't judge :) * Made vector_downward work on 64 bit types * vector_downward uses size_t, added offset64 to reflection * cleaned up adding offset64 in parser * Add C++ testing skeleton for 64-bit * working test for CreateVector64 * working >2 GiB buffers * support for large strings * simplified CreateString<> to just provide the offset type * generalize CreateVector template * update test_64.afb due to upstream format change * Added Vector64 type, which is just an alias for vector ATM * Switch to Offset64 for Vector64 * Update for reflection bfbs output change * Starting to add support for vector64 type in C++ * made a generic CreateVector that can handle different offsets and vector types * Support for 32-vector with 64-addressing * Vector64 basic builder + tests working * basic support for json vector64 support * renamed fields in test_64bit.fbs to better reflect their use * working C++ vector64 builder * Apply --annotate-sparse-vector to 64-bit tests * Enable Vector64 for --annotate-sparse-vectors * Merged from upstream * Add `near_string` field for testing 32-bit offsets alongside * keep track of where the 32-bit and 64-bit regions are for flatbufferbuilder * move template<> outside class body for GCC * update run.sh to build and run tests * basic assertion for adding 64-bit offset at the wrong time * started to separate `FlatBufferBuilder` into two classes, 1 64-bit aware, the other not * add test for nested flatbuffer vector64, fix bug in alignment of big vectors * fixed CreateDirect method by iterating by Offset64 first * internal refactoring of flatbufferbuilder * block not supported languages in the parser from using 64-bit * evolution tests for adding a vector64 field * conformity tests for adding/removing offset64 attributes * ensure test is for a big buffer * add parser error tests for `offset64` and `vector64` attributes * add missing static that GCC only complains about * remove stdint-uintn.h header that gets automatically added * move 64-bit CalculateOffset internal * fixed return size of EndVector * various fixes on windows * add SizeT to vector_downward * minimze range of size changes in vector and builder * reworked how tracking if 64-offsets are added * Add ReturnT to EndVector * small cleanups * remove need for second Array definition * combine IndirectHelpers into one definition * started support for vector of struct * Support for 32/64-vectors of structs + Offset64 * small cleanups * add verification for vector64 * add sized prefix for 64-bit buffers * add fuzzer for 64-bit * add example of adding many vectors using a wrapper table * run the new -bfbs-gen-embed logic on the 64-bit tests * remove run.sh and fix cmakelist issue * fixed bazel rules * fixed some PR comments * add 64-bit tests to cmakelist --- CMakeLists.txt | 5 + include/flatbuffers/array.h | 7 +- include/flatbuffers/base.h | 6 +- include/flatbuffers/buffer.h | 93 ++- include/flatbuffers/flatbuffer_builder.h | 549 ++++++++++----- include/flatbuffers/flatbuffers.h | 5 +- include/flatbuffers/idl.h | 63 +- include/flatbuffers/reflection.h | 1 + include/flatbuffers/reflection_generated.h | 30 +- include/flatbuffers/table.h | 32 +- include/flatbuffers/vector.h | 81 +-- include/flatbuffers/vector_downward.h | 43 +- include/flatbuffers/verifier.h | 79 ++- .../flatbuffers/reflection/BaseType.java | 5 +- .../google/flatbuffers/reflection/Field.java | 13 +- python/flatbuffers/reflection/BaseType.py | 3 +- python/flatbuffers/reflection/Field.py | 16 +- reflection/reflection.fbs | 3 + src/annotated_binary_text_gen.cpp | 12 +- src/binary_annotator.cpp | 161 +++-- src/binary_annotator.h | 10 +- src/flatc.cpp | 11 +- src/idl_gen_cpp.cpp | 306 ++++++--- src/idl_gen_text.cpp | 24 +- src/idl_parser.cpp | 178 +++-- tests/64bit/evolution/v1.fbs | 8 + tests/64bit/evolution/v1_generated.h | 219 ++++++ tests/64bit/evolution/v2.fbs | 9 + tests/64bit/evolution/v2_generated.h | 243 +++++++ tests/64bit/offset64_test.cpp | 447 +++++++++++++ tests/64bit/offset64_test.h | 19 + tests/64bit/test_64bit.afb | 74 +++ tests/64bit/test_64bit.bfbs | Bin 0 -> 1572 bytes tests/64bit/test_64bit.bin | Bin 0 -> 248 bytes tests/64bit/test_64bit.fbs | 49 ++ tests/64bit/test_64bit.json | 17 + tests/64bit/test_64bit_bfbs_generated.h | 93 +++ tests/64bit/test_64bit_generated.h | 625 ++++++++++++++++++ tests/BUILD.bazel | 6 + tests/MyGame/Example/Monster.php | 10 +- tests/evolution_test.cpp | 35 +- tests/fuzzer/.gitignore | 2 + tests/fuzzer/CMakeLists.txt | 11 + tests/fuzzer/flatbuffers_64bit_fuzzer.cc | 121 ++++ tests/fuzzer/flatbuffers_annotator_fuzzer.cc | 2 +- tests/parser_test.cpp | 41 +- tests/test.cpp | 16 +- tests/test_builder.cpp | 12 +- tests/test_builder.h | 8 +- 49 files changed, 3274 insertions(+), 529 deletions(-) create mode 100644 tests/64bit/evolution/v1.fbs create mode 100644 tests/64bit/evolution/v1_generated.h create mode 100644 tests/64bit/evolution/v2.fbs create mode 100644 tests/64bit/evolution/v2_generated.h create mode 100644 tests/64bit/offset64_test.cpp create mode 100644 tests/64bit/offset64_test.h create mode 100644 tests/64bit/test_64bit.afb create mode 100644 tests/64bit/test_64bit.bfbs create mode 100644 tests/64bit/test_64bit.bin create mode 100644 tests/64bit/test_64bit.fbs create mode 100644 tests/64bit/test_64bit.json create mode 100644 tests/64bit/test_64bit_bfbs_generated.h create mode 100644 tests/64bit/test_64bit_generated.h create mode 100644 tests/fuzzer/flatbuffers_64bit_fuzzer.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index e0b3248ae..d3115d882 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,6 +234,8 @@ set(FlatBuffers_Tests_SRCS tests/native_type_test_impl.cpp tests/alignment_test.h tests/alignment_test.cpp + tests/64bit/offset64_test.h + tests/64bit/offset64_test.cpp include/flatbuffers/code_generators.h src/code_generators.cpp ) @@ -527,6 +529,9 @@ if(FLATBUFFERS_BUILD_TESTS) compile_schema_for_test(tests/native_inline_table_test.fbs "${FLATC_OPT_COMP}") compile_schema_for_test(tests/native_type_test.fbs "${FLATC_OPT}") compile_schema_for_test(tests/key_field/key_field_sample.fbs "${FLATC_OPT_COMP}") + compile_schema_for_test(tests/64bit/test_64bit.fbs "${FLATC_OPT_COMP};--bfbs-gen-embed") + compile_schema_for_test(tests/64bit/evolution/v1.fbs "${FLATC_OPT_COMP}") + compile_schema_for_test(tests/64bit/evolution/v2.fbs "${FLATC_OPT_COMP}") if(FLATBUFFERS_CODE_SANITIZE) add_fsanitize_to_target(flattests ${FLATBUFFERS_CODE_SANITIZE}) diff --git a/include/flatbuffers/array.h b/include/flatbuffers/array.h index 2ff58c6fb..f4bfbf054 100644 --- a/include/flatbuffers/array.h +++ b/include/flatbuffers/array.h @@ -17,6 +17,7 @@ #ifndef FLATBUFFERS_ARRAY_H_ #define FLATBUFFERS_ARRAY_H_ +#include #include #include "flatbuffers/base.h" @@ -37,7 +38,7 @@ template class Array { public: typedef uint16_t size_type; typedef typename IndirectHelper::return_type return_type; - typedef VectorConstIterator const_iterator; + typedef VectorConstIterator const_iterator; typedef VectorReverseIterator const_reverse_iterator; // If T is a LE-scalar or a struct (!scalar_tag::value). @@ -158,11 +159,13 @@ template class Array { // Specialization for Array[struct] with access using Offset pointer. // This specialization used by idl_gen_text.cpp. -template class Array, length> { +template class OffsetT> +class Array, length> { static_assert(flatbuffers::is_same::value, "unexpected type T"); public: typedef const void *return_type; + typedef uint16_t size_type; const uint8_t *Data() const { return data_; } diff --git a/include/flatbuffers/base.h b/include/flatbuffers/base.h index ae3508b49..74ac9c8ca 100644 --- a/include/flatbuffers/base.h +++ b/include/flatbuffers/base.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -323,9 +324,11 @@ namespace flatbuffers { // Also, using a consistent offset type maintains compatibility of serialized // offset values between 32bit and 64bit systems. typedef uint32_t uoffset_t; +typedef uint64_t uoffset64_t; // Signed offsets for references that can go in both directions. typedef int32_t soffset_t; +typedef int64_t soffset64_t; // Offset/index used in v-tables, can be changed to uint8_t in // format forks to save a bit of space if desired. @@ -334,7 +337,8 @@ typedef uint16_t voffset_t; typedef uintmax_t largest_scalar_t; // In 32bits, this evaluates to 2GB - 1 -#define FLATBUFFERS_MAX_BUFFER_SIZE ((1ULL << (sizeof(::flatbuffers::soffset_t) * 8 - 1)) - 1) +#define FLATBUFFERS_MAX_BUFFER_SIZE std::numeric_limits<::flatbuffers::soffset_t>::max() +#define FLATBUFFERS_MAX_64_BUFFER_SIZE std::numeric_limits<::flatbuffers::soffset64_t>::max() // The minimum size buffer that can be a valid flatbuffer. // Includes the offset to the root table (uoffset_t), the offset to the vtable diff --git a/include/flatbuffers/buffer.h b/include/flatbuffers/buffer.h index e26a153c3..94d4f7903 100644 --- a/include/flatbuffers/buffer.h +++ b/include/flatbuffers/buffer.h @@ -25,14 +25,33 @@ namespace flatbuffers { // Wrapper for uoffset_t to allow safe template specialization. // Value is allowed to be 0 to indicate a null object (see e.g. AddOffset). -template struct Offset { - uoffset_t o; +template struct Offset { + // The type of offset to use. + typedef uoffset_t offset_type; + + offset_type o; Offset() : o(0) {} - Offset(uoffset_t _o) : o(_o) {} - Offset Union() const { return Offset(o); } + Offset(const offset_type _o) : o(_o) {} + Offset<> Union() const { return o; } bool IsNull() const { return !o; } }; +// Wrapper for uoffset64_t Offsets. +template struct Offset64 { + // The type of offset to use. + typedef uoffset64_t offset_type; + + offset_type o; + Offset64() : o(0) {} + Offset64(const offset_type offset) : o(offset) {} + Offset64<> Union() const { return o; } + bool IsNull() const { return !o; } +}; + +// Litmus check for ensuring the Offsets are the expected size. +static_assert(sizeof(Offset<>) == 4, "Offset has wrong size"); +static_assert(sizeof(Offset64<>) == 8, "Offset64 has wrong size"); + inline void EndianCheck() { int endiantest = 1; // If this fails, see FLATBUFFERS_LITTLEENDIAN above. @@ -75,35 +94,59 @@ template struct IndirectHelper { typedef T return_type; typedef T mutable_return_type; static const size_t element_stride = sizeof(T); - static return_type Read(const uint8_t *p, uoffset_t i) { + + static return_type Read(const uint8_t *p, const size_t i) { return EndianScalar((reinterpret_cast(p))[i]); } - static return_type Read(uint8_t *p, uoffset_t i) { - return Read(const_cast(p), i); + static mutable_return_type Read(uint8_t *p, const size_t i) { + return reinterpret_cast( + Read(const_cast(p), i)); } }; -template struct IndirectHelper> { + +// For vector of Offsets. +template class OffsetT> +struct IndirectHelper> { typedef const T *return_type; typedef T *mutable_return_type; - static const size_t element_stride = sizeof(uoffset_t); - static return_type Read(const uint8_t *p, uoffset_t i) { - p += i * sizeof(uoffset_t); - return reinterpret_cast(p + ReadScalar(p)); + typedef typename OffsetT::offset_type offset_type; + static const offset_type element_stride = sizeof(offset_type); + + static return_type Read(const uint8_t *const p, const offset_type i) { + // Offsets are relative to themselves, so first update the pointer to + // point to the offset location. + const uint8_t *const offset_location = p + i * element_stride; + + // Then read the scalar value of the offset (which may be 32 or 64-bits) and + // then determine the relative location from the offset location. + return reinterpret_cast( + offset_location + ReadScalar(offset_location)); } - static mutable_return_type Read(uint8_t *p, uoffset_t i) { - p += i * sizeof(uoffset_t); - return reinterpret_cast(p + ReadScalar(p)); + static mutable_return_type Read(uint8_t *const p, const offset_type i) { + // Offsets are relative to themselves, so first update the pointer to + // point to the offset location. + uint8_t *const offset_location = p + i * element_stride; + + // Then read the scalar value of the offset (which may be 32 or 64-bits) and + // then determine the relative location from the offset location. + return reinterpret_cast( + offset_location + ReadScalar(offset_location)); } }; + +// For vector of structs. template struct IndirectHelper { typedef const T *return_type; typedef T *mutable_return_type; static const size_t element_stride = sizeof(T); - static return_type Read(const uint8_t *p, uoffset_t i) { - return reinterpret_cast(p + i * sizeof(T)); + + static return_type Read(const uint8_t *const p, const size_t i) { + // Structs are stored inline, relative to the first struct pointer. + return reinterpret_cast(p + i * element_stride); } - static mutable_return_type Read(uint8_t *p, uoffset_t i) { - return reinterpret_cast(p + i * sizeof(T)); + static mutable_return_type Read(uint8_t *const p, const size_t i) { + // Structs are stored inline, relative to the first struct pointer. + return reinterpret_cast(p + i * element_stride); } }; @@ -130,23 +173,25 @@ inline bool BufferHasIdentifier(const void *buf, const char *identifier, /// @cond FLATBUFFERS_INTERNAL // Helpers to get a typed pointer to the root object contained in the buffer. template T *GetMutableRoot(void *buf) { + if (!buf) return nullptr; EndianCheck(); return reinterpret_cast( reinterpret_cast(buf) + EndianScalar(*reinterpret_cast(buf))); } -template T *GetMutableSizePrefixedRoot(void *buf) { - return GetMutableRoot(reinterpret_cast(buf) + - sizeof(uoffset_t)); +template +T *GetMutableSizePrefixedRoot(void *buf) { + return GetMutableRoot(reinterpret_cast(buf) + sizeof(SizeT)); } template const T *GetRoot(const void *buf) { return GetMutableRoot(const_cast(buf)); } -template const T *GetSizePrefixedRoot(const void *buf) { - return GetRoot(reinterpret_cast(buf) + sizeof(uoffset_t)); +template +const T *GetSizePrefixedRoot(const void *buf) { + return GetRoot(reinterpret_cast(buf) + sizeof(SizeT)); } } // namespace flatbuffers diff --git a/include/flatbuffers/flatbuffer_builder.h b/include/flatbuffers/flatbuffer_builder.h index caf9a3d15..ed932cd9c 100644 --- a/include/flatbuffers/flatbuffer_builder.h +++ b/include/flatbuffers/flatbuffer_builder.h @@ -18,12 +18,15 @@ #define FLATBUFFERS_FLATBUFFER_BUILDER_H_ #include +#include #include #include +#include #include "flatbuffers/allocator.h" #include "flatbuffers/array.h" #include "flatbuffers/base.h" +#include "flatbuffers/buffer.h" #include "flatbuffers/buffer_ref.h" #include "flatbuffers/default_allocator.h" #include "flatbuffers/detached_buffer.h" @@ -40,8 +43,9 @@ namespace flatbuffers { // Converts a Field ID to a virtual table offset. inline voffset_t FieldIndexToOffset(voffset_t field_id) { // Should correspond to what EndTable() below builds up. - const voffset_t fixed_fields = 2 * sizeof(voffset_t); // Vtable size and Object Size. - return fixed_fields + field_id * sizeof(voffset_t); + const voffset_t fixed_fields = + 2 * sizeof(voffset_t); // Vtable size and Object Size. + return fixed_fields + field_id * sizeof(voffset_t); } template> @@ -68,8 +72,13 @@ T *data(std::vector &v) { /// `PushElement`/`AddElement`/`EndTable`, or the builtin `CreateString`/ /// `CreateVector` functions. Do this is depth-first order to build up a tree to /// the root. `Finish()` wraps up the buffer ready for transport. -class FlatBufferBuilder { +template class FlatBufferBuilderImpl { public: + // This switches the size type of the builder, based on if its 64-bit aware + // (uoffset64_t) or not (uoffset_t). + typedef + typename std::conditional::type SizeT; + /// @brief Default constructor for FlatBufferBuilder. /// @param[in] initial_size The initial size of the buffer, in bytes. Defaults /// to `1024`. @@ -81,13 +90,16 @@ class FlatBufferBuilder { /// minimum alignment upon reallocation. Only needed if you intend to store /// types with custom alignment AND you wish to read the buffer in-place /// directly after creation. - explicit FlatBufferBuilder( + explicit FlatBufferBuilderImpl( size_t initial_size = 1024, Allocator *allocator = nullptr, bool own_allocator = false, size_t buffer_minalign = AlignOf()) - : buf_(initial_size, allocator, own_allocator, buffer_minalign), + : buf_(initial_size, allocator, own_allocator, buffer_minalign, + static_cast(Is64Aware ? FLATBUFFERS_MAX_64_BUFFER_SIZE + : FLATBUFFERS_MAX_BUFFER_SIZE)), num_field_loc(0), max_voffset_(0), + length_of_64_bit_region_(0), nested(false), finished(false), minalign_(1), @@ -98,10 +110,13 @@ class FlatBufferBuilder { } /// @brief Move constructor for FlatBufferBuilder. - FlatBufferBuilder(FlatBufferBuilder &&other) noexcept - : buf_(1024, nullptr, false, AlignOf()), + FlatBufferBuilderImpl(FlatBufferBuilderImpl &&other) noexcept + : buf_(1024, nullptr, false, AlignOf(), + static_cast(Is64Aware ? FLATBUFFERS_MAX_64_BUFFER_SIZE + : FLATBUFFERS_MAX_BUFFER_SIZE)), num_field_loc(0), max_voffset_(0), + length_of_64_bit_region_(0), nested(false), finished(false), minalign_(1), @@ -116,18 +131,19 @@ class FlatBufferBuilder { } /// @brief Move assignment operator for FlatBufferBuilder. - FlatBufferBuilder &operator=(FlatBufferBuilder &&other) noexcept { + FlatBufferBuilderImpl &operator=(FlatBufferBuilderImpl &&other) noexcept { // Move construct a temporary and swap idiom - FlatBufferBuilder temp(std::move(other)); + FlatBufferBuilderImpl temp(std::move(other)); Swap(temp); return *this; } - void Swap(FlatBufferBuilder &other) { + void Swap(FlatBufferBuilderImpl &other) { using std::swap; buf_.swap(other.buf_); swap(num_field_loc, other.num_field_loc); swap(max_voffset_, other.max_voffset_); + swap(length_of_64_bit_region_, other.length_of_64_bit_region_); swap(nested, other.nested); swap(finished, other.finished); swap(minalign_, other.minalign_); @@ -136,7 +152,7 @@ class FlatBufferBuilder { swap(string_pool, other.string_pool); } - ~FlatBufferBuilder() { + ~FlatBufferBuilderImpl() { if (string_pool) delete string_pool; } @@ -153,12 +169,36 @@ class FlatBufferBuilder { nested = false; finished = false; minalign_ = 1; + length_of_64_bit_region_ = 0; if (string_pool) string_pool->clear(); } /// @brief The current size of the serialized buffer, counting from the end. + /// @return Returns an `SizeT` with the current size of the buffer. + SizeT GetSize() const { return buf_.size(); } + + /// @brief The current size of the serialized buffer relative to the end of + /// the 32-bit region. /// @return Returns an `uoffset_t` with the current size of the buffer. - uoffset_t GetSize() const { return buf_.size(); } + template + // Only enable this method for the 64-bit builder, as only that builder is + // concerned with the 32/64-bit boundary, and should be the one to bare any + // run time costs. + typename std::enable_if::type GetSizeRelative32BitRegion() + const { + //[32-bit region][64-bit region] + // [XXXXXXXXXXXXXXXXXXX] GetSize() + // [YYYYYYYYYYYYY] length_of_64_bit_region_ + // [ZZZZ] return size + return static_cast(GetSize() - length_of_64_bit_region_); + } + + template + // Only enable this method for the 32-bit builder. + typename std::enable_if::type GetSizeRelative32BitRegion() + const { + return static_cast(GetSize()); + } /// @brief Get the serialized buffer (after you call `Finish()`). /// @return Returns an `uint8_t` pointer to the FlatBuffer data inside the @@ -270,14 +310,16 @@ class FlatBufferBuilder { } // Write a single aligned scalar to the buffer - template uoffset_t PushElement(T element) { + template + ReturnT PushElement(T element) { AssertScalarT(); Align(sizeof(T)); buf_.push_small(EndianScalar(element)); - return GetSize(); + return CalculateOffset(); } - template uoffset_t PushElement(Offset off) { + template class OffsetT = Offset> + uoffset_t PushElement(OffsetT off) { // Special case for offsets: see ReferTo below. return PushElement(ReferTo(off.o)); } @@ -307,11 +349,16 @@ class FlatBufferBuilder { AddElement(field, ReferTo(off.o), static_cast(0)); } + template void AddOffset(voffset_t field, Offset64 off) { + if (off.IsNull()) return; // Don't store. + AddElement(field, ReferTo(off.o), static_cast(0)); + } + template void AddStruct(voffset_t field, const T *structptr) { if (!structptr) return; // Default, don't store. Align(AlignOf()); buf_.push_small(*structptr); - TrackField(field, GetSize()); + TrackField(field, CalculateOffset()); } void AddStructOffset(voffset_t field, uoffset_t off) { @@ -322,12 +369,29 @@ class FlatBufferBuilder { // This function converts them to be relative to the current location // in the buffer (when stored here), pointing upwards. uoffset_t ReferTo(uoffset_t off) { - // Align to ensure GetSize() below is correct. + // Align to ensure GetSizeRelative32BitRegion() below is correct. Align(sizeof(uoffset_t)); - // Offset must refer to something already in buffer. - const uoffset_t size = GetSize(); + // 32-bit offsets are relative to the tail of the 32-bit region of the + // buffer. For most cases (without 64-bit entities) this is equivalent to + // size of the whole buffer (e.g. GetSize()) + return ReferTo(off, GetSizeRelative32BitRegion()); + } + + uoffset64_t ReferTo(uoffset64_t off) { + // Align to ensure GetSize() below is correct. + Align(sizeof(uoffset64_t)); + // 64-bit offsets are relative to tail of the whole buffer + return ReferTo(off, GetSize()); + } + + template T ReferTo(const T off, const T2 size) { FLATBUFFERS_ASSERT(off && off <= size); - return size - off + static_cast(sizeof(uoffset_t)); + return size - off + static_cast(sizeof(T)); + } + + template T ReferTo(const T off, const T size) { + FLATBUFFERS_ASSERT(off && off <= size); + return size - off + static_cast(sizeof(T)); } void NotNested() { @@ -349,7 +413,7 @@ class FlatBufferBuilder { uoffset_t StartTable() { NotNested(); nested = true; - return GetSize(); + return GetSizeRelative32BitRegion(); } // This finishes one serialized object by generating the vtable if it's a @@ -360,7 +424,9 @@ class FlatBufferBuilder { FLATBUFFERS_ASSERT(nested); // Write the vtable offset, which is the start of any Table. // We fill its value later. - const uoffset_t vtableoffsetloc = PushElement(0); + // This is relative to the end of the 32-bit region. + const uoffset_t vtable_offset_loc = + static_cast(PushElement(0)); // Write a vtable, which consists entirely of voffset_t elements. // It starts with the number of offsets, followed by a type id, followed // by the offsets themselves. In reverse: @@ -370,7 +436,7 @@ class FlatBufferBuilder { (std::max)(static_cast(max_voffset_ + sizeof(voffset_t)), FieldIndexToOffset(0)); buf_.fill_big(max_voffset_); - auto table_object_size = vtableoffsetloc - start; + const uoffset_t table_object_size = vtable_offset_loc - start; // Vtable use 16bit offsets. FLATBUFFERS_ASSERT(table_object_size < 0x10000); WriteScalar(buf_.data() + sizeof(voffset_t), @@ -380,7 +446,8 @@ class FlatBufferBuilder { for (auto it = buf_.scratch_end() - num_field_loc * sizeof(FieldLoc); it < buf_.scratch_end(); it += sizeof(FieldLoc)) { auto field_location = reinterpret_cast(it); - auto pos = static_cast(vtableoffsetloc - field_location->off); + const voffset_t pos = + static_cast(vtable_offset_loc - field_location->off); // If this asserts, it means you've set a field twice. FLATBUFFERS_ASSERT( !ReadScalar(buf_.data() + field_location->id)); @@ -389,7 +456,7 @@ class FlatBufferBuilder { ClearOffsets(); auto vt1 = reinterpret_cast(buf_.data()); auto vt1_size = ReadScalar(vt1); - auto vt_use = GetSize(); + auto vt_use = GetSizeRelative32BitRegion(); // See if we already have generated a vtable with this exact same // layout before. If so, make it point to the old one, remove this one. if (dedup_vtables_) { @@ -400,23 +467,24 @@ class FlatBufferBuilder { auto vt2_size = ReadScalar(vt2); if (vt1_size != vt2_size || 0 != memcmp(vt2, vt1, vt1_size)) continue; vt_use = *vt_offset_ptr; - buf_.pop(GetSize() - static_cast(vtableoffsetloc)); + buf_.pop(GetSizeRelative32BitRegion() - vtable_offset_loc); break; } } // If this is a new vtable, remember it. - if (vt_use == GetSize()) { buf_.scratch_push_small(vt_use); } + if (vt_use == GetSizeRelative32BitRegion()) { + buf_.scratch_push_small(vt_use); + } // Fill the vtable offset we created above. - // The offset points from the beginning of the object to where the - // vtable is stored. + // The offset points from the beginning of the object to where the vtable is + // stored. // Offsets default direction is downward in memory for future format // flexibility (storing all vtables at the start of the file). - WriteScalar(buf_.data_at(vtableoffsetloc), + WriteScalar(buf_.data_at(vtable_offset_loc + length_of_64_bit_region_), static_cast(vt_use) - - static_cast(vtableoffsetloc)); - + static_cast(vtable_offset_loc)); nested = false; - return vtableoffsetloc; + return vtable_offset_loc; } FLATBUFFERS_ATTRIBUTE([[deprecated("call the version above instead")]]) @@ -426,14 +494,20 @@ class FlatBufferBuilder { // This checks a required field has been set in a given table that has // just been constructed. - template void Required(Offset table, voffset_t field); + template void Required(Offset table, voffset_t field) { + auto table_ptr = reinterpret_cast(buf_.data_at(table.o)); + bool ok = table_ptr->GetOptionalFieldOffset(field) != 0; + // If this fails, the caller will show what field needs to be set. + FLATBUFFERS_ASSERT(ok); + (void)ok; + } uoffset_t StartStruct(size_t alignment) { Align(alignment); - return GetSize(); + return GetSizeRelative32BitRegion(); } - uoffset_t EndStruct() { return GetSize(); } + uoffset_t EndStruct() { return GetSizeRelative32BitRegion(); } void ClearOffsets() { buf_.scratch_pop(num_field_loc * sizeof(FieldLoc)); @@ -442,15 +516,18 @@ class FlatBufferBuilder { } // Aligns such that when "len" bytes are written, an object can be written - // after it with "alignment" without padding. + // after it (forward in the buffer) with "alignment" without padding. void PreAlign(size_t len, size_t alignment) { if (len == 0) return; TrackMinAlign(alignment); buf_.fill(PaddingBytes(GetSize() + len, alignment)); } - template void PreAlign(size_t len) { - AssertScalarT(); - PreAlign(len, AlignOf()); + + // Aligns such than when "len" bytes are written, an object of type `AlignT` + // can be written after it (forward in the buffer) without padding. + template void PreAlign(size_t len) { + AssertScalarT(); + PreAlign(len, AlignOf()); } /// @endcond @@ -458,34 +535,35 @@ class FlatBufferBuilder { /// @param[in] str A const char pointer to the data to be stored as a string. /// @param[in] len The number of bytes that should be stored from `str`. /// @return Returns the offset in the buffer where the string starts. - Offset CreateString(const char *str, size_t len) { - NotNested(); - PreAlign(len + 1); // Always 0-terminated. - buf_.fill(1); - PushBytes(reinterpret_cast(str), len); - PushElement(static_cast(len)); - return Offset(GetSize()); + template class OffsetT = Offset> + OffsetT CreateString(const char *str, size_t len) { + CreateStringImpl(str, len); + return OffsetT( + CalculateOffset::offset_type>()); } /// @brief Store a string in the buffer, which is null-terminated. /// @param[in] str A const char pointer to a C-string to add to the buffer. /// @return Returns the offset in the buffer where the string starts. - Offset CreateString(const char *str) { - return CreateString(str, strlen(str)); + template class OffsetT = Offset> + OffsetT CreateString(const char *str) { + return CreateString(str, strlen(str)); } /// @brief Store a string in the buffer, which is null-terminated. /// @param[in] str A char pointer to a C-string to add to the buffer. /// @return Returns the offset in the buffer where the string starts. - Offset CreateString(char *str) { - return CreateString(str, strlen(str)); + template class OffsetT = Offset> + OffsetT CreateString(char *str) { + return CreateString(str, strlen(str)); } /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const reference to a std::string to store in the buffer. /// @return Returns the offset in the buffer where the string starts. - Offset CreateString(const std::string &str) { - return CreateString(str.c_str(), str.length()); + template class OffsetT = Offset> + OffsetT CreateString(const std::string &str) { + return CreateString(str.c_str(), str.length()); } // clang-format off @@ -493,8 +571,9 @@ class FlatBufferBuilder { /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const string_view to copy in to the buffer. /// @return Returns the offset in the buffer where the string starts. - Offset CreateString(flatbuffers::string_view str) { - return CreateString(str.data(), str.size()); + template