blob: 3059c2010dabef658b5d55a9d68b5f5b81135fbc [file] [edit]
// Copyright 2026 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_allocator/framing_allocator.h"
#include <cstddef>
#include <cstring>
#include <optional>
#include "pw_allocator/testing.h"
#include "pw_compilation_testing/negative_compilation.h"
#include "pw_unit_test/framework.h"
namespace {
using ::pw::allocator::FramingAllocator;
using ::pw::allocator::Layout;
using ::pw::allocator::internal::FrameError;
// Test types
struct Trivial {
uint32_t value;
};
// This type is trivially copyable, but has a non-default alignment.
struct alignas(16) AlignedTrivial {
uint32_t value;
};
struct Base {
uint32_t x;
};
struct Derived : public Base {
uint32_t y;
};
// Non-trivial type for the *allocated* data, not the prefix/suffix.
struct NonTrivial {
static int count;
uint32_t value;
NonTrivial() : value(0) { count++; }
NonTrivial(uint32_t v) : value(v) { count++; }
~NonTrivial() { count--; }
};
int NonTrivial::count = 0;
struct NotDefaultConstructible {
NotDefaultConstructible() = delete;
};
struct NotTriviallyCopyable {
NotTriviallyCopyable() = default;
NotTriviallyCopyable(const NotTriviallyCopyable&) {}
};
// An "error handler" that records the first error encountered, allowing
// failures to be detected and inspected without crashing.
struct FrameErrorRecorder {
static inline std::optional<FrameError> error = std::nullopt;
static inline const void* ptr1 = nullptr;
static inline size_t val1 = 0;
static inline const void* ptr2 = nullptr;
static inline size_t val2 = 0;
static void Reset() {
error.reset();
ptr1 = nullptr;
val1 = 0;
ptr2 = nullptr;
val2 = 0;
}
static void HandleError(FrameError err,
const void* p1 = nullptr,
size_t v1 = 0,
const void* p2 = nullptr,
size_t v2 = 0) {
if (!error.has_value()) {
error = err;
ptr1 = p1;
val1 = v1;
ptr2 = p2;
val2 = v2;
}
}
};
template <typename Prefix, typename Suffix>
class TestFramingAllocator
: public FramingAllocator<Prefix, Suffix, FrameErrorRecorder> {
public:
using Base = FramingAllocator<Prefix, Suffix, FrameErrorRecorder>;
TestFramingAllocator(pw::Allocator& allocator) : Base(allocator) {}
using Base::GetData;
using Base::GetFrame;
using Base::GetPrefix;
using Base::GetSuffix;
using Base::GetAllocatedLayout;
using Base::GetInfo;
using Base::GetRequestedLayout;
using Base::GetUsableLayout;
using Base::Recognizes;
using typename Base::InfoType;
};
class NoUsableLayoutAllocator : public pw::allocator::ForwardingAllocator {
public:
explicit NoUsableLayoutAllocator(pw::Allocator& allocator)
: ForwardingAllocator(allocator) {}
private:
pw::Result<Layout> DoGetInfo(InfoType info_type,
const void* ptr) const override {
if (info_type == InfoType::kUsableLayoutOf) {
return pw::Status::Unimplemented();
}
return ForwardingAllocator::DoGetInfo(info_type, ptr);
}
};
class FramingAllocatorTest : public ::testing::Test {
protected:
void SetUp() override {
NonTrivial::count = 0;
FrameErrorRecorder::Reset();
}
pw::allocator::test::AllocatorForTest<2048> allocator_;
NoUsableLayoutAllocator no_usable_layout_{allocator_};
};
TEST_F(FramingAllocatorTest, AllocateDeallocateTrivialTrivial) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
// Check if pointer is aligned.
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % alignof(size_t), 0u);
Trivial* prefix = allocator.GetPrefix(ptr);
Trivial* suffix = allocator.GetSuffix(ptr);
prefix->value = 0x12345678;
suffix->value = 0x87654321;
EXPECT_EQ(allocator.GetPrefix(ptr)->value, 0x12345678u);
EXPECT_EQ(allocator.GetSuffix(ptr)->value, 0x87654321u);
// Confirm GetData works.
EXPECT_EQ(allocator.GetData(prefix), ptr);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, AllocateDeallocateTrivialAlignedTrivial) {
TestFramingAllocator<Trivial, AlignedTrivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Trivial* prefix = allocator.GetPrefix(ptr);
AlignedTrivial* suffix = allocator.GetSuffix(ptr);
EXPECT_EQ(reinterpret_cast<uintptr_t>(prefix) % alignof(Trivial), 0u);
EXPECT_EQ(reinterpret_cast<uintptr_t>(suffix) % alignof(AlignedTrivial), 0u);
prefix->value = 0xAAAAAAAA;
suffix->value = 0xBBBBBBBB;
EXPECT_EQ(allocator.GetPrefix(ptr)->value, 0xAAAAAAAAu);
EXPECT_EQ(allocator.GetSuffix(ptr)->value, 0xBBBBBBBBu);
// Confirm GetData works.
EXPECT_EQ(allocator.GetData(prefix), ptr);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, ResizeTrivialTrivial) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
allocator.GetPrefix(ptr)->value = 0x11111111;
allocator.GetSuffix(ptr)->value = 0x22222222;
EXPECT_TRUE(allocator.Resize(ptr, sizeof(uint32_t) * 4));
EXPECT_EQ(allocator.GetPrefix(ptr)->value, 0x11111111u);
EXPECT_EQ(allocator.GetSuffix(ptr)->value, 0x22222222u);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, ReallocateTrivialTrivial) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
allocator.GetPrefix(ptr)->value = 0x33333333;
allocator.GetSuffix(ptr)->value = 0x44444444;
void* new_ptr = allocator.Reallocate(ptr, Layout::Of<uint64_t>());
ASSERT_NE(new_ptr, nullptr);
EXPECT_EQ(allocator.GetPrefix(new_ptr)->value, 0x33333333u);
EXPECT_EQ(allocator.GetSuffix(new_ptr)->value, 0x44444444u);
allocator.Deallocate(new_ptr);
}
TEST_F(FramingAllocatorTest, NewDelete) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
EXPECT_EQ(NonTrivial::count, 0);
NonTrivial* ptr = allocator.New<NonTrivial>(0x123u);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(ptr->value, 0x123u);
allocator.Delete(ptr);
EXPECT_EQ(NonTrivial::count, 0);
}
TEST_F(FramingAllocatorTest, MakeUnique) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
EXPECT_EQ(NonTrivial::count, 0);
{
auto unique_ptr = allocator.MakeUnique<NonTrivial>(0x456u);
ASSERT_NE(unique_ptr.get(), nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(unique_ptr->value, 0x456u);
}
EXPECT_EQ(NonTrivial::count, 0);
}
TEST_F(FramingAllocatorTest, MakeShared) {
#if PW_ALLOCATOR_HAS_ATOMICS
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
EXPECT_EQ(NonTrivial::count, 0);
{
auto shared_ptr = allocator.MakeShared<NonTrivial>(0x789u);
ASSERT_NE(shared_ptr.get(), nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(shared_ptr->value, 0x789u);
}
EXPECT_EQ(NonTrivial::count, 0);
#endif
}
TEST_F(FramingAllocatorTest, DerivedTypesAsFrame) {
TestFramingAllocator<Derived, Base> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Derived* prefix = allocator.GetPrefix(ptr);
Base* suffix = allocator.GetSuffix(ptr);
prefix->x = 1;
prefix->y = 2;
suffix->x = 3;
EXPECT_EQ(allocator.GetPrefix(ptr)->x, 1u);
EXPECT_EQ(allocator.GetPrefix(ptr)->y, 2u);
EXPECT_EQ(allocator.GetSuffix(ptr)->x, 3u);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, PrefixOnly) {
TestFramingAllocator<Trivial, void> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Trivial* prefix = allocator.GetPrefix(ptr);
prefix->value = 0xDEADBEEF;
EXPECT_EQ(allocator.GetPrefix(ptr)->value, 0xDEADBEEFu);
// Confirm GetData works.
EXPECT_EQ(allocator.GetData(prefix), ptr);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutAllocateDeallocate) {
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(allocator.GetUsableLayout(ptr).status(),
pw::Status::Unimplemented());
Trivial* prefix = allocator.GetPrefix(ptr);
ASSERT_NE(prefix, nullptr);
prefix->value = 0xDEADBEEF;
EXPECT_EQ(allocator.GetPrefix(ptr)->value, 0xDEADBEEFu);
EXPECT_EQ(allocator.GetFrame(ptr), prefix);
EXPECT_EQ(allocator.GetData(prefix), ptr);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
allocator.Deallocate(ptr);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutResize) {
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
allocator.GetPrefix(ptr)->value = 0x11111111;
EXPECT_TRUE(allocator.Resize(ptr, sizeof(uint32_t) * 4));
EXPECT_EQ(allocator.GetPrefix(ptr)->value, 0x11111111u);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutReallocate) {
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
void* ptr = allocator.Reallocate(nullptr, Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
allocator.GetPrefix(ptr)->value = 0x33333333;
// In-place reallocation via Resize succeeds without needing GetUsableLayout.
void* new_ptr = allocator.Reallocate(ptr, Layout::Of<uint32_t[2]>());
ASSERT_NE(new_ptr, nullptr);
EXPECT_EQ(new_ptr, ptr);
EXPECT_EQ(allocator.GetPrefix(new_ptr)->value, 0x33333333u);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
// When in-place Resize fails (e.g. blocked by a subsequent allocation),
// Reallocate cannot copy without GetUsableLayout and returns nullptr.
void* blocker = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(blocker, nullptr);
EXPECT_EQ(allocator.Reallocate(new_ptr, Layout::Of<uint32_t[8]>()), nullptr);
EXPECT_EQ(allocator.GetPrefix(new_ptr)->value, 0x33333333u);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
allocator.Deallocate(blocker);
allocator.Deallocate(new_ptr);
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutNewDelete) {
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
EXPECT_EQ(NonTrivial::count, 0);
NonTrivial* ptr = allocator.New<NonTrivial>(0x123u);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(ptr->value, 0x123u);
allocator.Delete(ptr);
EXPECT_EQ(NonTrivial::count, 0);
NonTrivial* bounded = allocator.New<NonTrivial[3]>();
ASSERT_NE(bounded, nullptr);
EXPECT_EQ(NonTrivial::count, 3);
allocator.Delete<NonTrivial[3]>(bounded);
EXPECT_EQ(NonTrivial::count, 0);
NonTrivial* unbounded = allocator.New<NonTrivial[]>(5);
ASSERT_NE(unbounded, nullptr);
EXPECT_EQ(NonTrivial::count, 5);
allocator.Delete<NonTrivial[]>(unbounded, 5);
EXPECT_EQ(NonTrivial::count, 0);
NonTrivial* array = allocator.New<NonTrivial[4]>();
ASSERT_NE(array, nullptr);
EXPECT_EQ(NonTrivial::count, 4);
allocator.DeleteArray(array, 4);
EXPECT_EQ(NonTrivial::count, 0);
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutMakeUnique) {
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
EXPECT_EQ(NonTrivial::count, 0);
{
auto unique_ptr = allocator.MakeUnique<NonTrivial>(0x456u);
ASSERT_NE(unique_ptr.get(), nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(unique_ptr->value, 0x456u);
}
EXPECT_EQ(NonTrivial::count, 0);
{
auto unique_arr = allocator.MakeUnique<NonTrivial[]>(3);
ASSERT_NE(unique_arr.get(), nullptr);
EXPECT_EQ(NonTrivial::count, 3);
}
EXPECT_EQ(NonTrivial::count, 0);
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutMakeShared) {
#if PW_ALLOCATOR_HAS_ATOMICS
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
EXPECT_EQ(NonTrivial::count, 0);
{
auto shared_ptr = allocator.MakeShared<NonTrivial>(0x789u);
ASSERT_NE(shared_ptr.get(), nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(shared_ptr->value, 0x789u);
}
EXPECT_EQ(NonTrivial::count, 0);
{
auto shared_arr = allocator.MakeShared<NonTrivial[]>(3);
ASSERT_NE(shared_arr.get(), nullptr);
EXPECT_EQ(NonTrivial::count, 3);
}
EXPECT_EQ(NonTrivial::count, 0);
#endif
}
TEST_F(FramingAllocatorTest, PrefixOnlyWithoutUsableLayoutQueries) {
TestFramingAllocator<Trivial, void> allocator(no_usable_layout_);
EXPECT_EQ(allocator.GetAllocated(), 0u);
auto capacity = allocator.GetCapacity();
PW_TEST_ASSERT_OK(capacity.status());
EXPECT_EQ(capacity.size(), allocator_.GetCapacity().size());
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
EXPECT_GT(allocator.GetAllocated(), 0u);
EXPECT_TRUE(allocator.MeasureFragmentation().has_value());
EXPECT_TRUE(allocator.Recognizes(ptr));
PW_TEST_EXPECT_OK(allocator.GetAllocatedLayout(ptr).status());
EXPECT_EQ(allocator.GetRequestedLayout(ptr).status(),
pw::Status::Unimplemented());
EXPECT_EQ(allocator.GetUsableLayout(ptr).status(),
pw::Status::Unimplemented());
allocator.Deallocate(ptr);
EXPECT_EQ(allocator.GetAllocated(), 0u);
}
TEST_F(FramingAllocatorTest, SuffixOnly) {
TestFramingAllocator<void, Trivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Trivial* suffix = allocator.GetSuffix(ptr);
suffix->value = 0xFEEDFACE;
EXPECT_EQ(allocator.GetSuffix(ptr)->value, 0xFEEDFACEu);
// To check GetData without a prefix, find the block that holds the ptr.
auto* data = reinterpret_cast<std::byte*>(ptr);
for (auto* block : allocator_.blocks()) {
std::byte* frame = block->UsableSpace();
if (frame < data && data < frame + block->InnerSize()) {
EXPECT_EQ(allocator.GetData(frame), data);
break;
}
}
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, AllocatedPointerIsValid) {
TestFramingAllocator<size_t, size_t> allocator(allocator_);
auto bytes = allocator.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
std::byte* data = bytes.get();
void* frame = allocator.GetFrame(data);
EXPECT_NE(frame, nullptr);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
EXPECT_EQ(allocator.GetPrefix(data), frame);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
EXPECT_EQ(allocator.GetData(frame), data);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
EXPECT_NE(allocator.GetSuffix(data), nullptr);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
}
TEST_F(FramingAllocatorTest, MisalignedPointerIsInvalid) {
TestFramingAllocator<size_t, size_t> allocator(allocator_);
auto bytes = allocator.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
std::byte* data = bytes.get();
EXPECT_EQ(allocator.GetFrame(data + 1), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kDataNotAligned);
EXPECT_EQ(FrameErrorRecorder::ptr1, data + 1);
EXPECT_EQ(FrameErrorRecorder::val1, alignof(size_t));
FrameErrorRecorder::Reset();
auto frame = reinterpret_cast<uintptr_t>(allocator.GetFrame(data));
auto* misaligned_frame = reinterpret_cast<void*>(frame + 1);
EXPECT_EQ(allocator.GetData(misaligned_frame), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, misaligned_frame);
}
TEST_F(FramingAllocatorTest, OutOfRangePointerIsInvalid) {
TestFramingAllocator<size_t, size_t> allocator(allocator_);
pw::ByteSpan buffer = allocator_.buffer();
void* non_const_null = nullptr;
auto* maxptr = reinterpret_cast<void*>(std::numeric_limits<uintptr_t>::max());
EXPECT_EQ(allocator.GetFrame(buffer.data() - 1), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kDataNotAligned);
EXPECT_EQ(FrameErrorRecorder::ptr1, buffer.data() - 1);
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetFrame(buffer.data() + buffer.size()), nullptr);
EXPECT_TRUE(FrameErrorRecorder::error.has_value());
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetFrame(non_const_null), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kDataTooSmall);
EXPECT_EQ(FrameErrorRecorder::ptr1, nullptr);
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetFrame(maxptr), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kDataNotAligned);
EXPECT_EQ(FrameErrorRecorder::ptr1, maxptr);
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetData(buffer.data() - 1), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, buffer.data() - 1);
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetData(buffer.data() + buffer.size()), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, buffer.data() + buffer.size());
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetData(non_const_null), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, nullptr);
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetData(maxptr), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, maxptr);
}
TEST_F(FramingAllocatorTest, UnrecognizedPointerIsInvalid) {
pw::allocator::test::AllocatorForTest<256> test_allocator1;
pw::allocator::test::AllocatorForTest<256> test_allocator2;
TestFramingAllocator<void, Trivial> allocator1(test_allocator1);
TestFramingAllocator<void, Trivial> allocator2(test_allocator2);
auto bytes = allocator1.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
void* data = bytes.get();
void* frame = allocator1.GetFrame(data);
EXPECT_EQ(allocator1.GetFrame(data), frame);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
EXPECT_EQ(allocator2.GetFrame(data), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, frame);
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator1.GetData(frame), data);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
EXPECT_EQ(allocator2.GetData(frame), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kUnrecognizedFrame);
EXPECT_EQ(FrameErrorRecorder::ptr1, frame);
}
TEST_F(FramingAllocatorTest, CorruptedFrameIsInvalid) {
TestFramingAllocator<size_t, size_t> allocator(allocator_);
auto bytes = allocator.MakeUnique<uint8_t[]>(16);
ASSERT_NE(bytes, nullptr);
uint8_t* data = bytes.get();
void* frame = allocator.GetFrame(data);
size_t* suffix = allocator.GetSuffix(data);
for (size_t i = 1; i <= sizeof(size_t) * 2; ++i) {
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetPrefix(data), frame);
EXPECT_EQ(allocator.GetData(frame), data);
EXPECT_EQ(allocator.GetSuffix(data), suffix);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
*(data - i) ^= 0xFF;
if (i <= sizeof(size_t)) {
EXPECT_EQ(allocator.GetSuffix(data), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kDataTooSmallForSuffix);
FrameErrorRecorder::Reset();
} else {
EXPECT_EQ(allocator.GetPrefix(data), nullptr);
EXPECT_TRUE(FrameErrorRecorder::error.has_value());
FrameErrorRecorder::Reset();
EXPECT_EQ(allocator.GetData(frame), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kFrameOffsetTooSmall);
FrameErrorRecorder::Reset();
}
*(data - i) ^= 0xFF;
EXPECT_EQ(allocator.GetPrefix(data), frame);
EXPECT_EQ(allocator.GetData(frame), data);
EXPECT_EQ(allocator.GetSuffix(data), suffix);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
}
}
TEST_F(FramingAllocatorTest, GetCapacity) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
auto capacity = allocator.GetCapacity();
PW_TEST_ASSERT_OK(capacity.status());
EXPECT_EQ(capacity.size(), allocator_.GetCapacity().size());
using InfoType = TestFramingAllocator<Trivial, Trivial>::InfoType;
auto info_capacity = allocator.GetInfo(InfoType::kCapacity, nullptr);
PW_TEST_ASSERT_OK(info_capacity);
EXPECT_EQ(info_capacity->size(), capacity.size());
}
TEST_F(FramingAllocatorTest, GetRequestedLayoutUnimplemented) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
using InfoType = TestFramingAllocator<Trivial, Trivial>::InfoType;
EXPECT_TRUE(allocator.GetRequestedLayout(ptr).status().IsUnimplemented());
EXPECT_TRUE(allocator.GetInfo(InfoType::kRequestedLayoutOf, ptr)
.status()
.IsUnimplemented());
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, GetInfoUnrecognizedOrNullPointerReturnsNotFound) {
pw::allocator::test::AllocatorForTest<256> other_allocator;
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
void* other_ptr = other_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(other_ptr, nullptr);
using InfoType = TestFramingAllocator<Trivial, Trivial>::InfoType;
// Null pointer queries should safely return NotFound, not crash.
EXPECT_TRUE(allocator.GetUsableLayout(nullptr).status().IsNotFound());
EXPECT_TRUE(allocator.GetAllocatedLayout(nullptr).status().IsNotFound());
EXPECT_TRUE(
allocator.GetInfo(InfoType::kRecognizes, nullptr).status().IsNotFound());
// Foreign pointer queries should safely return NotFound, not crash.
EXPECT_TRUE(allocator.GetUsableLayout(other_ptr).status().IsNotFound());
EXPECT_TRUE(allocator.GetAllocatedLayout(other_ptr).status().IsNotFound());
EXPECT_TRUE(allocator.GetInfo(InfoType::kRecognizes, other_ptr)
.status()
.IsNotFound());
// Unaligned pointer queries should safely return NotFound, not crash.
auto* unaligned =
reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(other_ptr) | 1);
EXPECT_TRUE(allocator.GetUsableLayout(unaligned).status().IsNotFound());
other_allocator.Deallocate(other_ptr);
}
TEST_F(FramingAllocatorTest, GetUsableLayoutPrefixOnly) {
TestFramingAllocator<Trivial, void> allocator(allocator_);
constexpr Layout layout = Layout::Of<uint32_t>();
void* ptr = allocator.Allocate(layout);
ASSERT_NE(ptr, nullptr);
auto usable_layout = allocator.GetUsableLayout(ptr);
PW_TEST_ASSERT_OK(usable_layout);
EXPECT_GE(usable_layout->size(), layout.size());
void* frame = allocator.GetFrame(ptr);
auto frame_layout =
TestFramingAllocator<Trivial, void>::GetUsableLayout(allocator_, frame);
PW_TEST_ASSERT_OK(frame_layout);
auto frame_offset =
reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(frame);
EXPECT_EQ(usable_layout->size(), frame_layout->size() - frame_offset);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, GetUsableLayoutWithSuffixBoundsUsableSpace) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
constexpr Layout layout = Layout::Of<uint32_t>();
void* ptr = allocator.Allocate(layout);
ASSERT_NE(ptr, nullptr);
Trivial* suffix = allocator.GetSuffix(ptr);
suffix->value = 0xCAFEBABE;
auto usable_layout = allocator.GetUsableLayout(ptr);
PW_TEST_ASSERT_OK(usable_layout);
// The usable layout must be bounded by the suffix offset so writing to the
// usable space does not overwrite the suffix.
auto suffix_offset =
reinterpret_cast<uintptr_t>(suffix) - reinterpret_cast<uintptr_t>(ptr);
EXPECT_EQ(usable_layout->size(), suffix_offset);
// Writing to the entire usable region should leave the suffix intact.
std::memset(ptr, 0xAA, usable_layout->size());
EXPECT_EQ(allocator.GetSuffix(ptr)->value, 0xCAFEBABEu);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, GetAllocatedLayoutPreservesFrameOverhead) {
TestFramingAllocator<Trivial, Trivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
auto allocated_layout = allocator.GetAllocatedLayout(ptr);
PW_TEST_ASSERT_OK(allocated_layout);
void* frame = allocator.GetFrame(ptr);
auto underlying_allocated =
TestFramingAllocator<Trivial, Trivial>::GetAllocatedLayout(allocator_,
frame);
PW_TEST_ASSERT_OK(underlying_allocated);
// Allocated layout should return the underlying block's allocated layout
// without subtracting the frame prefix or suffix overhead.
EXPECT_EQ(allocated_layout->size(), underlying_allocated->size());
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, GetUsableLayoutAlignmentMatchesPointer) {
TestFramingAllocator<Trivial, AlignedTrivial> allocator(allocator_);
void* ptr = allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
auto usable_layout = allocator.GetUsableLayout(ptr);
PW_TEST_ASSERT_OK(usable_layout);
// The returned alignment must divide the pointer's actual address.
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % usable_layout->alignment(), 0u);
allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, ValidateFrameDetectsSuffixPastFrameBounds) {
TestFramingAllocator<size_t, size_t> allocator(allocator_);
auto bytes = allocator.MakeUnique<uint8_t[]>(16);
ASSERT_NE(bytes.get(), nullptr);
uint8_t* data = bytes.get();
void* frame = allocator.GetFrame(data);
size_t* suffix = allocator.GetSuffix(data);
ASSERT_NE(suffix, nullptr);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
// Suffix offset is stored immediately before data.
auto* suffix_offset_ptr = reinterpret_cast<size_t*>(data) - 1;
size_t original_offset = *suffix_offset_ptr;
auto frame_usable =
TestFramingAllocator<size_t, size_t>::GetUsableLayout(allocator_, frame);
PW_TEST_ASSERT_OK(frame_usable);
size_t data_offset =
reinterpret_cast<uintptr_t>(data) - reinterpret_cast<uintptr_t>(frame);
size_t data_size = frame_usable->size() - data_offset;
// Set suffix_offset such that:
// suffix_offset <= frame_usable->size() - sizeof(size_t)
// (which would pass the old buggy check)
// BUT:
// suffix_offset > frame_usable->size() - data_offset - sizeof(size_t)
// (which should fail because suffix extends past the frame).
size_t invalid_offset = frame_usable->size() - sizeof(size_t);
ASSERT_GT(invalid_offset, data_size - sizeof(size_t));
*suffix_offset_ptr = invalid_offset;
EXPECT_EQ(allocator.GetSuffix(data), nullptr);
EXPECT_EQ(FrameErrorRecorder::error, FrameError::kDataTooSmallForSuffix);
EXPECT_EQ(FrameErrorRecorder::ptr1, data);
EXPECT_EQ(FrameErrorRecorder::val1, data_size);
EXPECT_EQ(FrameErrorRecorder::ptr2, nullptr);
EXPECT_EQ(FrameErrorRecorder::val2, invalid_offset);
FrameErrorRecorder::Reset();
// Restore
*suffix_offset_ptr = original_offset;
EXPECT_EQ(allocator.GetSuffix(data), suffix);
EXPECT_FALSE(FrameErrorRecorder::error.has_value());
}
#if PW_NC_TEST(PrefixAndSuffixCannotBothBeVoid)
PW_NC_EXPECT("prefix and suffix types cannot both be null");
void GetPrefixVoid(pw::Allocator& allocator) {
TestFramingAllocator<void, void> framing(allocator);
}
#elif PW_NC_TEST(GetPrefixFailsWhenVoid)
PW_NC_EXPECT("prefix type is void");
void GetPrefixVoid(TestFramingAllocator<void, uint16_t>& allocator, void* ptr) {
allocator.GetPrefix(ptr);
}
#elif PW_NC_TEST(GetSuffixFailsWhenVoid)
PW_NC_EXPECT("suffix type is void");
void GetSuffixVoid(TestFramingAllocator<uint16_t, void>& allocator, void* ptr) {
allocator.GetSuffix(ptr);
}
#elif PW_NC_TEST(PrefixNotDefaultConstructible)
PW_NC_EXPECT("prefix type must be void or default constructible");
void PrefixNotDef(pw::Allocator& allocator) {
FramingAllocator<NotDefaultConstructible, void> framing(allocator);
}
#elif PW_NC_TEST(SuffixNotDefaultConstructible)
PW_NC_EXPECT("suffix type must be void or default constructible");
void SuffixNotDef(pw::Allocator& allocator) {
FramingAllocator<void, NotDefaultConstructible> framing(allocator);
}
#elif PW_NC_TEST(PrefixNotTriviallyCopyable)
PW_NC_EXPECT("prefix type must be void or trivially copyable");
void PrefixNotTrivial(pw::Allocator& allocator) {
FramingAllocator<NotTriviallyCopyable, void> framing(allocator);
}
#elif PW_NC_TEST(SuffixNotTriviallyCopyable)
PW_NC_EXPECT("suffix type must be void or trivially copyable");
void SuffixNotTrivial(pw::Allocator& allocator) {
FramingAllocator<void, NotTriviallyCopyable> framing(allocator);
}
#endif
} // namespace