blob: 967964065baeedaab598ff4bc00c8125af83b529 [file]
// 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 "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;
// 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&) {}
};
const Layout kLayouts[] = {
Layout(0, 1),
Layout(1, 1),
Layout(3, 2),
Layout(64, 16),
Layout(128, 64),
};
template <typename Prefix, typename Suffix>
class TestFramingAllocator : public FramingAllocator<Prefix, Suffix> {
public:
TestFramingAllocator(pw::Allocator& allocator)
: FramingAllocator<Prefix, Suffix>(allocator) {}
using FramingAllocator<Prefix, Suffix>::GetFrameLayout;
using FramingAllocator<Prefix, Suffix>::IsValid;
using FramingAllocator<Prefix, Suffix>::GetPrefix;
using FramingAllocator<Prefix, Suffix>::GetData;
using FramingAllocator<Prefix, Suffix>::GetSuffix;
};
class FramingAllocatorTest : public ::testing::Test {
protected:
void SetUp() override { NonTrivial::count = 0; }
pw::allocator::test::AllocatorForTest<2048> allocator_;
};
TEST_F(FramingAllocatorTest, GetFrameLayoutVoidTrivial) {
for (const auto& layout : kLayouts) {
Layout frame_layout =
TestFramingAllocator<void, Trivial>::GetFrameLayout(layout);
size_t expected_size = 2 * sizeof(size_t);
expected_size += layout.alignment() + layout.size();
expected_size += alignof(Trivial) + sizeof(Trivial);
EXPECT_EQ(frame_layout.size(), expected_size);
EXPECT_EQ(frame_layout.alignment(), alignof(size_t));
}
}
TEST_F(FramingAllocatorTest, GetFrameLayoutTrivialVoid) {
for (const auto& layout : kLayouts) {
Layout frame_layout =
TestFramingAllocator<Trivial, void>::GetFrameLayout(layout);
size_t expected_size = pw::AlignUp(sizeof(Trivial), alignof(size_t));
expected_size += 2 * sizeof(size_t);
expected_size += layout.alignment() + layout.size();
EXPECT_EQ(frame_layout.size(), expected_size);
EXPECT_EQ(frame_layout.alignment(), alignof(size_t));
}
}
TEST_F(FramingAllocatorTest, GetFrameLayoutTrivialAlignedTrivial) {
for (const auto& layout : kLayouts) {
Layout frame_layout =
TestFramingAllocator<Trivial, AlignedTrivial>::GetFrameLayout(layout);
size_t expected_size = pw::AlignUp(sizeof(Trivial), alignof(size_t));
expected_size += 3 * sizeof(size_t);
expected_size += layout.alignment() + layout.size();
expected_size += alignof(AlignedTrivial) + sizeof(AlignedTrivial);
EXPECT_EQ(frame_layout.size(), expected_size);
EXPECT_EQ(frame_layout.alignment(), alignof(size_t));
}
}
TEST_F(FramingAllocatorTest, AllocateDeallocateTrivialTrivial) {
TestFramingAllocator<Trivial, Trivial> framing_allocator(allocator_);
void* ptr = framing_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 = framing_allocator.GetPrefix(ptr);
Trivial* suffix = framing_allocator.GetSuffix(ptr);
prefix->value = 0x12345678;
suffix->value = 0x87654321;
EXPECT_EQ(framing_allocator.GetPrefix(ptr)->value, 0x12345678u);
EXPECT_EQ(framing_allocator.GetSuffix(ptr)->value, 0x87654321u);
// Confirm GetData works.
EXPECT_EQ(framing_allocator.GetData(prefix), ptr);
framing_allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, AllocateDeallocateTrivialAlignedTrivial) {
TestFramingAllocator<Trivial, AlignedTrivial> framing_allocator(allocator_);
void* ptr = framing_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Trivial* prefix = framing_allocator.GetPrefix(ptr);
AlignedTrivial* suffix = framing_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(framing_allocator.GetPrefix(ptr)->value, 0xAAAAAAAAu);
EXPECT_EQ(framing_allocator.GetSuffix(ptr)->value, 0xBBBBBBBBu);
// Confirm GetData works.
EXPECT_EQ(framing_allocator.GetData(prefix), ptr);
framing_allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, ResizeTrivialTrivial) {
TestFramingAllocator<Trivial, Trivial> framing_allocator(allocator_);
void* ptr = framing_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
framing_allocator.GetPrefix(ptr)->value = 0x11111111;
framing_allocator.GetSuffix(ptr)->value = 0x22222222;
EXPECT_TRUE(framing_allocator.Resize(ptr, sizeof(uint32_t) * 2));
EXPECT_EQ(framing_allocator.GetPrefix(ptr)->value, 0x11111111u);
EXPECT_EQ(framing_allocator.GetSuffix(ptr)->value, 0x22222222u);
framing_allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, ReallocateTrivialTrivial) {
TestFramingAllocator<Trivial, Trivial> framing_allocator(allocator_);
void* ptr = framing_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
framing_allocator.GetPrefix(ptr)->value = 0x33333333;
framing_allocator.GetSuffix(ptr)->value = 0x44444444;
void* new_ptr = framing_allocator.Reallocate(ptr, Layout::Of<uint64_t>());
ASSERT_NE(new_ptr, nullptr);
EXPECT_EQ(framing_allocator.GetPrefix(new_ptr)->value, 0x33333333u);
EXPECT_EQ(framing_allocator.GetSuffix(new_ptr)->value, 0x44444444u);
framing_allocator.Deallocate(new_ptr);
}
TEST_F(FramingAllocatorTest, NewDelete) {
TestFramingAllocator<Trivial, Trivial> framing_allocator(allocator_);
EXPECT_EQ(NonTrivial::count, 0);
NonTrivial* ptr = framing_allocator.New<NonTrivial>(0x123u);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(NonTrivial::count, 1);
EXPECT_EQ(ptr->value, 0x123u);
framing_allocator.Delete(ptr);
EXPECT_EQ(NonTrivial::count, 0);
}
TEST_F(FramingAllocatorTest, MakeUnique) {
TestFramingAllocator<Trivial, Trivial> framing_allocator(allocator_);
EXPECT_EQ(NonTrivial::count, 0);
{
auto unique_ptr = framing_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> framing_allocator(allocator_);
EXPECT_EQ(NonTrivial::count, 0);
{
auto shared_ptr = framing_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> framing_allocator(allocator_);
void* ptr = framing_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Derived* prefix = framing_allocator.GetPrefix(ptr);
Base* suffix = framing_allocator.GetSuffix(ptr);
prefix->x = 1;
prefix->y = 2;
suffix->x = 3;
EXPECT_EQ(framing_allocator.GetPrefix(ptr)->x, 1u);
EXPECT_EQ(framing_allocator.GetPrefix(ptr)->y, 2u);
EXPECT_EQ(framing_allocator.GetSuffix(ptr)->x, 3u);
framing_allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, PrefixOnly) {
TestFramingAllocator<Trivial, void> framing_allocator(allocator_);
void* ptr = framing_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Trivial* prefix = framing_allocator.GetPrefix(ptr);
prefix->value = 0xDEADBEEF;
EXPECT_EQ(framing_allocator.GetPrefix(ptr)->value, 0xDEADBEEFu);
// Confirm GetData works.
EXPECT_EQ(framing_allocator.GetData(prefix), ptr);
framing_allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, SuffixOnly) {
TestFramingAllocator<void, Trivial> framing_allocator(allocator_);
void* ptr = framing_allocator.Allocate(Layout::Of<uint32_t>());
ASSERT_NE(ptr, nullptr);
Trivial* suffix = framing_allocator.GetSuffix(ptr);
suffix->value = 0xFEEDFACE;
EXPECT_EQ(framing_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(framing_allocator.GetData(frame), data);
break;
}
}
framing_allocator.Deallocate(ptr);
}
TEST_F(FramingAllocatorTest, AllocatedPointerIsValid) {
TestFramingAllocator<size_t, size_t> framing_allocator(allocator_);
auto bytes = framing_allocator.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
EXPECT_TRUE(framing_allocator.IsValid(bytes.get()));
}
TEST_F(FramingAllocatorTest, MisalignedPointerIsInvalid) {
TestFramingAllocator<size_t, size_t> framing_allocator(allocator_);
auto bytes = framing_allocator.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
EXPECT_FALSE(framing_allocator.IsValid(bytes.get() + 1));
}
TEST_F(FramingAllocatorTest, OutOfRangePointerIsInvalid) {
TestFramingAllocator<size_t, size_t> framing_allocator(allocator_);
pw::ByteSpan buffer = allocator_.buffer();
EXPECT_FALSE(framing_allocator.IsValid(buffer.data() - 1));
EXPECT_FALSE(framing_allocator.IsValid(buffer.data() + buffer.size()));
EXPECT_FALSE(framing_allocator.IsValid(nullptr));
EXPECT_FALSE(framing_allocator.IsValid(
reinterpret_cast<void*>(std::numeric_limits<uintptr_t>::max())));
}
TEST_F(FramingAllocatorTest, UnrecognizedPointerIsInvalid) {
pw::allocator::test::AllocatorForTest<256> allocator1;
pw::allocator::test::AllocatorForTest<256> allocator2;
TestFramingAllocator<void, Trivial> framing_allocator1(allocator1);
TestFramingAllocator<void, Trivial> framing_allocator2(allocator2);
auto bytes = framing_allocator1.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
EXPECT_TRUE(framing_allocator1.IsValid(bytes.get()));
EXPECT_FALSE(framing_allocator2.IsValid(bytes.get()));
}
TEST_F(FramingAllocatorTest, CorruptedFrameIsInvalid) {
TestFramingAllocator<size_t, size_t> framing_allocator(allocator_);
auto bytes = framing_allocator.MakeUnique<std::byte[]>(16);
ASSERT_NE(bytes, nullptr);
auto* data = reinterpret_cast<uint8_t*>(bytes.get());
for (size_t i = 1; i <= sizeof(size_t) * 2; ++i) {
EXPECT_TRUE(framing_allocator.IsValid(data));
*(data - i) ^= 0xFF;
EXPECT_FALSE(framing_allocator.IsValid(data));
*(data - i) ^= 0xFF;
EXPECT_TRUE(framing_allocator.IsValid(data));
}
}
#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);
}
#elif PW_NC_TEST(GetFrameLayoutFailsWhenPrefixAlignmentTooLarge)
PW_NC_EXPECT("alignof\(Prefix\) cannot exceed alignof\(size_t\)");
void GetFrameLayoutLargePrefixAlign() {
TestFramingAllocator<AlignedTrivial, void>::GetFrameLayout(Layout(0, 1));
}
#endif
} // namespace