blob: 4a5af13fad8af51db5c887357ecf9f7ade7930ac [file] [log] [blame]
// Copyright 2023 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/trivial-allocator/posix.h>
#include <lib/trivial-allocator/sealed-page-allocator.h>
#ifdef __Fuchsia__
#include <lib/trivial-allocator/zircon.h>
#endif
#include <array>
#include <gtest/gtest.h>
namespace {
template <class Memory, size_t Reserve, typename... Args>
void SealedPageAllocatorTestWithReserve(Args&&... args) {
trivial_allocator::SealedPageAllocator<Memory, Reserve> allocator(args...);
auto& memory = allocator.memory();
static_assert(std::is_same_v<decltype(memory), Memory&>);
const auto& const_allocator = allocator;
auto& const_memory = const_allocator.memory();
static_assert(std::is_same_v<decltype(const_memory), const Memory&>);
const size_t pagesize = const_memory.page_size();
struct Live {
decltype(allocator(std::declval<size_t&>(), size_t{})) allocation;
int* ptr = nullptr;
};
// The allocator should keep exactly this many released allocations writable.
constexpr size_t kUnsealedCount = Reserve + 1;
std::array<Live, kUnsealedCount> live;
for (auto& l : live) {
size_t size = 1;
l.allocation = allocator(size, 1);
EXPECT_TRUE(l.allocation);
EXPECT_EQ(pagesize, size);
int* iptr = reinterpret_cast<int*>(l.allocation.get());
EXPECT_EQ(0, *iptr);
*iptr = 17;
l.ptr = reinterpret_cast<int*>(l.allocation.release());
EXPECT_EQ(iptr, l.ptr);
}
// After all those are released, they should still be writable.
for (auto& l : live) {
volatile int* vptr = l.ptr;
EXPECT_EQ(17, *vptr);
*vptr = 23;
}
for (auto& l : live) {
EXPECT_EQ(23, *l.ptr);
}
// These allocations should push all those out so they get sealed.
std::array<Live, kUnsealedCount> extra;
for (auto& l : extra) {
size_t size = 1;
l.allocation = allocator(size, 1);
EXPECT_TRUE(l.allocation);
EXPECT_EQ(pagesize, size);
int* iptr = reinterpret_cast<int*>(l.allocation.get());
EXPECT_EQ(0, *iptr);
*iptr = 17;
l.ptr = reinterpret_cast<int*>(l.allocation.release());
EXPECT_EQ(iptr, l.ptr);
}
// The old pointers should now be read-only.
for (auto& l : live) {
volatile int* vptr = l.ptr;
EXPECT_EQ(23, *vptr);
EXPECT_DEATH({ *vptr = 42; }, "") << "unsealed count " << kUnsealedCount;
}
// Finish off the allocator so everything is sealed.
std::move(allocator).Seal();
// These pointers should now be sealed too.
for (auto& l : extra) {
volatile int* vptr = l.ptr;
EXPECT_EQ(17, *vptr);
EXPECT_DEATH({ *vptr = 23; }, "");
}
// Allocate and release some pages but don't seal them.
for (auto& l : live) {
size_t size = 1;
l.allocation = allocator(size, 1);
EXPECT_TRUE(l.allocation);
EXPECT_EQ(pagesize, size);
int* iptr = reinterpret_cast<int*>(l.allocation.get());
EXPECT_EQ(0, *iptr);
*iptr = 17;
l.ptr = reinterpret_cast<int*>(l.allocation.release());
EXPECT_EQ(iptr, l.ptr);
}
// Get a fresh allocator instance and some fresh allocations.
allocator = decltype(allocator)(args...);
for (auto& l : live) {
size_t size = 1;
l.allocation = allocator(size, 1);
EXPECT_TRUE(l.allocation);
EXPECT_EQ(pagesize, size);
int* iptr = reinterpret_cast<int*>(l.allocation.get());
EXPECT_EQ(0, *iptr);
*iptr = 17;
l.ptr = reinterpret_cast<int*>(l.allocation.release());
EXPECT_EQ(iptr, l.ptr);
}
// Destroy the allocator without sealing.
allocator = decltype(allocator)();
// All those live allocations should have been unmapped.
for (auto& l : live) {
volatile int* vptr = l.ptr;
EXPECT_DEATH({ *vptr = 42; }, "");
}
}
template <class Memory, typename... Args>
void SealedPageAllocatorTest(Args&&... args) {
SealedPageAllocatorTestWithReserve<Memory, 0>(args...);
SealedPageAllocatorTestWithReserve<Memory, 1>(args...);
SealedPageAllocatorTestWithReserve<Memory, 2>(args...);
SealedPageAllocatorTestWithReserve<Memory, 3>(args...);
}
TEST(TrivialAllocatorTests, SealedPageAllocatorMmap) {
ASSERT_NO_FATAL_FAILURE(SealedPageAllocatorTest<trivial_allocator::PosixMmap>());
}
#ifdef __Fuchsia__
TEST(TrivialAllocatorTests, SealedPageAllocatorVmar) {
ASSERT_NO_FATAL_FAILURE(
SealedPageAllocatorTest<trivial_allocator::ZirconVmar>(*zx::vmar::root_self()));
}
#endif
} // namespace