From 86b45cdd3da46a97a4476be2f2a61909677284a2 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Fri, 9 Oct 2026 10:22:03 -0700 Subject: [PATCH] Report allocation failures in fixed size PayloadBuffers (2.1.6) A fixed size buffer that runs out of space now reports it and keeps its existing contents: - Allocate, Realloc, SetString, AllocateMainMessage and NewMessage return nullptr, AllocateString returns an empty span, and AllocateMetadata, VectorPush, VectorReserve and VectorResize return false. - The magic word records the failure in kAllocationFailedFlag, reported by AllocationFailed(). IsValidMagic and IsMoveable ignore the flag, and readers that check the magic with kBitMapMask see the buffer as invalid. - A small block that has no room for a new bitmap run comes from the free list, so small fixed buffers can use all of their space. - VectorResize sets the elements it adds to zero. Previously reserved capacity and elements removed by shrinking kept stale values. --- MODULE.bazel | 2 +- README.md | 23 +++- toolbelt/payload_buffer.cc | 53 ++++++-- toolbelt/payload_buffer.h | 90 +++++++++++--- toolbelt/payload_buffer_test.cc | 212 ++++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+), 29 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 7117fe6..689bcea 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = "cpp_toolbelt", - version = "2.1.5", + version = "2.1.6", ) bazel_dep(name = "platforms", version = "1.0.0") diff --git a/README.md b/README.md index fda81f4..7254b91 100644 --- a/README.md +++ b/README.md @@ -833,12 +833,14 @@ struct PayloadBuffer { // Vector operations template - static void VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, + static bool VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, bool enable_small_block = true); template - static void VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, + static bool VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, bool enable_small_block = true); template + static bool VectorResize(PayloadBuffer **self, VectorHeader *hdr, size_t n); + template T VectorGet(const VectorHeader *hdr, size_t index) const; // Address conversion @@ -852,9 +854,26 @@ struct PayloadBuffer { void Dump(std::ostream &os); bool IsValidMagic() const; bool IsMoveable() const; + bool AllocationFailed() const; }; ``` +#### Fixed-size buffers that run out of space + +When a fixed-size buffer has no room for an allocation, the allocating +functions report it and leave the buffer's existing contents unchanged: +`Allocate`, `Realloc`, `SetString`, `AllocateMainMessage` and `NewMessage` +return `nullptr`, `AllocateString` returns an empty span, and +`AllocateMetadata`, `VectorPush`, `VectorReserve` and `VectorResize` return +`false`. The buffer also records the failure in its magic word +(`kAllocationFailedFlag`), and `AllocationFailed()` reports it. The writer can +keep making smaller allocations, while a reader that checks the magic using +`kBitMapMask` treats the incomplete buffer as invalid. + +With bitmaps enabled, a small allocation that has no room for a new bitmap run +comes from the free list instead, so a small fixed-size buffer can use all of +its space. `VectorResize` sets the elements it adds to zero. + #### Example ```cpp diff --git a/toolbelt/payload_buffer.cc b/toolbelt/payload_buffer.cc index 0e249d7..fe0f3fc 100644 --- a/toolbelt/payload_buffer.cc +++ b/toolbelt/payload_buffer.cc @@ -38,15 +38,22 @@ inline int BitmapRunIndexFromEncodedSize(uint32_t n) { void *PayloadBuffer::AllocateMainMessage(PayloadBuffer **self, size_t size) { void *msg = Allocate(self, size, 8, true); + if (msg == nullptr) { + return nullptr; + } (*self)->message = (*self)->ToOffset(msg); return msg; } -void PayloadBuffer::AllocateMetadata(PayloadBuffer **self, void *md, +bool PayloadBuffer::AllocateMetadata(PayloadBuffer **self, void *md, size_t size) { void *m = Allocate(self, size, 1, false); + if (m == nullptr) { + return false; + } memcpy(m, md, size); (*self)->metadata = (*self)->ToOffset(m); + return true; } char *PayloadBuffer::SetString(PayloadBuffer **self, const char *s, size_t len, @@ -66,6 +73,9 @@ char *PayloadBuffer::SetString(PayloadBuffer **self, const char *s, size_t len, } else { str = Allocate(self, len + 4, 4, false); } + if (str == nullptr) { + return nullptr; + } uint32_t *p = reinterpret_cast(str); p[0] = uint32_t(len); memcpy(p + 1, s, len); @@ -207,6 +217,9 @@ absl::Span PayloadBuffer::AllocateString(PayloadBuffer **self, size_t len, } else { str = Allocate(self, len + 4, 4, clear); } + if (str == nullptr) { + return {}; + } uint32_t *p = reinterpret_cast(str); p[0] = uint32_t(len); @@ -225,6 +238,9 @@ void PayloadBuffer::Dump(std::ostream &os) { << std::endl; os << " bitmaps: " << ((magic & kBitMapFlag) ? "enabled" : "disabled") << std::endl; + if (AllocationFailed()) { + os << " allocation failed" << std::endl; + } os << " hwm: " << hwm << " " << ToAddress(hwm) << std::endl; os << " full_size: " << full_size << std::endl; os << " metadata: " << metadata << " " << ToAddress(metadata) << std::endl; @@ -336,6 +352,7 @@ void *PayloadBuffer::Allocate(PayloadBuffer **buffer, uint32_t n, Resizer *resizer = (*buffer)->GetResizer(); if (resizer == nullptr) { // Really out of memory. + (*buffer)->magic |= kAllocationFailedFlag; return nullptr; } size_t old_size = (*buffer)->full_size; @@ -769,7 +786,11 @@ bool PayloadBuffer::PrimeBitmapAllocator(PayloadBuffer **self, size_t size) { } // Re-derive hdr since AllocateBitMapRun may have triggered a buffer resize. VectorHeader *hdr = (*self)->ToAddress((*self)->bitmaps[index]); - (*self)->VectorPush(self, hdr, (*self)->ToOffset(run), false); + if (!(*self)->VectorPush(self, hdr, (*self)->ToOffset(run), + false)) { + (*self)->Free(run); + return false; + } return true; } @@ -782,9 +803,13 @@ BufferOffset PayloadBuffer::AllocateBitMapRunVector(PayloadBuffer **self) { } BufferOffset hdr_offset = (*self)->ToOffset(hdr); - // Preallocate space for 8 elements. - VectorReserve(self, reinterpret_cast(hdr), 8, - false); + // Preallocate space for 8 elements. This is only an optimization so a + // failure leaves the allocation-failed flag as it was. + const uint32_t failed = (*self)->magic & kAllocationFailedFlag; + if (!VectorReserve(self, reinterpret_cast(hdr), + 8, false)) { + (*self)->magic = ((*self)->magic & ~kAllocationFailedFlag) | failed; + } return hdr_offset; } @@ -860,7 +885,11 @@ void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size, // Re-derive hdr since AllocateBitMapRun may have triggered a buffer // resize, invalidating the previous pointer. hdr = (*pb)->ToAddress((*pb)->bitmaps[index]); - (*pb)->VectorPush(pb, hdr, (*pb)->ToOffset(run), false); + if (!(*pb)->VectorPush(pb, hdr, (*pb)->ToOffset(run), + false)) { + (*pb)->Free(run); + return nullptr; + } } } @@ -879,8 +908,16 @@ void BitMapRun::Free(PayloadBuffer *pb, int index, int bitmap_index, void *PayloadBuffer::AllocateSmallBlock(PayloadBuffer **pb, uint32_t size, int index, bool clear) { - return BitMapRun::Allocate(pb, index, size, bitmp_run_infos[index].size, - bitmp_run_infos[index].num, clear); + // A new run holds many blocks, so a fixed size buffer may have room for the + // block but not for the run. The block then comes from the free list. + const uint32_t failed = (*pb)->magic & kAllocationFailedFlag; + void *addr = BitMapRun::Allocate(pb, index, size, bitmp_run_infos[index].size, + bitmp_run_infos[index].num, clear); + if (addr != nullptr) { + return addr; + } + (*pb)->magic = ((*pb)->magic & ~kAllocationFailedFlag) | failed; + return Allocate(pb, size, clear, false); } void PayloadBuffer::FreeSmallBlock(PayloadBuffer *pb, int index, diff --git a/toolbelt/payload_buffer.h b/toolbelt/payload_buffer.h index 3feca78..cabef12 100644 --- a/toolbelt/payload_buffer.h +++ b/toolbelt/payload_buffer.h @@ -22,6 +22,12 @@ constexpr uint32_t kMovableBufferMagic = 0xc5f6f1c4; constexpr uint32_t kBitMapMask = 0xfffffffe; constexpr uint32_t kBitMapFlag = 1; +// Set when an allocation failed because a fixed size buffer was full. The +// buffer's contents are then incomplete, and readers that check the magic +// using kBitMapMask see it as invalid. +constexpr uint32_t kAllocationFailedFlag = 2; +constexpr uint32_t kMagicFlagsMask = kBitMapFlag | kAllocationFailedFlag; + using BufferOffset = uint32_t; struct FreeBlockHeader { @@ -183,6 +189,13 @@ struct PayloadBuffer { bool BitmapsEnabled() const { return (magic & kBitMapFlag) != 0; } + // True if an allocation has failed because the buffer was full. Functions + // that allocate return nullptr, false or an empty span when that happens + // and leave the existing contents unchanged. + bool AllocationFailed() const { + return (magic & kAllocationFailedFlag) != 0; + } + void SetResizer(Resizer r) { // Place a pointer to the resizer function in the buffer just after the // header. @@ -201,11 +214,12 @@ struct PayloadBuffer { size_t Size() const { return size_t(hwm); } // Allocate space for the main message in the buffer and set the - // 'message' field to its offset. + // 'message' field to its offset. Returns nullptr if there is no space. static void *AllocateMainMessage(PayloadBuffer **self, size_t size); - // Allocate space for the message metadata and copy it in. - static void AllocateMetadata(PayloadBuffer **self, void *md, size_t size); + // Allocate space for the message metadata and copy it in. Returns false + // if there is no space. + static bool AllocateMetadata(PayloadBuffer **self, void *md, size_t size); // Prime the bitmap runs for the given size. This pre-allocates the runs // for the size to remove the initial allocation time overhead for the // first allocation. Returns true if successful. @@ -253,7 +267,8 @@ struct PayloadBuffer { // Allocate space for the string. // 'header_offset' is the offset into the buffer of the StringHeader. - // The string is copied in. + // The string is copied in. Returns nullptr, leaving any previous string + // in place, if there is no space. // C-string style (allows for no allocation of std::string). static char *SetString(PayloadBuffer **self, const char *s, size_t len, @@ -267,6 +282,8 @@ struct PayloadBuffer { static void ClearString(PayloadBuffer **self, BufferOffset header_offset); + // Returns an empty span, leaving any previous string in place, if there is + // no space. static absl::Span AllocateString(PayloadBuffer **self, size_t len, BufferOffset header_offset, bool clear = false); @@ -279,16 +296,19 @@ struct PayloadBuffer { template void Set(BufferOffset offset, T v); template T &Get(BufferOffset offset); + // The vector functions return false, leaving the vector unchanged, if + // there is no space. template - static void VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, + static bool VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, bool enable_small_block = true); template - static void VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, + static bool VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, bool enable_small_block = true); + // Elements added by growing the vector are zero. template - static void VectorResize(PayloadBuffer **self, VectorHeader *hdr, size_t n); + static bool VectorResize(PayloadBuffer **self, VectorHeader *hdr, size_t n); template static void VectorClear(PayloadBuffer **self, VectorHeader *hdr); @@ -356,11 +376,11 @@ struct PayloadBuffer { uint32_t n, bool clear = true); bool IsValidMagic() const { - uint32_t m = magic & kBitMapMask; + uint32_t m = magic & ~kMagicFlagsMask; return m == kFixedBufferMagic || m == kMovableBufferMagic; } bool IsMoveable() const { - return (magic & kBitMapMask) == kMovableBufferMagic; + return (magic & ~kMagicFlagsMask) == kMovableBufferMagic; } // Returns true if [addr, addr + nbytes) lies entirely within the first @@ -522,7 +542,7 @@ template inline T &PayloadBuffer::Get(BufferOffset offset) { } template -inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, +inline bool PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, bool enable_small_block) { // hdr points to a VectorHeader: // uint32_t num_elements; - number of elements in the vector @@ -531,10 +551,12 @@ inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, // by the block size (in bytes). BufferOffset hdr_offset = (*self)->ToOffset(hdr); - uint32_t total_size = hdr->num_elements * sizeof(T); if (hdr->data == 0) { // The vector is empty, allocate it with a default size of 2. void *vecp = Allocate(self, 2 * sizeof(T), true, enable_small_block); + if (vecp == nullptr) { + return false; + } VectorHeader *new_hdr = (*self)->ToAddress(hdr_offset); new_hdr->data = (*self)->ToOffset(vecp); hdr = new_hdr; @@ -551,6 +573,9 @@ inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, const uint32_t new_size = std::max( needed, std::max(current_size * 2, min_capacity)); void *vecp = Realloc(self, block, new_size, true, enable_small_block); + if (vecp == nullptr) { + return false; + } VectorHeader *new_hdr = (*self)->ToAddress(hdr_offset); new_hdr->data = (*self)->ToOffset(vecp); hdr = new_hdr; @@ -562,15 +587,22 @@ inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, *valuep = v; // Increment the number of elements. hdr->num_elements++; + return true; } template -inline void PayloadBuffer::VectorReserve(PayloadBuffer **self, +inline bool PayloadBuffer::VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, bool enable_small_block) { + if (n == 0) { + return true; + } BufferOffset hdr_offset = (*self)->ToOffset(hdr); if (hdr->data == 0) { void *vecp = Allocate(self, n * sizeof(T), false, enable_small_block); + if (vecp == nullptr) { + return false; + } VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); new_hdr->data = (*self)->ToOffset(vecp); hdr = new_hdr; @@ -583,22 +615,31 @@ inline void PayloadBuffer::VectorReserve(PayloadBuffer **self, // Need to expand the memory to the size given. void *vecp = Realloc(self, block, n * sizeof(T), false, enable_small_block); + if (vecp == nullptr) { + return false; + } VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); new_hdr->data = (*self)->ToOffset(vecp); hdr = new_hdr; } } + return true; } template -inline void PayloadBuffer::VectorResize(PayloadBuffer **self, VectorHeader *hdr, +inline bool PayloadBuffer::VectorResize(PayloadBuffer **self, VectorHeader *hdr, size_t n) { BufferOffset hdr_offset = (*self)->ToOffset(hdr); if (hdr->data == 0) { - void *vecp = Allocate(self, n * sizeof(T)); - VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + if (n > 0) { + void *vecp = Allocate(self, n * sizeof(T)); + if (vecp == nullptr) { + return false; + } + VectorHeader *new_hdr = (*self)->ToAddress(hdr_offset); + new_hdr->data = (*self)->ToOffset(vecp); + hdr = new_hdr; + } } else { // Vector has some values in it. Retrieve the total size from // the allocated block header (before the start of the memory) @@ -606,13 +647,23 @@ inline void PayloadBuffer::VectorResize(PayloadBuffer **self, VectorHeader *hdr, uint32_t current_size = DecodeSize(block); if (current_size < n * sizeof(T)) { // Need to expand the memory to the size given. - void *vecp = Realloc(self, block, n * sizeof(T), 8); + void *vecp = Realloc(self, block, n * sizeof(T), true); + if (vecp == nullptr) { + return false; + } VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); new_hdr->data = (*self)->ToOffset(vecp); hdr = new_hdr; } } + if (n > hdr->num_elements) { + // Reserved capacity and elements removed by shrinking are not cleared. + T *data = (*self)->ToAddress(hdr->data); + memset(static_cast(data + hdr->num_elements), 0, + (n - hdr->num_elements) * sizeof(T)); + } hdr->num_elements = n; + return true; } template @@ -642,6 +693,9 @@ inline MessageType *PayloadBuffer::NewMessage(PayloadBuffer **self, uint32_t size, BufferOffset offset) { void *msg = Allocate(self, size); + if (msg == nullptr) { + return nullptr; + } BufferOffset *p = (*self)->ToAddress(offset); *p = (*self)->ToOffset(msg); return reinterpret_cast(msg); diff --git a/toolbelt/payload_buffer_test.cc b/toolbelt/payload_buffer_test.cc index 357a771..84430ff 100644 --- a/toolbelt/payload_buffer_test.cc +++ b/toolbelt/payload_buffer_test.cc @@ -723,6 +723,218 @@ TEST(BufferTest, Resizeable) { free(pb); } +TEST(BufferTest, FullFixedBufferSetsAllocationFailed) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_FALSE(pb->AllocationFailed()); + + ASSERT_EQ(nullptr, PayloadBuffer::Allocate(&pb, 1024)); + ASSERT_TRUE(pb->AllocationFailed()); + + // The writer still treats the buffer as valid, so smaller allocations keep + // working. A reader that masks only the bitmap flag sees an invalid magic. + ASSERT_TRUE(pb->IsValidMagic()); + ASSERT_FALSE(pb->IsMoveable()); + ASSERT_NE(toolbelt::kFixedBufferMagic, pb->magic & toolbelt::kBitMapMask); + ASSERT_NE(nullptr, PayloadBuffer::Allocate(&pb, 16)); + ASSERT_TRUE(pb->AllocationFailed()); +} + +TEST(BufferTest, ZeroSizeAllocationIsNotAFailure) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_EQ(nullptr, PayloadBuffer::Allocate(&pb, 0)); + ASSERT_FALSE(pb->AllocationFailed()); + + ASSERT_NE(nullptr, PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader))); + VectorHeader *hdr = pb->ToAddress(pb->message); + ASSERT_TRUE(PayloadBuffer::VectorReserve(&pb, hdr, 0)); + ASSERT_TRUE(PayloadBuffer::VectorResize(&pb, hdr, 0)); + ASSERT_FALSE(pb->AllocationFailed()); +} + +TEST(BufferTest, MainMessageAndMetadataFailCleanly) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + + ASSERT_EQ(nullptr, PayloadBuffer::AllocateMainMessage(&pb, 1024)); + ASSERT_EQ(0u, pb->message); + + char md[1024] = {}; + ASSERT_FALSE(PayloadBuffer::AllocateMetadata(&pb, md, sizeof(md))); + ASSERT_EQ(0u, pb->metadata); + ASSERT_TRUE(PayloadBuffer::AllocateMetadata(&pb, md, 16)); + ASSERT_NE(0u, pb->metadata); + ASSERT_TRUE(pb->AllocationFailed()); +} + +TEST(BufferTest, SetStringFailureKeepsOldString) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_NE(nullptr, PayloadBuffer::AllocateMainMessage(&pb, 32)); + BufferOffset offset = pb->message; + + ASSERT_NE(nullptr, PayloadBuffer::SetString(&pb, std::string("hello"), offset)); + ASSERT_FALSE(pb->AllocationFailed()); + + ASSERT_EQ(nullptr, + PayloadBuffer::SetString(&pb, std::string(1000, 'x'), offset)); + ASSERT_TRUE(pb->AllocationFailed()); + ASSERT_EQ("hello", pb->GetString(offset)); + + absl::Span span = PayloadBuffer::AllocateString(&pb, 1000, offset); + ASSERT_TRUE(span.empty()); + ASSERT_EQ(nullptr, span.data()); + ASSERT_EQ("hello", pb->GetString(offset)); +} + +TEST(BufferTest, SetStringFailureOnEmptyString) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_NE(nullptr, PayloadBuffer::AllocateMainMessage(&pb, 32)); + BufferOffset offset = pb->message; + + ASSERT_EQ(nullptr, + PayloadBuffer::SetString(&pb, std::string(1000, 'x'), offset)); + ASSERT_EQ(0u, *pb->ToAddress(offset)); + ASSERT_EQ("", pb->GetString(offset)); +} + +TEST(BufferTest, VectorFailureLeavesVectorIntact) { + alignas(8) char buffer[512] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_NE(nullptr, + PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader))); + BufferOffset hdr_offset = pb->message; + + uint32_t pushed = 0; + for (;;) { + VectorHeader *hdr = pb->ToAddress(hdr_offset); + if (!PayloadBuffer::VectorPush(&pb, hdr, pushed + 1)) { + break; + } + pushed++; + ASSERT_LT(pushed, 1000u); + } + ASSERT_GT(pushed, 0u); + ASSERT_TRUE(pb->AllocationFailed()); + + VectorHeader *hdr = pb->ToAddress(hdr_offset); + ASSERT_EQ(pushed, hdr->num_elements); + for (uint32_t i = 0; i < pushed; i++) { + ASSERT_EQ(i + 1, pb->VectorGet(hdr, i)); + } + + BufferOffset data = hdr->data; + ASSERT_FALSE(PayloadBuffer::VectorReserve(&pb, hdr, 1000)); + ASSERT_FALSE(PayloadBuffer::VectorResize(&pb, hdr, 1000)); + ASSERT_EQ(pushed, hdr->num_elements); + ASSERT_EQ(data, hdr->data); + for (uint32_t i = 0; i < pushed; i++) { + ASSERT_EQ(i + 1, pb->VectorGet(hdr, i)); + } +} + +TEST(BufferTest, EmptyVectorFailure) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_NE(nullptr, + PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader))); + VectorHeader *hdr = pb->ToAddress(pb->message); + + ASSERT_FALSE(PayloadBuffer::VectorReserve(&pb, hdr, 1000)); + ASSERT_FALSE(PayloadBuffer::VectorResize(&pb, hdr, 1000)); + ASSERT_EQ(0u, hdr->num_elements); + ASSERT_EQ(0u, hdr->data); +} + +TEST(BufferTest, NewMessageFailureLeavesOffset) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_NE(nullptr, PayloadBuffer::AllocateMainMessage(&pb, 16)); + BufferOffset field = pb->message; + + ASSERT_EQ(nullptr, PayloadBuffer::NewMessage(&pb, 1024, field)); + ASSERT_EQ(0u, *pb->ToAddress(field)); + ASSERT_TRUE(pb->AllocationFailed()); +} + +TEST(BufferTest, SmallBlocksExhaustFixedBuffer) { + alignas(8) char buffer[1024] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), true); + + int allocated = 0; + for (;;) { + void *p = PayloadBuffer::Allocate(&pb, 16); + if (p == nullptr) { + break; + } + memset(p, 0xa5, 16); + allocated++; + ASSERT_LT(allocated, 1000); + } + ASSERT_GT(allocated, 0); + ASSERT_TRUE(pb->AllocationFailed()); + ASSERT_TRUE(pb->IsValidMagic()); + ASSERT_TRUE(pb->BitmapsEnabled()); +} + +TEST(BufferTest, VectorResizeZeroesNewElements) { + alignas(8) char buffer[1024]; + memset(buffer, 0xff, sizeof(buffer)); + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), false); + ASSERT_NE(nullptr, + PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader))); + VectorHeader *hdr = pb->ToAddress(pb->message); + + ASSERT_TRUE(PayloadBuffer::VectorReserve(&pb, hdr, 8)); + ASSERT_TRUE(PayloadBuffer::VectorResize(&pb, hdr, 8)); + for (int i = 0; i < 8; i++) { + ASSERT_EQ(0u, pb->VectorGet(hdr, i)); + } + + uint32_t *data = pb->ToAddress(hdr->data); + for (uint32_t i = 0; i < 8; i++) { + data[i] = 100 + i; + } + ASSERT_TRUE(PayloadBuffer::VectorResize(&pb, hdr, 2)); + ASSERT_TRUE(PayloadBuffer::VectorResize(&pb, hdr, 6)); + ASSERT_EQ(100u, pb->VectorGet(hdr, 0)); + ASSERT_EQ(101u, pb->VectorGet(hdr, 1)); + for (int i = 2; i < 6; i++) { + ASSERT_EQ(0u, pb->VectorGet(hdr, i)); + } +} + +TEST(BufferTest, SmallBlockUsesFreeListWhenRunDoesNotFit) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), true); + ASSERT_TRUE(pb->BitmapsEnabled()); + + void *p = PayloadBuffer::Allocate(&pb, 16); + ASSERT_NE(nullptr, p); + ASSERT_FALSE(pb->AllocationFailed()); + memset(p, 0xa5, 16); + + void *q = PayloadBuffer::Realloc(&pb, p, 24); + ASSERT_NE(nullptr, q); + ASSERT_EQ(0xa5, static_cast(q)[15]); + pb->Free(q); + + void *r = PayloadBuffer::Allocate(&pb, 16); + ASSERT_NE(nullptr, r); + pb->Free(r); + ASSERT_FALSE(pb->AllocationFailed()); + ASSERT_TRUE(pb->IsValidMagic()); +} + +TEST(BufferTest, PrimeBitmapAllocatorFailsInFullBuffer) { + alignas(8) char buffer[256] = {}; + PayloadBuffer *pb = new (buffer) PayloadBuffer(sizeof(buffer), true); + ASSERT_FALSE(PayloadBuffer::PrimeBitmapAllocator(&pb, 128)); + ASSERT_TRUE(pb->AllocationFailed()); +} + int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv);