blob: 1709fb991fb458fb0cf9646d6d79bec4ce28387b [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fidl/llcpp/aligned.h>
#include <lib/fidl/llcpp/buffer_then_heap_allocator.h>
#include <list>
#include <gtest/gtest.h>
TEST(BufferThenHeapAllocator, MultipleArgumentMake) {
struct A {
A(int64_t x, bool y) : x(x), y(y) {}
int64_t x;
bool y;
};
fidl::BufferThenHeapAllocator<2048> allocator;
fidl::tracking_ptr<A> ptr = allocator.make<A>(1, true);
EXPECT_EQ(ptr->x, 1);
EXPECT_EQ(ptr->y, true);
}
TEST(BufferThenHeapAllocator, AllocationLayout) {
fidl::BufferThenHeapAllocator<2048> allocator;
fidl::tracking_ptr<uint8_t> ptr1 = allocator.make<uint8_t>();
fidl::tracking_ptr<uint8_t> ptr2 = allocator.make<uint8_t>();
fidl::tracking_ptr<uint64_t[]> ptr3 = allocator.make<uint64_t[]>(2);
fidl::tracking_ptr<uint16_t> ptr4 = allocator.make<uint16_t>();
// Alignment.
EXPECT_TRUE(reinterpret_cast<uintptr_t>(ptr1.get()) % FIDL_ALIGNMENT == 0);
EXPECT_TRUE(reinterpret_cast<uintptr_t>(ptr2.get()) % FIDL_ALIGNMENT == 0);
EXPECT_TRUE(reinterpret_cast<uintptr_t>(ptr3.get()) % FIDL_ALIGNMENT == 0);
EXPECT_TRUE(reinterpret_cast<uintptr_t>(ptr4.get()) % FIDL_ALIGNMENT == 0);
// Ensure objects don't overlap.
// The +1 is to get to the end of the object.
EXPECT_LE(ptr1.get() + 1, ptr2.get());
EXPECT_LE(ptr2.get() + 1, reinterpret_cast<uint8_t*>(ptr3.get()));
EXPECT_LE(ptr3.get() + 1, reinterpret_cast<uint64_t*>(ptr4.get()));
}
struct DestructCounter {
DestructCounter() : count(nullptr) {}
DestructCounter(int* count) : count(count) {}
~DestructCounter() { ++*count; }
int* count;
};
TEST(BufferThenHeapAllocator, SingleItemDestructor) {
int destructCountA = 0;
int destructCountB = 0;
int destructCountC = 0;
{
fidl::BufferThenHeapAllocator<2048> allocator;
{
allocator.make<DestructCounter>(&destructCountA);
allocator.make<DestructCounter>(&destructCountB);
allocator.make<DestructCounter>(&destructCountC);
}
EXPECT_EQ(destructCountA, 0);
EXPECT_EQ(destructCountB, 0);
EXPECT_EQ(destructCountC, 0);
}
EXPECT_EQ(destructCountA, 1);
EXPECT_EQ(destructCountB, 1);
EXPECT_EQ(destructCountC, 1);
}
TEST(BufferThenHeapAllocator, ResetDestructor) {
int destructCountA = 0;
int destructCountB = 0;
int destructCountC = 0;
{
fidl::BufferThenHeapAllocator<2048> allocator;
allocator.make<DestructCounter>(&destructCountA);
allocator.make<DestructCounter>(&destructCountB);
allocator.make<DestructCounter>(&destructCountC);
EXPECT_EQ(destructCountA, 0);
EXPECT_EQ(destructCountB, 0);
EXPECT_EQ(destructCountC, 0);
allocator.inner_allocator().reset();
EXPECT_EQ(destructCountA, 1);
EXPECT_EQ(destructCountB, 1);
EXPECT_EQ(destructCountC, 1);
allocator.make<DestructCounter>(&destructCountA);
allocator.make<DestructCounter>(&destructCountB);
allocator.make<DestructCounter>(&destructCountC);
EXPECT_EQ(destructCountA, 1);
EXPECT_EQ(destructCountB, 1);
EXPECT_EQ(destructCountC, 1);
}
EXPECT_EQ(destructCountA, 2);
EXPECT_EQ(destructCountB, 2);
EXPECT_EQ(destructCountC, 2);
}
TEST(BufferThenHeapAllocator, ArrayDestructor) {
constexpr int n = 3;
int destructCounts[n] = {};
{
fidl::BufferThenHeapAllocator<2048> allocator;
{
fidl::tracking_ptr<DestructCounter[]> ptr = allocator.make<DestructCounter[]>(n);
for (int i = 0; i < n; i++) {
ptr[i].count = &destructCounts[i];
}
}
for (int i = 0; i < n; i++) {
EXPECT_EQ(destructCounts[i], 0);
}
}
for (int i = 0; i < n; i++) {
EXPECT_EQ(destructCounts[i], 1);
}
}
TEST(BufferThenHeapAllocator, PrimitiveEightBytesEach) {
// Primitives will each use 8 bytes because allocations maintain FIDL_ALIGNMENT.
fidl::BufferThenHeapAllocator<64> allocator;
uint8_t* last_ptr = nullptr;
for (int i = 0; i < 8; i++) {
fidl::tracking_ptr<uint16_t> ptr = allocator.make<uint16_t>();
uint8_t* ptr_as_byte = reinterpret_cast<uint8_t*>(ptr.get());
EXPECT_GE(ptr_as_byte - last_ptr, 8);
last_ptr = ptr_as_byte;
}
}
TEST(BufferThenHeapAllocator, PrimitiveArrayFullSpace) {
// Primitives using at least 2 byte alignment should be able to allocate the
// full space. There should be no metadata.
// Currently (in the name of keeping the allocator interface simple), there is
// no way to verify the internal allocator state, in that all 32 bytes were
// consumed.
fidl::BufferThenHeapAllocator<32> allocator;
fidl::tracking_ptr<uint16_t[]> ptr = allocator.make<uint16_t[]>(16);
for (int i = 0; i < 16; i++)
EXPECT_EQ(ptr[i], 0);
}
TEST(BufferThenHeapAllocator, EmptyAllocator) {
// In some implementations, it might be possible for uninitialized fields to trigger bad behavior
// for instance, uninitialized destructor metadata could be misinterpreted.
fidl::BufferThenHeapAllocator<2048> allocator;
}
template <size_t NBytes>
struct DestructCounterBuffer {
DestructCounterBuffer() : count_(nullptr) {}
DestructCounterBuffer(uint32_t* count) : count_(count) { *count_ = 0; }
~DestructCounterBuffer() {
if (count_) {
++*count_;
}
}
uint32_t* count_ = nullptr;
uint8_t buf_[NBytes];
};
TEST(BufferThenHeapAllocator, TooSmallAllocatorWorksAnyway) {
constexpr uint32_t kAllocationCount = 64;
constexpr size_t kBufferThenHeapAllocatorSize = 1;
constexpr size_t kPerAllocationSize = 1024;
uint32_t destruction_count = 0;
using Buffer = DestructCounterBuffer<kPerAllocationSize>;
std::list<fidl::tracking_ptr<Buffer>> allocations;
{ // scope allocator
fidl::BufferThenHeapAllocator<kBufferThenHeapAllocatorSize> allocator;
for (uint32_t i = 0; i < kAllocationCount; ++i) {
auto ptr = allocator.make<Buffer>(&destruction_count);
EXPECT_TRUE(ptr.get() != nullptr);
allocations.push_back(std::move(ptr));
}
} // ~allocator
// None of the items are destructed yet because they were all allocated on the heap.
EXPECT_EQ(0u, destruction_count);
// Now destruct and deallocate all the allocations.
allocations.clear();
EXPECT_EQ(kAllocationCount, destruction_count);
}
TEST(BufferThenHeapAllocator, InternalThenHeapFallback) {
constexpr uint32_t kAllocationCount = 64;
constexpr size_t kBufferThenHeapAllocatorSize = 1024;
constexpr size_t kPerAllocationSize = 768;
uint32_t destruction_count = 0;
using Buffer = DestructCounterBuffer<kPerAllocationSize>;
std::list<fidl::tracking_ptr<Buffer>> allocations;
{ // scope allocator
fidl::BufferThenHeapAllocator<kBufferThenHeapAllocatorSize> allocator;
for (uint32_t i = 0; i < kAllocationCount; ++i) {
auto ptr = allocator.make<Buffer>(&destruction_count);
EXPECT_TRUE(ptr.get() != nullptr);
if (i == 0) {
// Check that the pointer points to the buffer within the allocator.
EXPECT_TRUE((reinterpret_cast<std::uintptr_t>(ptr.get()) >=
reinterpret_cast<std::uintptr_t>(&allocator)) &&
(reinterpret_cast<std::uintptr_t>(ptr.get()) <=
reinterpret_cast<std::uintptr_t>(&allocator + sizeof(allocator))));
} else {
// Check that the pointer does not point to the buffer within the allocator.
EXPECT_TRUE((reinterpret_cast<std::uintptr_t>(ptr.get()) <
reinterpret_cast<std::uintptr_t>(&allocator)) ||
(reinterpret_cast<std::uintptr_t>(ptr.get()) >
reinterpret_cast<std::uintptr_t>(&allocator + sizeof(allocator))));
}
allocations.push_back(std::move(ptr));
}
} // ~allocator
// 1 of the items are destructed because 1 was in the allocator.
EXPECT_EQ(1u, destruction_count);
// The first item is an unowned_ptr_t, so it's fine that it's about to be deleted - it won't run
// the destructor or double-free its ptr. The fact that its presently dangling is expected as
// allocations by a BufferThenHeapAllocator<> cannot be assumed to out-last the allocator.
//
// Now destruct and deallocate all the allocations.
allocations.clear();
EXPECT_EQ(kAllocationCount, destruction_count);
}
TEST(BufferThenHeapAllocator, InternalAllocationTest) {
uint32_t destruct_count = 0;
{
fidl::BufferThenHeapAllocator<2048> allocator;
{
auto destruct_counter = allocator.make<DestructCounterBuffer<100>>(&destruct_count);
// Check that the pointer points to the buffer within the allocator.
EXPECT_TRUE((reinterpret_cast<std::uintptr_t>(destruct_counter.get()) >=
reinterpret_cast<std::uintptr_t>(&allocator)) &&
(reinterpret_cast<std::uintptr_t>(destruct_counter.get()) <=
reinterpret_cast<std::uintptr_t>(&allocator + sizeof(allocator))));
}
EXPECT_EQ(0u, destruct_count);
}
EXPECT_EQ(1u, destruct_count);
}
TEST(BufferThenHeapAllocator, FailoverAllocationTest) {
uint32_t destruct_count = 0;
{
fidl::tracking_ptr<DestructCounterBuffer<2048>> destruct_counter;
{
fidl::BufferThenHeapAllocator<10> allocator;
// Make this big enough so it has to be heap allocated.
destruct_counter = allocator.make<DestructCounterBuffer<2048>>(&destruct_count);
// Check that the pointer does not point to the buffer within the allocator.
EXPECT_TRUE((reinterpret_cast<std::uintptr_t>(destruct_counter.get()) <
reinterpret_cast<std::uintptr_t>(&allocator)) ||
(reinterpret_cast<std::uintptr_t>(destruct_counter.get()) >
reinterpret_cast<std::uintptr_t>(&allocator + sizeof(allocator))));
EXPECT_EQ(0u, destruct_count);
}
// Failover is the heap so it is still available until destruct_counter goes out of scope.
EXPECT_EQ(0u, destruct_count);
}
EXPECT_EQ(1u, destruct_count);
}
TEST(BufferThenHeapAllocator, FailoverArrayAllocation) {
constexpr size_t kArraySize = 1000;
fidl::BufferThenHeapAllocator<10> allocator;
auto array = allocator.make<uint64_t[]>(kArraySize);
// Write to each so ASAN can pick up on bad accesses.
for (size_t i = 0; i < kArraySize; i++) {
array[i] = i;
}
}
TEST(BufferThenHeapAllocator, FailoverSingleEntryArrayAllocation) {
constexpr size_t kPerAllocationSize = 1024;
fidl::BufferThenHeapAllocator<1> allocator;
using Buffer = DestructCounterBuffer<kPerAllocationSize>;
auto array = allocator.make<Buffer[]>(1);
array[0].buf_[0] = 0xab;
}