blob: f808c16cb8e5f6925cb2dda2bc4f4fcb567433b0 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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
*
* http://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 <string.h>
#include <gtest/gtest.h>
#include <lib/support/PrivateHeap.h>
namespace {
constexpr size_t kBlockHeaderSize = sizeof(internal::PrivateHeapBlockHeader);
// Splitting block tests assume we know the size
static_assert(kBlockHeaderSize == 16, "Test assumes block size of 16");
// helper class for allocating things
template <size_t kSize>
class PrivateHeapAllocator
{
public:
PrivateHeapAllocator() { PrivateHeapInit(mHeap.buffer, kSize); }
void * HeapAlloc(size_t size) { return PrivateHeapAlloc(mHeap.buffer, size); }
void HeapFree(void * buffer) { PrivateHeapFree(buffer); }
void * HeapRealloc(void * buffer, size_t size) { return PrivateHeapRealloc(mHeap.buffer, buffer, size); }
private:
struct alignas(kPrivateHeapAllocationAlignment)
{
uint8_t buffer[kSize];
} mHeap;
};
TEST(TestPrivateHeap, TestSingleHeapAllocAndFree)
{
PrivateHeapAllocator<16 + 2 * kBlockHeaderSize> allocator;
EXPECT_EQ(nullptr, allocator.HeapAlloc(17)); // insufficient size
void * ptr = allocator.HeapAlloc(16);
ASSERT_NE(nullptr, ptr);
EXPECT_EQ(nullptr, allocator.HeapAlloc(1)); // insufficient size
memset(ptr, 0xab, 16);
allocator.HeapFree(ptr);
// allocate different sizes on this heap, see how that goes
for (size_t i = 1; i < 17; ++i)
{
ptr = allocator.HeapAlloc(i);
ASSERT_NE(nullptr, ptr);
EXPECT_EQ(nullptr, allocator.HeapAlloc(17 - i)); // insufficient size
allocator.HeapFree(ptr);
}
}
TEST(TestPrivateHeap, TestSplitHeapAllocAndFree)
{
PrivateHeapAllocator<128> allocator;
// allocator state:
// <HDR-FREE> 96 <HDR-END>
void * p1 = allocator.HeapAlloc(30);
ASSERT_NE(nullptr, p1);
// allocator state:
// <HDR-IN_USE> 32 <HRD-FREE> 48 <HDR-END>
void * p2 = allocator.HeapAlloc(4);
ASSERT_NE(nullptr, p2);
// allocator state:
// <HDR-IN_USE> 32 <HRD-IN_USE> 8 <HDR-FREE> 24 <HDR-END>
allocator.HeapFree(p1);
// allocator state:
// <HDR-FREE> 32 <HRD-IN_USE> 8 <HDR-FREE> 24 <HDR-END>
allocator.HeapFree(p2);
// allocator state:
// <HDR-FREE> 96 <HDR-END>
p1 = allocator.HeapAlloc(90);
ASSERT_NE(nullptr, p1);
allocator.HeapFree(p1);
}
TEST(TestPrivateHeap, TestFreeMergeNext)
{
PrivateHeapAllocator<5 * 16> allocator;
void * p1 = allocator.HeapAlloc(16);
void * p2 = allocator.HeapAlloc(16);
ASSERT_NE(nullptr, p1);
ASSERT_NE(nullptr, p2);
EXPECT_EQ(nullptr, allocator.HeapAlloc(1));
memset(p1, 0xab, 16);
memset(p2, 0xcd, 16);
// freeing 1,2 should clear space
allocator.HeapFree(p1);
allocator.HeapFree(p2);
p1 = allocator.HeapAlloc(3 * 16);
ASSERT_NE(nullptr, p1);
allocator.HeapFree(p1);
}
TEST(TestPrivateHeap, TestFreeMergePrevious)
{
PrivateHeapAllocator<5 * 16> allocator;
void * p1 = allocator.HeapAlloc(16);
void * p2 = allocator.HeapAlloc(16);
ASSERT_NE(nullptr, p1);
ASSERT_NE(nullptr, p2);
EXPECT_EQ(nullptr, allocator.HeapAlloc(1));
memset(p1, 0xab, 16);
memset(p2, 0xcd, 16);
// freeing 2,1 should clear space
allocator.HeapFree(p2);
allocator.HeapFree(p1);
p1 = allocator.HeapAlloc(3 * 16);
ASSERT_NE(nullptr, p1);
allocator.HeapFree(p1);
}
TEST(TestPrivateHeap, TestFreeMergePreviousAndNext)
{
PrivateHeapAllocator<7 * 16> allocator;
void * p1 = allocator.HeapAlloc(16);
void * p2 = allocator.HeapAlloc(16);
void * p3 = allocator.HeapAlloc(16);
ASSERT_NE(nullptr, p1);
ASSERT_NE(nullptr, p2);
ASSERT_NE(nullptr, p3);
EXPECT_EQ(nullptr, allocator.HeapAlloc(1));
memset(p1, 0xab, 16);
memset(p2, 0xcd, 16);
memset(p3, 0xef, 16);
allocator.HeapFree(p1);
allocator.HeapFree(p3);
// we have 2 slots of size 16 available now
EXPECT_EQ(nullptr, allocator.HeapAlloc(17));
// Freeing p2 makes enoug space
allocator.HeapFree(p2);
p1 = allocator.HeapAlloc(5 * 16);
ASSERT_NE(nullptr, p1);
allocator.HeapFree(p1);
}
TEST(TestPrivateHeap, TestMultipleMerge)
{
PrivateHeapAllocator<32 * kBlockHeaderSize> allocator;
// 31 blocks available for alloc
void * p1 = allocator.HeapAlloc(2 * kBlockHeaderSize); // uses up 3 blocks
void * p2 = allocator.HeapAlloc(5 * kBlockHeaderSize); // uses up 6 blocks
void * p3 = allocator.HeapAlloc(8 * kBlockHeaderSize); // uses up 9 blocks
void * p4 = allocator.HeapAlloc(1 * kBlockHeaderSize); // uses up 2 blocks
void * p5 = allocator.HeapAlloc(7 * kBlockHeaderSize); // uses up 8 blocks
void * p6 = allocator.HeapAlloc(2 * kBlockHeaderSize); // uses up 2 (last given)
ASSERT_NE(nullptr, p1);
ASSERT_NE(nullptr, p2);
ASSERT_NE(nullptr, p3);
ASSERT_NE(nullptr, p4);
ASSERT_NE(nullptr, p5);
ASSERT_NE(nullptr, p6);
allocator.HeapFree(p3);
allocator.HeapFree(p4);
// 10 blocks available (9 from p3 without HDR and 2 from p4 + HDR)
p3 = allocator.HeapAlloc(10 * kBlockHeaderSize);
ASSERT_NE(nullptr, p3);
EXPECT_EQ(nullptr, allocator.HeapAlloc(1)); // full
allocator.HeapFree(p6);
allocator.HeapFree(p5);
allocator.HeapFree(p3);
allocator.HeapFree(p2);
allocator.HeapFree(p1);
p1 = allocator.HeapAlloc(30 * kBlockHeaderSize);
ASSERT_NE(nullptr, p1);
allocator.HeapFree(p1);
}
TEST(TestPrivateHeap, TestForwardFreeAndRealloc)
{
constexpr int kNumBlocks = 16;
PrivateHeapAllocator<(2 * kNumBlocks + 1) * kBlockHeaderSize> allocator;
void * ptrs[kNumBlocks];
for (auto & ptr : ptrs)
{
ptr = allocator.HeapAlloc(kBlockHeaderSize);
ASSERT_NE(nullptr, ptr);
memset(ptr, 0xab, kBlockHeaderSize);
}
// heap looks like:
/// |HDR| 16 |HDR| 16 |HDR| ..... |HDR| 16 |HDR|
// free each block from the start and re-allocate into a bigger block
for (size_t i = 1; i < kNumBlocks; ++i)
{
allocator.HeapFree(ptrs[0]);
allocator.HeapFree(ptrs[i]);
ptrs[0] = allocator.HeapAlloc((1 + 2 * i) * kBlockHeaderSize);
ASSERT_NE(nullptr, ptrs[0]);
}
allocator.HeapFree(ptrs[0]);
}
TEST(TestPrivateHeap, TestBackwardFreeAndRealloc)
{
constexpr int kNumBlocks = 16;
PrivateHeapAllocator<(2 * kNumBlocks + 1) * kBlockHeaderSize> allocator;
void * ptrs[kNumBlocks];
for (auto & ptr : ptrs)
{
ptr = allocator.HeapAlloc(kBlockHeaderSize);
ASSERT_NE(nullptr, ptr);
memset(ptr, 0xab, kBlockHeaderSize);
}
// heap looks like:
/// |HDR| 16 |HDR| 16 |HDR| ..... |HDR| 16 |HDR|
// free each block from the send and re-allocate into a bigger block
for (size_t i = 1; i < kNumBlocks; ++i)
{
allocator.HeapFree(ptrs[kNumBlocks - 1]);
allocator.HeapFree(ptrs[kNumBlocks - i - 1]);
ptrs[kNumBlocks - 1] = allocator.HeapAlloc((1 + 2 * i) * kBlockHeaderSize);
ASSERT_NE(nullptr, ptrs[kNumBlocks - 1]);
}
allocator.HeapFree(ptrs[kNumBlocks - 1]);
}
// Fills the data with a known pattern
void FillKnownPattern(void * buffer, size_t size, uint8_t start)
{
uint8_t * p = static_cast<uint8_t *>(buffer);
size_t cnt = start;
while (cnt++ < size)
{
uint8_t value = static_cast<uint8_t>(cnt * 31 + 7);
*p = value;
}
}
// checks if the specified buffer has the given pattern in it
bool IsKnownPattern(void * buffer, size_t size, uint8_t start)
{
uint8_t * p = static_cast<uint8_t *>(buffer);
size_t cnt = start;
while (cnt++ < size)
{
uint8_t value = static_cast<uint8_t>(cnt * 31 + 7);
if (*p != value)
{
return false;
}
}
return true;
}
TEST(TestPrivateHeap, TestRealloc)
{
PrivateHeapAllocator<6 * 16> allocator;
void * p1 = allocator.HeapRealloc(nullptr, 16); // malloc basically
ASSERT_NE(p1, nullptr);
FillKnownPattern(p1, 16, 11);
void * p2 = allocator.HeapRealloc(p1, 8); // resize, should fit
EXPECT_EQ(p1, p2);
EXPECT_TRUE(IsKnownPattern(p1, 8, 11));
p2 = allocator.HeapRealloc(p1, 16); // resize, should fit
EXPECT_EQ(p1, p2);
EXPECT_TRUE(IsKnownPattern(p2, 8, 11)); // only 8 bytes are guaranteed
FillKnownPattern(p1, 16, 33);
p2 = allocator.HeapRealloc(p1, 32); // resize, does not fit. This frees p1
ASSERT_NE(p2, nullptr);
EXPECT_NE(p2, p1); // new reallocation occurred
EXPECT_TRUE(IsKnownPattern(p2, 16, 33));
void * p3 = allocator.HeapAlloc(48); // insufficient heap for this
EXPECT_EQ(p3, nullptr);
p1 = allocator.HeapRealloc(p2, 16); // reallocation does not change block size
EXPECT_EQ(p1, p2);
p3 = allocator.HeapAlloc(48); // still insufficient heap for this
EXPECT_EQ(p3, nullptr);
p2 = allocator.HeapRealloc(p1, 48); // insufficient heap, p1 is NOT freed
EXPECT_EQ(p2, nullptr);
p2 = allocator.HeapRealloc(p1, 48); // Repeat the test to ensure p1 is not freed
EXPECT_EQ(p2, nullptr);
allocator.HeapFree(p1);
p3 = allocator.HeapAlloc(48); // above free should have made sufficient space
ASSERT_NE(p3, nullptr);
allocator.HeapFree(p3);
}
} // namespace