#include "partition_alloc/bucket_lookup.h"
#include "partition_alloc/slot_start.h"
#if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <limits>
#include <memory>
#include <random>
#include <set>
#include <tuple>
#include <vector>
#include "partition_alloc/address_space_randomization.h"
#include "partition_alloc/bounds_checks.h"
#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/dangling_raw_ptr_checks.h"
#include "partition_alloc/in_slot_metadata.h"
#include "partition_alloc/internal/partition_root_internal.h"
#include "partition_alloc/memory_reclaimer.h"
#include "partition_alloc/page_allocator_constants.h"
#include "partition_alloc/partition_address_space.h"
#include "partition_alloc/partition_alloc_base/bits.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_base/cpu.h"
#include "partition_alloc/partition_alloc_base/logging.h"
#include "partition_alloc/partition_alloc_base/numerics/checked_math.h"
#include "partition_alloc/partition_alloc_base/rand_util.h"
#include "partition_alloc/partition_alloc_base/system/sys_info.h"
#include "partition_alloc/partition_alloc_base/test/gtest_util.h"
#include "partition_alloc/partition_alloc_base/thread_annotations.h"
#include "partition_alloc/partition_alloc_base/threading/platform_thread_for_testing.h"
#include "partition_alloc/partition_alloc_config.h"
#include "partition_alloc/partition_alloc_constants.h"
#include "partition_alloc/partition_alloc_for_testing.h"
#include "partition_alloc/partition_alloc_forward.h"
#include "partition_alloc/partition_bucket.h"
#include "partition_alloc/partition_cookie.h"
#include "partition_alloc/partition_freelist_entry.h"
#include "partition_alloc/partition_page.h"
#include "partition_alloc/partition_stats.h"
#include "partition_alloc/reservation_offset_table.h"
#include "partition_alloc/scheduler_loop_quarantine_support.h"
#include "partition_alloc/slot_address_and_size.h"
#include "partition_alloc/tagging.h"
#include "partition_alloc/thread_isolation/thread_isolation.h"
#include "partition_alloc/use_death_tests.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(__ARM_FEATURE_MEMORY_TAGGING)
#include <arm_acle.h>
#endif
#if PA_BUILDFLAG(IS_POSIX)
#if PA_BUILDFLAG(IS_LINUX)
#include <linux/mman.h>
#endif #include <sys/mman.h>
#include <sys/resource.h>
#include <sys/time.h>
#endif
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_BUILDFLAG(IS_MAC)
#include <OpenCL/opencl.h>
#endif
#if PA_BUILDFLAG(IS_MAC)
#include "partition_alloc/partition_alloc_base/mac/mac_util.h"
#endif
#if PA_BUILDFLAG(ENABLE_PKEYS)
#include <sys/syscall.h>
#endif
#if PA_BUILDFLAG(IS_FUCHSIA)
#include <zircon/syscalls.h>
#elif PA_BUILDFLAG(IS_WIN)
#include <windows.h>
#elif PA_BUILDFLAG(IS_APPLE)
#include <sys/sysctl.h>
#elif PA_BUILDFLAG(IS_POSIX)
#include <unistd.h>
#endif
#if PA_BUILDFLAG(IS_LINUX) || PA_BUILDFLAG(IS_ANDROID) || \
PA_BUILDFLAG(IS_CHROMEOS)
#include "partition_alloc/partition_alloc_base/debug/proc_maps_linux.h"
#endif
#define PA_EXPECT_PTR_EQ(ptr1, ptr2) \
{ EXPECT_EQ(UntagPtr(ptr1), UntagPtr(ptr2)); }
#define PA_EXPECT_PTR_NE(ptr1, ptr2) \
{ EXPECT_NE(UntagPtr(ptr1), UntagPtr(ptr2)); }
namespace {
uint64_t AmountOfPhysicalMemory() {
#if PA_BUILDFLAG(IS_FUCHSIA)
return zx_system_get_physmem();
#elif PA_BUILDFLAG(IS_WIN)
MEMORYSTATUSEX mem_status = {.dwLength = sizeof(mem_status)};
if (GlobalMemoryStatusEx(&mem_status)) {
return mem_status.ullTotalPhys;
}
return 0;
#elif PA_BUILDFLAG(IS_APPLE)
uint64_t physical_memory;
size_t size = sizeof(physical_memory);
int rv = sysctlbyname("hw.memsize", &physical_memory, &size, nullptr, 0);
PA_CHECK(rv == 0) << "sysctlbyname(\"hw.memsize\")";
return physical_memory;
#elif PA_BUILDFLAG(IS_POSIX)
long pages = sysconf(_SC_PHYS_PAGES);
long page_size = sysconf(_SC_PAGESIZE);
if (pages >= 0 && page_size >= 0) {
return static_cast<uint64_t>(pages) * static_cast<uint64_t>(page_size);
}
return 0;
#else
return 0;
#endif
}
bool IsLargeMemoryDevice() {
return AmountOfPhysicalMemory() >= 4000ULL * 1024 * 1024;
}
bool SetAddressSpaceLimit() {
#if !PA_BUILDFLAG(PA_ARCH_CPU_64_BITS) || !PA_BUILDFLAG(IS_POSIX)
return true;
#elif PA_BUILDFLAG(IS_POSIX) && !PA_BUILDFLAG(IS_APPLE)
const size_t kAddressSpaceLimit = static_cast<size_t>(6144) * 1024 * 1024;
struct rlimit limit;
if (getrlimit(RLIMIT_DATA, &limit) != 0) {
return false;
}
if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > kAddressSpaceLimit) {
limit.rlim_cur = kAddressSpaceLimit;
if (setrlimit(RLIMIT_DATA, &limit) != 0) {
return false;
}
}
return true;
#else
return false;
#endif
}
bool ClearAddressSpaceLimit() {
#if !PA_BUILDFLAG(PA_ARCH_CPU_64_BITS) || !PA_BUILDFLAG(IS_POSIX)
return true;
#elif PA_BUILDFLAG(IS_POSIX)
struct rlimit limit;
if (getrlimit(RLIMIT_DATA, &limit) != 0) {
return false;
}
limit.rlim_cur = limit.rlim_max;
if (setrlimit(RLIMIT_DATA, &limit) != 0) {
return false;
}
return true;
#else
return false;
#endif
}
const size_t kTestSizes[] = {
1,
17,
100,
partition_alloc::internal::SystemPageSize(),
partition_alloc::internal::SystemPageSize() + 1,
partition_alloc::PartitionRoot::GetDirectMapSlotSize(100),
1 << 20,
1 << 21,
};
constexpr size_t kTestSizesCount = std::size(kTestSizes);
using FreeFunction = void (*)(partition_alloc::PartitionRoot*,
void*,
size_t,
size_t);
constexpr size_t kNoAlignment = 0;
template <
partition_alloc::AllocFlags alloc_flags,
partition_alloc::FreeFlags free_flags = partition_alloc::FreeFlags::kNone>
void AllocateRandomly(partition_alloc::PartitionRoot* root,
size_t count,
FreeFunction free_func) {
std::vector<void*> allocations(count, nullptr);
std::vector<size_t> sizes(count, 0);
for (size_t i = 0; i < count; ++i) {
const size_t size = PA_UNSAFE_TODO(
kTestSizes[partition_alloc::internal::base::RandGenerator(
kTestSizesCount)]);
allocations[i] = root->Alloc<alloc_flags>(size);
sizes[i] = size;
EXPECT_NE(nullptr, allocations[i]) << " size: " << size << " i: " << i;
}
for (size_t i = 0; i < count; ++i) {
if (allocations[i]) {
free_func(root, allocations[i], sizes[i], kNoAlignment);
}
}
}
void HandleOOM(size_t unused_size) {
PA_LOG(FATAL) << "Out of memory";
}
int g_dangling_raw_ptr_detected_count = 0;
int g_dangling_raw_ptr_released_count = 0;
class CountDanglingRawPtr {
public:
CountDanglingRawPtr() {
g_dangling_raw_ptr_detected_count = 0;
g_dangling_raw_ptr_released_count = 0;
old_detected_fn_ = partition_alloc::GetDanglingRawPtrDetectedFn();
old_released_fn_ = partition_alloc::GetDanglingRawPtrReleasedFn();
partition_alloc::SetDanglingRawPtrDetectedFn(
CountDanglingRawPtr::DanglingRawPtrDetected);
partition_alloc::SetDanglingRawPtrReleasedFn(
CountDanglingRawPtr::DanglingRawPtrReleased);
}
~CountDanglingRawPtr() {
partition_alloc::SetDanglingRawPtrDetectedFn(old_detected_fn_);
partition_alloc::SetDanglingRawPtrReleasedFn(old_released_fn_);
}
private:
static void DanglingRawPtrDetected(uintptr_t) {
g_dangling_raw_ptr_detected_count++;
}
static void DanglingRawPtrReleased(uintptr_t) {
g_dangling_raw_ptr_released_count++;
}
partition_alloc::DanglingRawPtrDetectedFn* old_detected_fn_;
partition_alloc::DanglingRawPtrReleasedFn* old_released_fn_;
};
}
namespace partition_alloc::internal {
using BucketDistribution = PartitionRoot::BucketDistribution;
using SlotSpan = SlotSpanMetadata;
const size_t kTestAllocSize = 16;
constexpr size_t kPointerOffset = 0;
#if !PA_BUILDFLAG(USE_PARTITION_COOKIE)
constexpr size_t kExtraAllocSizeWithoutMetadata = 0ull;
#else
constexpr size_t kExtraAllocSizeWithoutMetadata = kCookieSize;
#endif
const char* type_name = nullptr;
void SetDistributionForPartitionRoot(PartitionRoot* root,
BucketDistribution distribution) {
switch (distribution) {
case BucketDistribution::kNeutral:
root->ResetBucketDistributionForTesting();
break;
case BucketDistribution::kDenser:
root->SwitchToDenserBucketDistribution();
break;
}
}
struct PartitionAllocTestParam {
BucketDistribution bucket_distribution;
bool use_pkey_pool;
FreeFunction free_func;
};
const std::vector<PartitionAllocTestParam> GetPartitionAllocTestParams() {
std::vector<PartitionAllocTestParam> params;
auto free_func = [](PartitionRoot* root, void* ptr, size_t, size_t) {
root->FreeInline(ptr);
};
params.emplace_back(
PartitionAllocTestParam{BucketDistribution::kNeutral, false, free_func});
params.emplace_back(
PartitionAllocTestParam{BucketDistribution::kDenser, false, free_func});
#if PA_BUILDFLAG(ENABLE_PKEYS)
if (CPUHasPkeySupport()) {
params.emplace_back(
PartitionAllocTestParam{BucketDistribution::kNeutral, true, free_func});
params.emplace_back(
PartitionAllocTestParam{BucketDistribution::kDenser, true, free_func});
}
#endif
return params;
}
const std::vector<PartitionAllocTestParam>
GetPartitionAllocWithSizedFreeTestParams() {
auto params = GetPartitionAllocTestParams();
auto free_with_size_func = [](PartitionRoot* root, void* ptr, size_t size,
size_t) {
root->Free<FreeFlags::kWithSizeHint>(ptr, {.size = size});
};
params.emplace_back(PartitionAllocTestParam{BucketDistribution::kNeutral,
false, free_with_size_func});
params.emplace_back(PartitionAllocTestParam{BucketDistribution::kDenser,
false, free_with_size_func});
return params;
}
const std::vector<PartitionAllocTestParam>
GetPartitionAllocWithFreeWithSizeAndAlignmentTestParams() {
auto params = GetPartitionAllocTestParams();
auto free_with_size_and_alignment_func = [](PartitionRoot* root, void* ptr,
size_t size, size_t alignment) {
root->Free<FreeFlags::kWithSizeHint | FreeFlags::kWithAlignmentHint>(
ptr, {.size = size, .alignment = alignment});
};
params.emplace_back(PartitionAllocTestParam{
BucketDistribution::kNeutral, false, free_with_size_and_alignment_func});
params.emplace_back(PartitionAllocTestParam{
BucketDistribution::kDenser, false, free_with_size_and_alignment_func});
return params;
}
class PartitionAllocTest
: public testing::TestWithParam<PartitionAllocTestParam> {
protected:
class ScopedPageAllocation {
public:
ScopedPageAllocation(PartitionAllocator& allocator,
base::CheckedNumeric<size_t> npages)
: allocator_(allocator),
npages_(npages),
ptr_(static_cast<char*>(allocator_.root()->Alloc(
(npages * SystemPageSize() - ExtraAllocSize(allocator_))
.ValueOrDie(),
type_name))) {}
~ScopedPageAllocation() { allocator_.root()->Free(ptr_); }
void TouchAllPages() {
PA_UNSAFE_TODO(
memset(ptr_, 'A',
((npages_ * SystemPageSize()) - ExtraAllocSize(allocator_))
.ValueOrDie()));
}
void* PageAtIndex(size_t index) {
return PA_UNSAFE_TODO(ptr_ - kPointerOffset + (SystemPageSize() * index));
}
private:
PartitionAllocator& allocator_;
const base::CheckedNumeric<size_t> npages_;
char* ptr_;
};
PartitionAllocTest() = default;
~PartitionAllocTest() override = default;
struct PartitionTestOptions {
bool use_memory_reclaimer = false;
bool uncap_empty_slot_span_memory = false;
bool set_bucket_distribution = false;
};
void InitializeTestRoot(PartitionRoot* root,
PartitionOptions opts,
PartitionTestOptions test_opts) {
root->Init(opts);
if (test_opts.use_memory_reclaimer) {
MemoryReclaimer::Instance()->RegisterPartition(root);
}
if (test_opts.uncap_empty_slot_span_memory) {
root->UncapEmptySlotSpanMemoryForTesting();
}
if (test_opts.set_bucket_distribution) {
SetDistributionForPartitionRoot(root, GetBucketDistribution());
}
}
std::unique_ptr<PartitionRoot> CreateCustomTestRoot(
PartitionOptions opts,
PartitionTestOptions test_opts) {
PA_CHECK(opts.thread_cache == PartitionOptions::kDisabled);
auto root = std::make_unique<PartitionRoot>();
InitializeTestRoot(root.get(), opts, test_opts);
return root;
}
struct PartitionRootCustomDeleter {
void operator()(PartitionRoot* root) const {
if (root) {
ThreadCache::SwapForTesting(nullptr,
root->settings_.thread_cache_index);
root->settings_.with_thread_cache = false;
}
}
};
std::unique_ptr<PartitionRoot, PartitionRootCustomDeleter>
CreateCustomTestRootWithThreadCache(PartitionOptions opts,
PartitionTestOptions test_opts,
size_t thread_cache_index) {
PA_CHECK(thread_cache_index != 0)
<< "Thread cache index 0 is reserved for PartitionAllocatorForTesting.";
auto root = std::make_unique<PartitionRoot>();
opts.thread_cache = PartitionOptions::kEnabled;
opts.thread_cache_index = thread_cache_index;
InitializeTestRoot(root.get(), opts, test_opts);
return std::unique_ptr<PartitionRoot, PartitionRootCustomDeleter>(
root.release(), PartitionRootCustomDeleter());
}
PartitionOptions GetCommonPartitionOptions() {
PartitionOptions opts;
opts.eventually_zero_freed_memory = PartitionOptions::kEnabled;
opts.scheduler_loop_quarantine_global_config = {
.branch_capacity_in_bytes = std::numeric_limits<size_t>::max(),
.leak_on_destruction = true,
.enable_quarantine = true,
.enable_zapping = true,
};
opts.scheduler_loop_quarantine_thread_local_config = {
.branch_capacity_in_bytes = std::numeric_limits<size_t>::max(),
.leak_on_destruction = false,
.enable_quarantine = true,
.enable_zapping = true,
};
return opts;
}
void InitializeMainTestAllocators() {
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
PartitionOptions::EnableToggle enable_backup_ref_ptr =
PartitionOptions::kEnabled;
#endif
#if PA_BUILDFLAG(ENABLE_PKEYS)
int pkey = PkeyAlloc(UseThreadIsolatedPool() ? 0 : PKEY_DISABLE_WRITE);
if (pkey != -1) {
pkey_ = pkey;
}
PartitionOptions pkey_opts = GetCommonPartitionOptions();
pkey_opts.thread_isolation = ThreadIsolationOption(pkey_);
InitializeTestRoot(pkey_allocator.root(), pkey_opts,
PartitionTestOptions{.use_memory_reclaimer = true});
ThreadIsolationOption thread_isolation_opt;
if (UseThreadIsolatedPool() && pkey_ != kInvalidPkey) {
thread_isolation_opt = ThreadIsolationOption(pkey_);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
enable_backup_ref_ptr = PartitionOptions::kDisabled;
#endif
}
#endif
PartitionOptions opts = GetCommonPartitionOptions();
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
opts.backup_ref_ptr = enable_backup_ref_ptr;
#endif
#if PA_BUILDFLAG(ENABLE_PKEYS)
opts.thread_isolation = thread_isolation_opt;
#endif
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
opts.memory_tagging = {
.enabled =
partition_alloc::internal::base::CPU::GetInstanceNoAllocation()
.has_mte()
? PartitionOptions::kEnabled
: PartitionOptions::kDisabled,
};
#endif InitializeTestRoot(
allocator.root(), opts,
PartitionTestOptions{.use_memory_reclaimer = true,
.uncap_empty_slot_span_memory = true,
.set_bucket_distribution = true});
}
size_t ActualTestAllocSize() const {
return SizeToBucketSize(kTestAllocSize + ExtraAllocSize(allocator));
}
void SetUp() override {
PartitionRoot::SetStraightenLargerSlotSpanFreeListsMode(
StraightenLargerSlotSpanFreeListsMode::kOnlyWhenUnprovisioning);
PartitionRoot::SetSortSmallerSlotSpanFreeListsEnabled(true);
PartitionRoot::SetSortActiveSlotSpansEnabled(true);
PartitionAllocGlobalInit(HandleOOM);
InitializeMainTestAllocators();
test_bucket_index_ = SizeToIndex(ActualTestAllocSize());
}
void TearDown() override {
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans |
PurgeFlags::kDiscardUnusedSystemPages);
PartitionAllocGlobalUninitForTesting();
#if PA_BUILDFLAG(ENABLE_PKEYS)
if (pkey_ != kInvalidPkey) {
PkeyFree(pkey_);
}
#endif
}
size_t SizeToIndex(size_t size) const {
const auto distribution_to_use = GetBucketDistribution();
return PartitionRoot::SizeToBucketIndex(size, distribution_to_use);
}
size_t SizeToBucketSize(size_t size) const {
const auto index = SizeToIndex(size);
return PA_UNSAFE_TODO(allocator.root()->buckets_[index]).slot_size;
}
static size_t ExtraAllocSize(const PartitionAllocator& allocator) {
size_t metadata_size = 0;
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (allocator.root()->brp_enabled()) {
metadata_size = kInSlotMetadataSizeAdjustment;
}
#endif return kExtraAllocSizeWithoutMetadata + metadata_size;
}
size_t GetNumPagesPerSlotSpan(size_t size) {
size_t real_size = size + ExtraAllocSize(allocator);
size_t bucket_index = SizeToIndex(real_size);
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
return (bucket->num_system_pages_per_slot_span +
(NumSystemPagesPerPartitionPage() - 1)) /
NumSystemPagesPerPartitionPage();
}
const SlotSpan* GetFullSlotSpan(size_t size) {
size_t real_size = size + ExtraAllocSize(allocator);
size_t bucket_index = SizeToIndex(real_size);
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
size_t num_slots =
(bucket->num_system_pages_per_slot_span * SystemPageSize()) /
bucket->slot_size;
UntaggedSlotStart first = UntaggedSlotStart::Unchecked(0);
UntaggedSlotStart last = UntaggedSlotStart::Unchecked(0);
size_t i;
for (i = 0; i < num_slots; ++i) {
void* ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
if (!i) {
first = SlotStart::Unchecked(ptr).Untag();
} else if (i == num_slots - 1) {
last = SlotStart::Unchecked(ptr).Untag();
}
}
EXPECT_EQ(SlotSpan::FromSlotStart(first, allocator.root()),
SlotSpan::FromSlotStart(last, allocator.root()));
if (bucket->num_system_pages_per_slot_span ==
NumSystemPagesPerPartitionPage()) {
EXPECT_EQ(first.value() & PartitionPageBaseMask(),
last.value() & PartitionPageBaseMask());
}
EXPECT_EQ(num_slots, bucket->active_slot_spans_head->num_allocated_slots);
EXPECT_EQ(nullptr, bucket->active_slot_spans_head->get_freelist_head());
EXPECT_TRUE(bucket->is_valid());
EXPECT_TRUE(bucket->active_slot_spans_head !=
SlotSpan::get_sentinel_slot_span());
EXPECT_TRUE(bucket->active_slot_spans_head->is_full());
return bucket->active_slot_spans_head;
}
void ClearEmptySlotSpanCache() {
allocator.root()->DecommitEmptySlotSpansForTesting();
}
enum ReturnNullTestMode {
kPartitionAlloc,
kPartitionRealloc,
};
void DoReturnNullTest(size_t alloc_size, ReturnNullTestMode mode) {
if (!IsLargeMemoryDevice()) {
PA_LOG(WARNING)
<< "Skipping test on this device because of crbug.com/678782";
PA_LOG(FATAL) << "Passed DoReturnNullTest";
}
ASSERT_TRUE(SetAddressSpaceLimit());
const int num_allocations = (6 * 1024 * 1024) / (alloc_size / 1024);
void** ptrs = static_cast<void**>(
allocator.root()->Alloc(num_allocations * sizeof(void*), type_name));
int i;
for (i = 0; i < num_allocations; ++i) {
switch (mode) {
case kPartitionAlloc: {
PA_UNSAFE_TODO(ptrs[i]) =
allocator.root()->Alloc<AllocFlags::kReturnNull>(alloc_size,
type_name);
break;
}
case kPartitionRealloc: {
PA_UNSAFE_TODO(ptrs[i]) =
allocator.root()->Alloc<AllocFlags::kReturnNull>(1, type_name);
PA_UNSAFE_TODO(ptrs[i]) =
allocator.root()->Realloc<AllocFlags::kReturnNull>(
PA_UNSAFE_TODO(ptrs[i]), alloc_size, type_name);
break;
}
}
if (!i) {
EXPECT_TRUE(PA_UNSAFE_TODO(ptrs[0]));
}
if (!PA_UNSAFE_TODO(ptrs[i])) {
PA_UNSAFE_TODO(ptrs[i]) =
allocator.root()->Alloc<AllocFlags::kReturnNull>(alloc_size,
type_name);
EXPECT_FALSE(PA_UNSAFE_TODO(ptrs[i]));
break;
}
}
EXPECT_LT(i, num_allocations);
for (--i; i >= 0; --i) {
allocator.root()->Free(PA_UNSAFE_TODO(ptrs[i]));
PA_UNSAFE_TODO(ptrs[i]) =
allocator.root()->Alloc<AllocFlags::kReturnNull>(alloc_size,
type_name);
EXPECT_TRUE(PA_UNSAFE_TODO(ptrs[i]));
allocator.root()->Free(PA_UNSAFE_TODO(ptrs[i]));
}
allocator.root()->Free(ptrs);
EXPECT_TRUE(ClearAddressSpaceLimit());
PA_LOG(FATAL) << "Passed DoReturnNullTest";
}
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
void RunRefCountReallocSubtest(size_t orig_size, size_t new_size);
#endif
PA_NOINLINE PA_MALLOC_FN void* Alloc(size_t size) {
return allocator.root()->Alloc(size);
}
PA_NOINLINE void Free(void* ptr) { allocator.root()->Free(ptr); }
BucketDistribution GetBucketDistribution() const {
return GetParam().bucket_distribution;
}
bool UseThreadIsolatedPool() const { return GetParam().use_pkey_pool; }
bool UseBRPPool() const {
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
return allocator.root()->brp_enabled();
#else
return false;
#endif }
partition_alloc::PartitionAllocatorForTesting allocator;
#if PA_BUILDFLAG(ENABLE_PKEYS)
partition_alloc::PartitionAllocatorForTesting pkey_allocator;
#endif
size_t test_bucket_index_;
#if PA_BUILDFLAG(ENABLE_PKEYS)
int pkey_ = kInvalidPkey;
#endif
};
#if PA_USE_DEATH_TESTS()
class PartitionAllocDeathTest : public PartitionAllocTest {};
INSTANTIATE_TEST_SUITE_P(AlternateTestParams,
PartitionAllocDeathTest,
testing::ValuesIn(GetPartitionAllocTestParams()));
#endif
namespace {
void FreeFullSlotSpan(PartitionRoot* root, const SlotSpan* slot_span) {
EXPECT_TRUE(slot_span->is_full());
size_t size = slot_span->bucket->slot_size;
size_t num_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
size;
EXPECT_EQ(num_slots, slot_span->num_allocated_slots);
uintptr_t address = SlotSpan::ToSlotSpanStart(slot_span, root).value();
size_t i;
for (i = 0; i < num_slots; ++i) {
root->Free(
internal::UntaggedSlotStart::Unchecked(address).Tag().ToObject());
address += size;
}
EXPECT_TRUE(slot_span->is_empty());
}
#if PA_BUILDFLAG(IS_LINUX) || PA_BUILDFLAG(IS_CHROMEOS)
bool CheckPageInCore(void* ptr, bool in_core) {
unsigned char ret = 0;
EXPECT_EQ(0, mincore(ptr, SystemPageSize(), &ret));
return in_core == (ret & 1);
}
#define CHECK_PAGE_IN_CORE(ptr, in_core) \
EXPECT_TRUE(CheckPageInCore(ptr, in_core))
#else
#define CHECK_PAGE_IN_CORE(ptr, in_core) (void)(0)
#endif
class MockPartitionStatsDumper : public PartitionStatsDumper {
public:
MockPartitionStatsDumper() = default;
void PartitionDumpTotals(const char* partition_name,
const PartitionMemoryStats* stats) override {
EXPECT_GE(stats->total_mmapped_bytes, stats->total_resident_bytes);
EXPECT_EQ(total_resident_bytes, stats->total_resident_bytes);
EXPECT_EQ(total_active_bytes, stats->total_active_bytes);
EXPECT_EQ(total_decommittable_bytes, stats->total_decommittable_bytes);
EXPECT_EQ(total_discardable_bytes, stats->total_discardable_bytes);
}
void PartitionsDumpBucketStats(
[[maybe_unused]] const char* partition_name,
const PartitionBucketMemoryStats* stats) override {
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->bucket_slot_size & sizeof(void*));
bucket_stats.push_back(*stats);
total_resident_bytes += stats->resident_bytes;
total_active_bytes += stats->active_bytes;
total_decommittable_bytes += stats->decommittable_bytes;
total_discardable_bytes += stats->discardable_bytes;
}
bool IsMemoryAllocationRecorded() {
return total_resident_bytes != 0 && total_active_bytes != 0;
}
const PartitionBucketMemoryStats* GetBucketStats(size_t bucket_size) {
for (auto& stat : bucket_stats) {
if (stat.bucket_slot_size == bucket_size) {
return &stat;
}
}
return nullptr;
}
private:
size_t total_resident_bytes = 0;
size_t total_active_bytes = 0;
size_t total_decommittable_bytes = 0;
size_t total_discardable_bytes = 0;
std::vector<PartitionBucketMemoryStats> bucket_stats;
};
#if PA_BUILDFLAG(IS_APPLE)
bool IsNormalBucketsAllocatedByRoot(uintptr_t address, PartitionRoot* root) {
partition_alloc::internal::PartitionSuperPageExtentEntry* extent =
root->first_extent_;
while (extent != nullptr) {
uintptr_t super_page =
partition_alloc::internal::SuperPagesBeginFromExtent(extent);
uintptr_t super_page_end =
partition_alloc::internal::SuperPagesEndFromExtent(extent);
if (super_page <= address && address < super_page_end) {
return true;
}
extent = extent->next;
}
return false;
}
bool IsDirectMapAllocatedByRoot(uintptr_t address, PartitionRoot* root) {
::partition_alloc::internal::ScopedGuard locker{
partition_alloc::internal::PartitionRootLock(root)};
partition_alloc::internal::PartitionDirectMapExtent* extent =
root->direct_map_list_;
while (extent != nullptr) {
uintptr_t super_page = PartitionMetadataPageToSuperPage(
reinterpret_cast<uintptr_t>(extent) & SystemPageBaseMask(),
root->MetadataOffset());
PA_DCHECK(!(super_page & kSuperPageOffsetMask));
uintptr_t super_page_end = super_page + extent->reservation_size;
if (super_page <= address && address < super_page_end) {
return true;
}
extent = extent->next_extent;
}
return false;
}
#endif
bool IsManagedByNormalBucketsForTesting(uintptr_t address,
PartitionRoot* root) {
return root->GetReservationOffsetTable().IsManagedByNormalBuckets(address)
#if PA_BUILDFLAG(IS_APPLE)
&& IsNormalBucketsAllocatedByRoot(address, root)
#endif ;
}
bool IsManagedByDirectMapForTesting(uintptr_t address, PartitionRoot* root) {
return root->GetReservationOffsetTable().IsManagedByDirectMap(address)
#if PA_BUILDFLAG(IS_APPLE)
&& IsDirectMapAllocatedByRoot(address, root)
#endif ;
}
bool IsManagedByNormalBucketsOrDirectMapForTesting(uintptr_t address,
PartitionRoot* root) {
return root->GetReservationOffsetTable().IsManagedByNormalBucketsOrDirectMap(
address)
#if PA_BUILDFLAG(IS_APPLE)
&& (IsManagedByNormalBucketsForTesting(address, root) ||
IsManagedByDirectMapForTesting(address, root))
#endif ;
}
}
INSTANTIATE_TEST_SUITE_P(AlternateTestParams,
PartitionAllocTest,
testing::ValuesIn(GetPartitionAllocTestParams()));
class PartitionAllocWithSizedFreeTest : public PartitionAllocTest {
public:
void FreePtr(void* ptr, size_t size) {
GetParam().free_func(allocator.root(), ptr, size, kNoAlignment);
}
};
INSTANTIATE_TEST_SUITE_P(
AlternateTestParams,
PartitionAllocWithSizedFreeTest,
testing::ValuesIn(GetPartitionAllocWithSizedFreeTestParams()));
class PartitionAllocWithFreeWithSizeAndAlignmentTest
: public PartitionAllocTest {};
INSTANTIATE_TEST_SUITE_P(
AlternateTestParams,
PartitionAllocWithFreeWithSizeAndAlignmentTest,
testing::ValuesIn(
GetPartitionAllocWithFreeWithSizeAndAlignmentTestParams()));
TEST_P(PartitionAllocWithSizedFreeTest, Basic) {
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[test_bucket_index_]);
auto* sent_slot_span = SlotSpan::get_sentinel_slot_span();
EXPECT_FALSE(bucket->empty_slot_spans_head);
EXPECT_FALSE(bucket->decommitted_slot_spans_head);
EXPECT_EQ(sent_slot_span, bucket->active_slot_spans_head);
EXPECT_EQ(nullptr, bucket->active_slot_spans_head->next_slot_span);
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
EXPECT_EQ(kPointerOffset, UntagPtr(ptr) & PartitionPageOffsetMask());
EXPECT_EQ(PartitionPageSize() + kPointerOffset,
UntagPtr(ptr) & kSuperPageOffsetMask);
FreePtr(ptr, kTestAllocSize);
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_FALSE(bucket->decommitted_slot_spans_head);
}
TEST_P(PartitionAllocTest, MultiAlloc) {
void* ptr1 = allocator.root()->Alloc(kTestAllocSize, type_name);
void* ptr2 = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr1);
EXPECT_TRUE(ptr2);
ptrdiff_t diff = UntagPtr(ptr2) - UntagPtr(ptr1);
EXPECT_EQ(static_cast<ptrdiff_t>(ActualTestAllocSize()), diff);
allocator.root()->Free(ptr2);
ptr2 = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr2);
diff = UntagPtr(ptr2) - UntagPtr(ptr1);
EXPECT_EQ(static_cast<ptrdiff_t>(ActualTestAllocSize()), diff);
allocator.root()->Free(ptr1);
ptr1 = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr1);
diff = UntagPtr(ptr2) - UntagPtr(ptr1);
EXPECT_EQ(static_cast<ptrdiff_t>(ActualTestAllocSize()), diff);
void* ptr3 = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr3);
diff = UntagPtr(ptr3) - UntagPtr(ptr1);
EXPECT_EQ(static_cast<ptrdiff_t>(ActualTestAllocSize() * 2), diff);
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr3);
}
TEST_P(PartitionAllocTest, MultiSlotSpans) {
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[test_bucket_index_]);
auto* slot_span = GetFullSlotSpan(kTestAllocSize);
FreeFullSlotSpan(allocator.root(), slot_span);
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_EQ(SlotSpan::get_sentinel_slot_span(), bucket->active_slot_spans_head);
EXPECT_EQ(nullptr, slot_span->next_slot_span);
EXPECT_EQ(0u, slot_span->num_allocated_slots);
slot_span = GetFullSlotSpan(kTestAllocSize);
auto* slot_span2 = GetFullSlotSpan(kTestAllocSize);
EXPECT_EQ(slot_span2, bucket->active_slot_spans_head);
EXPECT_EQ(nullptr, slot_span2->next_slot_span);
EXPECT_EQ(SlotSpan::ToSlotSpanStart(slot_span, allocator.root()).value() &
kSuperPageBaseMask,
SlotSpan::ToSlotSpanStart(slot_span2, allocator.root()).value() &
kSuperPageBaseMask);
FreeFullSlotSpan(allocator.root(), slot_span);
EXPECT_EQ(0u, slot_span->num_allocated_slots);
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_EQ(SlotSpan::get_sentinel_slot_span(), bucket->active_slot_spans_head);
slot_span = GetFullSlotSpan(kTestAllocSize);
EXPECT_FALSE(bucket->empty_slot_spans_head);
EXPECT_EQ(slot_span, bucket->active_slot_spans_head);
FreeFullSlotSpan(allocator.root(), slot_span);
FreeFullSlotSpan(allocator.root(), slot_span2);
EXPECT_EQ(0u, slot_span->num_allocated_slots);
EXPECT_EQ(0u, slot_span2->num_allocated_slots);
EXPECT_EQ(0u, slot_span2->num_unprovisioned_slots);
EXPECT_TRUE(slot_span2->in_empty_cache());
}
TEST_P(PartitionAllocTest, SlotSpanTransitions) {
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[test_bucket_index_]);
auto* slot_span1 = GetFullSlotSpan(kTestAllocSize);
EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
EXPECT_EQ(nullptr, slot_span1->next_slot_span);
auto* slot_span2 = GetFullSlotSpan(kTestAllocSize);
EXPECT_EQ(slot_span2, bucket->active_slot_spans_head);
EXPECT_EQ(nullptr, slot_span2->next_slot_span);
void* ptr = SlotSpan::ToSlotSpanStart(slot_span1, allocator.root())
.AsSlotStart()
.Tag()
.ToObject();
allocator.root()->Free(ptr);
EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
std::ignore = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
EXPECT_EQ(slot_span2, bucket->active_slot_spans_head->next_slot_span);
auto* slot_span3 = GetFullSlotSpan(kTestAllocSize);
EXPECT_EQ(slot_span3, bucket->active_slot_spans_head);
EXPECT_EQ(nullptr, slot_span3->next_slot_span);
ptr = SlotSpan::ToSlotSpanStart(slot_span2, allocator.root())
.AsSlotStart()
.Tag()
.ToObject();
allocator.root()->Free(ptr);
void* ptr2 = allocator.root()->Alloc(kTestAllocSize, type_name);
PA_EXPECT_PTR_EQ(ptr, ptr2);
EXPECT_EQ(slot_span2, bucket->active_slot_spans_head);
EXPECT_EQ(slot_span3, slot_span2->next_slot_span);
ptr = SlotSpan::ToSlotSpanStart(slot_span1, allocator.root())
.AsSlotStart()
.Tag()
.ToObject();
allocator.root()->Free(ptr);
ptr2 = allocator.root()->Alloc(kTestAllocSize, type_name);
PA_EXPECT_PTR_EQ(ptr, ptr2);
EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
EXPECT_EQ(slot_span2, slot_span1->next_slot_span);
FreeFullSlotSpan(allocator.root(), slot_span3);
FreeFullSlotSpan(allocator.root(), slot_span2);
FreeFullSlotSpan(allocator.root(), slot_span1);
ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, ExtraAllocSize) {
size_t slot_size = 64;
size_t bucket_index =
PartitionRoot::SizeToBucketIndex(slot_size, GetBucketDistribution());
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
ASSERT_EQ(bucket->slot_size, slot_size);
size_t requested_size1 = slot_size - ExtraAllocSize(allocator);
size_t requested_size2 = requested_size1 + 1;
void* ptr1 = allocator.root()->Alloc(requested_size1);
void* ptr2 = allocator.root()->Alloc(requested_size2);
size_t capacity1 = allocator.root()->AllocationCapacityFromSlotStart(
SlotStart::Unchecked(ptr1).Untag());
size_t capacity2 = allocator.root()->AllocationCapacityFromSlotStart(
SlotStart::Unchecked(ptr2).Untag());
EXPECT_EQ(capacity1, requested_size1);
EXPECT_LT(capacity1, capacity2);
EXPECT_LT(requested_size2, capacity2);
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
}
TEST_P(PartitionAllocTest, PreferSlotSpansWithProvisionedEntries) {
size_t size = SystemPageSize() - ExtraAllocSize(allocator);
size_t real_size = size + ExtraAllocSize(allocator);
size_t bucket_index =
PartitionRoot::SizeToBucketIndex(real_size, GetBucketDistribution());
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
ASSERT_EQ(bucket->slot_size, real_size);
size_t slots_per_span = bucket->num_system_pages_per_slot_span;
constexpr int kSpans = 10;
std::vector<std::vector<void*>> allocated_memory_spans(kSpans);
for (int span_index = 0; span_index < kSpans; span_index++) {
for (size_t i = 0; i < slots_per_span; i++) {
allocated_memory_spans[span_index].push_back(
allocator.root()->Alloc(size));
}
}
for (int span_index = kSpans - 1; span_index >= 0; span_index--) {
allocator.root()->Free(allocated_memory_spans[span_index].back());
allocated_memory_spans[span_index].pop_back();
}
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans |
PurgeFlags::kDiscardUnusedSystemPages);
std::vector<const SlotSpan*> active_slot_spans;
for (auto* span = bucket->active_slot_spans_head; span;
span = span->next_slot_span) {
active_slot_spans.push_back(span);
ASSERT_EQ(span->num_unprovisioned_slots, 1u);
ASSERT_FALSE(span->get_freelist_head());
}
constexpr size_t kSpanIndex = 5;
allocator.root()->Free(allocated_memory_spans[kSpanIndex].back());
allocated_memory_spans[kSpanIndex].pop_back();
ASSERT_TRUE(active_slot_spans[kSpanIndex]->get_freelist_head());
ASSERT_FALSE(bucket->active_slot_spans_head->get_freelist_head());
void* new_ptr = allocator.root()->Alloc(size);
auto* new_active_slot_span = active_slot_spans[kSpanIndex];
ASSERT_FALSE(new_active_slot_span->get_freelist_head());
active_slot_spans.erase(active_slot_spans.begin() + kSpanIndex);
active_slot_spans.insert(active_slot_spans.begin(), new_active_slot_span);
int index = 0;
for (auto* span = bucket->active_slot_spans_head; span;
span = span->next_slot_span) {
EXPECT_EQ(span, active_slot_spans[index]);
index++;
}
EXPECT_EQ(index, kSpans);
allocator.root()->Free(new_ptr);
for (int span_index = 0; span_index < kSpans; span_index++) {
for (void* ptr : allocated_memory_spans[span_index]) {
allocator.root()->Free(ptr);
}
}
}
TEST_P(PartitionAllocTest, FreeSlotSpanListSlotSpanTransitions) {
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[test_bucket_index_]);
size_t num_to_fill_free_list_slot_span =
PartitionPageSize() / (sizeof(SlotSpan) + ExtraAllocSize(allocator));
++num_to_fill_free_list_slot_span;
auto slot_spans =
std::make_unique<const SlotSpan*[]>(num_to_fill_free_list_slot_span);
size_t i;
for (i = 0; i < num_to_fill_free_list_slot_span; ++i) {
PA_UNSAFE_TODO(slot_spans[i]) = GetFullSlotSpan(kTestAllocSize);
}
EXPECT_EQ(PA_UNSAFE_TODO(slot_spans[num_to_fill_free_list_slot_span - 1]),
bucket->active_slot_spans_head);
for (i = 0; i < num_to_fill_free_list_slot_span; ++i) {
FreeFullSlotSpan(allocator.root(), PA_UNSAFE_TODO(slot_spans[i]));
}
EXPECT_EQ(SlotSpan::get_sentinel_slot_span(), bucket->active_slot_spans_head);
EXPECT_TRUE(bucket->empty_slot_spans_head);
auto* slot_span1 = GetFullSlotSpan(kTestAllocSize * 2);
auto* slot_span2 = GetFullSlotSpan(kTestAllocSize * 2);
FreeFullSlotSpan(allocator.root(), slot_span1);
FreeFullSlotSpan(allocator.root(), slot_span2);
for (i = 0; i < num_to_fill_free_list_slot_span; ++i) {
PA_UNSAFE_TODO(slot_spans[i]) = GetFullSlotSpan(kTestAllocSize);
}
EXPECT_EQ(PA_UNSAFE_TODO(slot_spans[num_to_fill_free_list_slot_span - 1]),
bucket->active_slot_spans_head);
for (i = 0; i < num_to_fill_free_list_slot_span; ++i) {
FreeFullSlotSpan(allocator.root(), PA_UNSAFE_TODO(slot_spans[i]));
}
EXPECT_EQ(SlotSpan::get_sentinel_slot_span(), bucket->active_slot_spans_head);
EXPECT_TRUE(bucket->empty_slot_spans_head);
}
TEST_P(PartitionAllocTest, MultiPageAllocs) {
size_t num_pages_per_slot_span = GetNumPagesPerSlotSpan(kTestAllocSize);
size_t num_slot_spans_needed =
(NumPartitionPagesPerSuperPage() - 2) / num_pages_per_slot_span;
++num_slot_spans_needed;
EXPECT_GT(num_slot_spans_needed, 1u);
auto slot_spans = std::make_unique<const SlotSpan*[]>(num_slot_spans_needed);
uintptr_t first_super_page_base = 0;
size_t i;
for (i = 0; i < num_slot_spans_needed; ++i) {
PA_UNSAFE_TODO(slot_spans[i]) = GetFullSlotSpan(kTestAllocSize);
uintptr_t slot_span_start =
SlotSpan::ToSlotSpanStart(PA_UNSAFE_TODO(slot_spans[i]),
allocator.root())
.value();
if (!i) {
first_super_page_base = slot_span_start & kSuperPageBaseMask;
}
if (i == num_slot_spans_needed - 1) {
uintptr_t second_super_page_base = slot_span_start & kSuperPageBaseMask;
uintptr_t second_super_page_offset =
slot_span_start & kSuperPageOffsetMask;
EXPECT_FALSE(second_super_page_base == first_super_page_base);
EXPECT_EQ(PartitionPageSize(), second_super_page_offset);
}
}
for (i = 0; i < num_slot_spans_needed; ++i) {
FreeFullSlotSpan(allocator.root(), PA_UNSAFE_TODO(slot_spans[i]));
}
}
TEST_P(PartitionAllocTest, Alloc) {
void* ptr = allocator.root()->Alloc(1, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
ptr =
allocator.root()->Alloc(BucketIndexLookup::kMaxBucketSize + 1, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
size_t base_size = partition_alloc::internal::base::bits::AlignUp(
ExtraAllocSize(allocator), kAlignment) -
ExtraAllocSize(allocator);
ptr = allocator.root()->Alloc(base_size + 1, type_name);
EXPECT_TRUE(ptr);
void* orig_ptr = ptr;
char* char_ptr = static_cast<char*>(ptr);
*char_ptr = 'A';
void* new_ptr = allocator.root()->Realloc(ptr, base_size + 2, type_name);
PA_EXPECT_PTR_EQ(ptr, new_ptr);
new_ptr = allocator.root()->Realloc(ptr, base_size + 1, type_name);
PA_EXPECT_PTR_EQ(ptr, new_ptr);
new_ptr = allocator.root()->Realloc(
ptr, base_size + BucketIndexLookup::kMinBucketSize, type_name);
PA_EXPECT_PTR_EQ(ptr, new_ptr);
new_ptr = allocator.root()->Realloc(
ptr, base_size + BucketIndexLookup::kMinBucketSize + 1, type_name);
PA_EXPECT_PTR_NE(new_ptr, ptr);
char* new_char_ptr = static_cast<char*>(new_ptr);
EXPECT_EQ(*new_char_ptr, 'A');
#if PA_BUILDFLAG(EXPENSIVE_DCHECKS_ARE_ON)
EXPECT_EQ(kUninitializedByte,
static_cast<unsigned char>(PA_UNSAFE_TODO(
*(new_char_ptr + BucketIndexLookup::kMinBucketSize))));
#endif
*new_char_ptr = 'B';
void* reused_ptr = allocator.root()->Alloc(base_size + 1, type_name);
PA_EXPECT_PTR_EQ(reused_ptr, orig_ptr);
allocator.root()->Free(reused_ptr);
ptr = new_ptr;
new_ptr = allocator.root()->Realloc(ptr, base_size + 1, type_name);
PA_EXPECT_PTR_EQ(new_ptr, orig_ptr);
new_char_ptr = static_cast<char*>(new_ptr);
EXPECT_EQ(*new_char_ptr, 'B');
*new_char_ptr = 'C';
ptr = new_ptr;
new_ptr = allocator.root()->Realloc(
ptr, BucketIndexLookup::kMaxBucketSize + 1, type_name);
PA_EXPECT_PTR_NE(new_ptr, ptr);
new_char_ptr = static_cast<char*>(new_ptr);
EXPECT_EQ(*new_char_ptr, 'C');
*new_char_ptr = 'D';
ptr = new_ptr;
new_ptr = allocator.root()->Realloc(
ptr, BucketIndexLookup::kMaxBucketSize * 10, type_name);
new_char_ptr = static_cast<char*>(new_ptr);
EXPECT_EQ(*new_char_ptr, 'D');
*new_char_ptr = 'E';
ptr = new_ptr;
new_ptr = allocator.root()->Realloc(
ptr, BucketIndexLookup::kMaxBucketSize * 2, type_name);
new_char_ptr = static_cast<char*>(new_ptr);
EXPECT_EQ(*new_char_ptr, 'E');
*new_char_ptr = 'F';
ptr = new_ptr;
new_ptr = allocator.root()->Realloc(ptr, base_size + 1, type_name);
PA_EXPECT_PTR_NE(new_ptr, ptr);
PA_EXPECT_PTR_EQ(new_ptr, orig_ptr);
new_char_ptr = static_cast<char*>(new_ptr);
EXPECT_EQ(*new_char_ptr, 'F');
allocator.root()->Free(new_ptr);
}
TEST_P(PartitionAllocWithSizedFreeTest, AllocSizes) {
{
void* ptr = allocator.root()->Alloc(0, type_name);
EXPECT_TRUE(ptr);
FreePtr(ptr, 0);
}
{
const size_t size = PartitionPageSize() - ExtraAllocSize(allocator);
void* ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
void* ptr2 = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr2);
FreePtr(ptr, size);
auto* slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_TRUE(slot_span->in_empty_cache());
FreePtr(ptr2, size);
}
#if !(PA_BUILDFLAG(IS_LINUX) && PA_BUILDFLAG(PA_ARCH_CPU_ARM64))
{
const size_t size =
PartitionPageSize() * kMaxPartitionPagesPerRegularSlotSpan + 1;
void* ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
PA_UNSAFE_TODO(memset(ptr, 'A', size));
void* ptr2 = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr2);
void* ptr3 = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr3);
void* ptr4 = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr4);
auto* slot_span = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr).Untag());
auto* slot_span2 =
SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr3).Untag());
EXPECT_NE(slot_span, slot_span2);
FreePtr(ptr, size);
FreePtr(ptr3, size);
FreePtr(ptr2, size);
EXPECT_TRUE(slot_span->in_empty_cache());
EXPECT_EQ(0u, slot_span->num_allocated_slots);
EXPECT_EQ(0u, slot_span->num_unprovisioned_slots);
void* new_ptr_1 = allocator.root()->Alloc(size, type_name);
PA_EXPECT_PTR_EQ(ptr2, new_ptr_1);
void* new_ptr_2 = allocator.root()->Alloc(size, type_name);
PA_EXPECT_PTR_EQ(ptr3, new_ptr_2);
FreePtr(new_ptr_1, size);
FreePtr(new_ptr_2, size);
FreePtr(ptr4, size);
#if PA_BUILDFLAG(EXPENSIVE_DCHECKS_ARE_ON)
EXPECT_EQ(
kFreedByte,
*(PA_UNSAFE_TODO(static_cast<unsigned char*>(new_ptr_1) + (size - 1))));
#endif
}
#endif
if (IsLargeMemoryDevice()) {
constexpr size_t kSize = 128 * 1024 * 1024 + 1;
void* ptr = allocator.root()->Alloc(kSize, type_name);
FreePtr(ptr, kSize);
}
{
size_t size = 20 * 1024 * 1024;
ASSERT_GT(size, BucketIndexLookup::kMaxBucketSize);
size -= SystemPageSize();
size -= 1;
void* ptr = allocator.root()->Alloc(size, type_name);
char* char_ptr = static_cast<char*>(ptr);
*(PA_UNSAFE_TODO(char_ptr + (size - 1))) = 'A';
FreePtr(ptr, size);
FreePtr(nullptr, size);
EXPECT_EQ(nullptr, allocator.root()->Alloc<AllocFlags::kReturnNull>(
3u * 1024 * 1024 * 1024, type_name));
}
}
TEST_P(PartitionAllocTest, AllocGetSizeAndStart) {
void* ptr;
size_t requested_size, actual_capacity, predicted_capacity;
requested_size = 511 - ExtraAllocSize(allocator);
predicted_capacity =
allocator.root()->AllocationCapacityFromRequestedSize(requested_size);
ptr = allocator.root()->Alloc(requested_size, type_name);
EXPECT_TRUE(ptr);
SlotStart slot_start = SlotStart::Unchecked(ptr);
actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
EXPECT_EQ(predicted_capacity, actual_capacity);
EXPECT_LT(requested_size, actual_capacity);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (UseBRPPool()) {
uintptr_t address = UntagPtr(ptr);
for (size_t offset = 0; offset < requested_size; ++offset) {
EXPECT_EQ(SlotAddressAndSize::FromBRPPool(address + offset).slot_start,
slot_start.Untag());
}
}
#endif allocator.root()->Free(ptr);
requested_size = (256 * 1024) - ExtraAllocSize(allocator);
predicted_capacity =
allocator.root()->AllocationCapacityFromRequestedSize(requested_size);
ptr = allocator.root()->Alloc(requested_size, type_name);
EXPECT_TRUE(ptr);
slot_start = SlotStart::Unchecked(ptr);
actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
EXPECT_EQ(predicted_capacity, actual_capacity);
EXPECT_EQ(requested_size, actual_capacity);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (UseBRPPool()) {
uintptr_t address = UntagPtr(ptr);
for (size_t offset = 0; offset < requested_size; offset += 877) {
EXPECT_EQ(SlotAddressAndSize::FromBRPPool(address + offset).slot_start,
slot_start.Untag());
}
}
#endif allocator.root()->Free(ptr);
size_t num = 64;
while (num * SystemPageSize() >= 1024 * 1024) {
num /= 2;
}
requested_size =
num * SystemPageSize() - SystemPageSize() - ExtraAllocSize(allocator);
predicted_capacity =
allocator.root()->AllocationCapacityFromRequestedSize(requested_size);
ptr = allocator.root()->Alloc(requested_size, type_name);
EXPECT_TRUE(ptr);
slot_start = SlotStart::Unchecked(ptr);
actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
EXPECT_EQ(predicted_capacity, actual_capacity);
EXPECT_EQ(requested_size + SystemPageSize(), actual_capacity);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (UseBRPPool()) {
uintptr_t address = UntagPtr(ptr);
for (size_t offset = 0; offset < requested_size; offset += 4999) {
EXPECT_EQ(SlotAddressAndSize::FromBRPPool(address + offset).slot_start,
slot_start.Untag());
}
}
#endif allocator.root()->Free(ptr);
requested_size =
BucketIndexLookup::kMaxBucketSize - ExtraAllocSize(allocator);
predicted_capacity =
allocator.root()->AllocationCapacityFromRequestedSize(requested_size);
ptr = allocator.root()->Alloc(requested_size, type_name);
EXPECT_TRUE(ptr);
slot_start = SlotStart::Unchecked(ptr);
actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
EXPECT_EQ(predicted_capacity, actual_capacity);
EXPECT_EQ(requested_size, actual_capacity);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (UseBRPPool()) {
uintptr_t address = UntagPtr(ptr);
for (size_t offset = 0; offset < requested_size; offset += 4999) {
EXPECT_EQ(SlotAddressAndSize::FromBRPPool(address + offset).slot_start,
slot_start.Untag());
}
}
#endif
char* char_ptr = static_cast<char*>(ptr);
*(PA_UNSAFE_TODO(char_ptr + (actual_capacity - 1))) = 'A';
allocator.root()->Free(ptr);
if (IsLargeMemoryDevice()) {
requested_size = 128 * 1024 * 1024 - 33;
predicted_capacity =
allocator.root()->AllocationCapacityFromRequestedSize(requested_size);
ptr = allocator.root()->Alloc(requested_size, type_name);
EXPECT_TRUE(ptr);
slot_start = SlotStart::Unchecked(ptr);
actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
EXPECT_EQ(predicted_capacity, actual_capacity);
EXPECT_LT(requested_size, actual_capacity);
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
if (UseBRPPool()) {
uintptr_t address = UntagPtr(ptr);
for (size_t offset = 0; offset < requested_size; offset += 16111) {
EXPECT_EQ(SlotAddressAndSize::FromBRPPool(address + offset).slot_start,
slot_start.Untag());
}
}
#endif allocator.root()->Free(ptr);
}
requested_size = MaxDirectMapped() + 1;
predicted_capacity =
allocator.root()->AllocationCapacityFromRequestedSize(requested_size);
EXPECT_EQ(requested_size, predicted_capacity);
}
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
TEST_P(PartitionAllocTest, MTEProtectsFreedPtr) {
base::CPU cpu;
if (!cpu.has_mte()) {
GTEST_SKIP();
}
ChangeMemoryTaggingModeForCurrentThread(
TagViolationReportingMode::kSynchronous);
ASSERT_TRUE(GetMemoryTaggingModeForCurrentThread() !=
TagViolationReportingMode::kDisabled)
<< "Test was built with MTE enabled and the CPU supports it, but MTE is "
"currently disabled in the device.";
size_t alloc_size = 64 - ExtraAllocSize(allocator);
uint64_t* ptr1 =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
EXPECT_TRUE(ptr1);
allocator.root()->Free(ptr1);
uint64_t* ptr2 =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
PA_EXPECT_PTR_EQ(ptr1, ptr2);
EXPECT_NE(ptr1, ptr2);
allocator.root()->Free(ptr2);
uint64_t* ptr3 =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
PA_EXPECT_PTR_EQ(ptr2, ptr3);
EXPECT_NE(ptr1, ptr3);
EXPECT_NE(ptr2, ptr3);
allocator.root()->Free(ptr3);
}
#endif
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
TEST_P(PartitionAllocTest, IsPtrWithinSameAlloc) {
if (!UseBRPPool()) {
return;
}
const size_t kMinReasonableTestSize =
partition_alloc::internal::base::bits::AlignUp(
ExtraAllocSize(allocator) + 1, kAlignment);
ASSERT_GT(kMinReasonableTestSize, ExtraAllocSize(allocator));
const size_t kSizes[] = {
kMinReasonableTestSize,
256,
SystemPageSize(),
PartitionPageSize(),
MaxRegularSlotSpanSize(),
MaxRegularSlotSpanSize() + 1,
MaxRegularSlotSpanSize() + SystemPageSize(),
MaxRegularSlotSpanSize() + PartitionPageSize(),
BucketIndexLookup::kMaxBucketSize,
BucketIndexLookup::kMaxBucketSize + 1,
BucketIndexLookup::kMaxBucketSize + SystemPageSize(),
BucketIndexLookup::kMaxBucketSize + PartitionPageSize(),
kSuperPageSize};
#if PA_BUILDFLAG(HAS_64_BIT_POINTERS)
constexpr size_t kFarFarAwayDelta = 512 * kGiB;
#else
constexpr size_t kFarFarAwayDelta = kGiB;
#endif
for (size_t size : kSizes) {
size_t requested_size = size - ExtraAllocSize(allocator);
if (size <= MaxRegularSlotSpanSize()) {
ASSERT_EQ(requested_size,
allocator.root()->AllocationCapacityFromRequestedSize(
requested_size));
}
constexpr size_t kNumRepeats = 3;
void* ptrs[kNumRepeats];
for (void*& ptr : ptrs) {
ptr = allocator.root()->Alloc(requested_size, type_name);
if (size <= MaxRegularSlotSpanSize()) {
SlotStart slot_start = SlotStart::Unchecked(ptr);
EXPECT_EQ(requested_size,
allocator.root()->AllocationCapacityFromSlotStart(
slot_start.Untag()));
}
uintptr_t address = UntagPtr(ptr);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address,
address - kFarFarAwayDelta, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(
IsPtrWithinSameAllocInBRPPool(address, address - kSuperPageSize, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address, address - 1, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address, address, 0u),
PtrPosWithinAlloc::kInBounds);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address,
address + requested_size / 2, 0u),
PtrPosWithinAlloc::kInBounds);
#if PA_BUILDFLAG(BACKUP_REF_PTR_POISON_OOB_PTR)
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address,
address + requested_size - 1, 1u),
PtrPosWithinAlloc::kInBounds);
EXPECT_EQ(
IsPtrWithinSameAllocInBRPPool(address, address + requested_size, 1u),
PtrPosWithinAlloc::kAllocEnd);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address,
address + requested_size - 4, 4u),
PtrPosWithinAlloc::kInBounds);
for (size_t subtrahend = 0; subtrahend < 4; subtrahend++) {
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address, address + requested_size - subtrahend, 4u),
PtrPosWithinAlloc::kAllocEnd);
}
#else
EXPECT_EQ(
IsPtrWithinSameAllocInBRPPool(address, address + requested_size, 0u),
PtrPosWithinAlloc::kInBounds);
#endif
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address,
address + requested_size + 1, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address, address + requested_size + kSuperPageSize, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address, address + requested_size + kFarFarAwayDelta, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address + requested_size,
address + requested_size + kFarFarAwayDelta, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address + requested_size,
address + requested_size + kSuperPageSize, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size,
address + requested_size + 1, 0u),
PtrPosWithinAlloc::kFarOOB);
#if PA_BUILDFLAG(BACKUP_REF_PTR_POISON_OOB_PTR)
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size - 1,
address + requested_size - 1, 1u),
PtrPosWithinAlloc::kInBounds);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size - 1,
address + requested_size, 1u),
PtrPosWithinAlloc::kAllocEnd);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size,
address + requested_size, 1u),
PtrPosWithinAlloc::kAllocEnd);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size - 4,
address + requested_size - 4, 4u),
PtrPosWithinAlloc::kInBounds);
for (size_t addend = 1; addend < 4; addend++) {
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address + requested_size - 4,
address + requested_size - 4 + addend, 4u),
PtrPosWithinAlloc::kAllocEnd);
}
#else
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size,
address + requested_size, 0u),
PtrPosWithinAlloc::kInBounds);
#endif
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(
address + requested_size,
address + requested_size - (requested_size / 2), 0u),
PtrPosWithinAlloc::kInBounds);
EXPECT_EQ(
IsPtrWithinSameAllocInBRPPool(address + requested_size, address, 0u),
PtrPosWithinAlloc::kInBounds);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size,
address - 1, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size,
address - kSuperPageSize, 0u),
PtrPosWithinAlloc::kFarOOB);
EXPECT_EQ(IsPtrWithinSameAllocInBRPPool(address + requested_size,
address - kFarFarAwayDelta, 0u),
PtrPosWithinAlloc::kFarOOB);
}
for (void* ptr : ptrs) {
allocator.root()->Free(ptr);
}
}
}
TEST_P(PartitionAllocTest, GetSlotStartMultiplePages) {
if (!UseBRPPool()) {
return;
}
auto* root = allocator.root();
size_t real_size = 0;
for (const auto& bucket : root->buckets_) {
if ((PA_UNSAFE_TODO(root->buckets_ + SizeToIndex(bucket.slot_size)))
->slot_size != bucket.slot_size) {
continue;
}
if (bucket.slot_size <= ExtraAllocSize(allocator)) {
continue;
}
if (bucket.num_system_pages_per_slot_span >
NumSystemPagesPerPartitionPage()) {
real_size = bucket.slot_size;
break;
}
}
ASSERT_GT(real_size, 0u);
const size_t requested_size = real_size - ExtraAllocSize(allocator);
EXPECT_GT(requested_size, 0u);
EXPECT_LE(requested_size, real_size);
const auto* bucket =
PA_UNSAFE_TODO(allocator.root()->buckets_ + SizeToIndex(real_size));
EXPECT_EQ(bucket->slot_size, real_size);
EXPECT_GT(bucket->num_system_pages_per_slot_span,
PartitionPageSize() / SystemPageSize());
size_t num_slots =
(bucket->num_system_pages_per_slot_span * SystemPageSize()) / real_size;
std::vector<void*> ptrs;
for (size_t i = 0; i < num_slots; ++i) {
ptrs.push_back(allocator.root()->Alloc(requested_size, type_name));
}
for (void* ptr : ptrs) {
uintptr_t address = UntagPtr(ptr);
SlotStart slot_start = SlotStart::Unchecked(ptr);
EXPECT_EQ(
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag()),
requested_size);
for (size_t offset = 0; offset < requested_size; offset += 13) {
EXPECT_EQ(SlotAddressAndSize::FromBRPPool(address + offset).slot_start,
slot_start.Untag());
}
allocator.root()->Free(ptr);
}
}
#endif
TEST_P(PartitionAllocTest, Realloc) {
void* ptr = allocator.root()->Realloc(nullptr, kTestAllocSize, type_name);
PA_UNSAFE_TODO(memset(ptr, 'A', kTestAllocSize));
auto* slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
void* ptr2 = allocator.root()->Realloc(ptr, 0, type_name);
EXPECT_EQ(nullptr, ptr2);
EXPECT_EQ(SlotStart::Unchecked(ptr).Untag(),
UntagPtr(slot_span->get_freelist_head()));
size_t size = SystemPageSize() - ExtraAllocSize(allocator);
ASSERT_EQ(size, allocator.root()->AllocationCapacityFromRequestedSize(size));
ptr = allocator.root()->Alloc(size, type_name);
PA_UNSAFE_TODO(memset(ptr, 'A', size));
ptr2 = allocator.root()->Realloc(ptr, size + 1, type_name);
PA_EXPECT_PTR_NE(ptr, ptr2);
char* char_ptr2 = static_cast<char*>(ptr2);
EXPECT_EQ('A', char_ptr2[0]);
EXPECT_EQ('A', PA_UNSAFE_TODO(char_ptr2[size - 1]));
#if PA_BUILDFLAG(EXPENSIVE_DCHECKS_ARE_ON)
EXPECT_EQ(kUninitializedByte,
static_cast<unsigned char>(PA_UNSAFE_TODO(char_ptr2[size])));
#endif
ptr = allocator.root()->Realloc(ptr2, size - 1, type_name);
PA_EXPECT_PTR_NE(ptr2, ptr);
char* char_ptr = static_cast<char*>(ptr);
EXPECT_EQ('A', char_ptr[0]);
EXPECT_EQ('A', PA_UNSAFE_TODO(char_ptr[size - 2]));
#if PA_BUILDFLAG(EXPENSIVE_DCHECKS_ARE_ON)
EXPECT_EQ(kUninitializedByte,
static_cast<unsigned char>(PA_UNSAFE_TODO(char_ptr[size - 1])));
#endif
allocator.root()->Free(ptr);
size = MaxRegularSlotSpanSize() + 1;
ASSERT_LE(2 * size,
BucketIndexLookup::kMaxBucketSize); ASSERT_LT(size, allocator.root()->AllocationCapacityFromRequestedSize(size));
ptr = allocator.root()->Alloc(size, type_name);
PA_UNSAFE_TODO(memset(ptr, 'A', size));
ptr2 = allocator.root()->Realloc(ptr, size * 2, type_name);
PA_EXPECT_PTR_NE(ptr, ptr2);
char_ptr2 = static_cast<char*>(ptr2);
EXPECT_EQ('A', char_ptr2[0]);
EXPECT_EQ('A', PA_UNSAFE_TODO(char_ptr2[size - 1]));
#if PA_BUILDFLAG(EXPENSIVE_DCHECKS_ARE_ON)
EXPECT_EQ(kUninitializedByte,
static_cast<unsigned char>(PA_UNSAFE_TODO(char_ptr2[size])));
#endif
allocator.root()->Free(ptr2);
size = 2 * (MaxRegularSlotSpanSize() + 1);
ASSERT_GT(size / 2, MaxRegularSlotSpanSize()); ptr = allocator.root()->Alloc(size, type_name);
PA_UNSAFE_TODO(memset(ptr, 'A', size));
ptr2 = allocator.root()->Realloc(ptr2, size / 2, type_name);
PA_EXPECT_PTR_NE(ptr, ptr2);
char_ptr2 = static_cast<char*>(ptr2);
EXPECT_EQ('A', char_ptr2[0]);
EXPECT_EQ('A', PA_UNSAFE_TODO(char_ptr2[size / 2 - 1]));
#if PA_BUILDFLAG(USE_PARTITION_COOKIE)
EXPECT_EQ(kCookieValue[0],
static_cast<unsigned char>(PA_UNSAFE_TODO(char_ptr2[size / 2])));
#endif
allocator.root()->Free(ptr2);
size = 10 * kSuperPageSize + SystemPageSize() - 42;
ASSERT_GT(size - 32 * SystemPageSize(), BucketIndexLookup::kMaxBucketSize);
ptr = allocator.root()->Alloc(size, type_name);
SlotStart slot_start = SlotStart::Unchecked(ptr);
size_t actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
ptr2 = allocator.root()->Realloc(ptr, size - SystemPageSize(), type_name);
SlotStart slot_start2 = SlotStart::Unchecked(ptr2);
EXPECT_EQ(slot_start.Untag(), slot_start2.Untag());
EXPECT_EQ(
actual_capacity - SystemPageSize(),
allocator.root()->AllocationCapacityFromSlotStart(slot_start2.Untag()));
void* ptr3 =
allocator.root()->Realloc(ptr2, size - 32 * SystemPageSize(), type_name);
SlotStart slot_start3 = SlotStart::Unchecked(ptr3);
EXPECT_EQ(slot_start2.Untag(), slot_start3.Untag());
EXPECT_EQ(
actual_capacity - 32 * SystemPageSize(),
allocator.root()->AllocationCapacityFromSlotStart(slot_start3.Untag()));
ptr = allocator.root()->Realloc(ptr3, size, type_name);
slot_start = SlotStart::Unchecked(ptr);
EXPECT_EQ(slot_start3.Untag(), slot_start.Untag());
EXPECT_EQ(actual_capacity, allocator.root()->AllocationCapacityFromSlotStart(
slot_start.Untag()));
ptr2 = allocator.root()->Realloc(ptr, actual_capacity, type_name);
slot_start2 = SlotStart::Unchecked(ptr2);
EXPECT_EQ(slot_start.Untag(), slot_start2.Untag());
EXPECT_EQ(actual_capacity, allocator.root()->AllocationCapacityFromSlotStart(
slot_start2.Untag()));
ptr3 = allocator.root()->Realloc(ptr2, SystemPageSize(), type_name);
slot_start3 = SlotStart::Unchecked(ptr3);
EXPECT_NE(slot_start.Untag(), slot_start3.Untag());
allocator.root()->Free(ptr3);
}
TEST_P(PartitionAllocTest, ReallocDirectMapAligned) {
size_t alignments[] = {
PartitionPageSize(),
2 * PartitionPageSize(),
kMaxSupportedAlignment / 2,
kMaxSupportedAlignment,
};
for (size_t alignment : alignments) {
size_t size = 10 * kSuperPageSize + SystemPageSize() - 42;
ASSERT_GT(size, BucketIndexLookup::kMaxBucketSize);
void* ptr =
allocator.root()->AllocInternalForTesting(size, alignment, type_name);
SlotStart slot_start = SlotStart::Unchecked(ptr);
size_t actual_capacity =
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag());
void* ptr2 =
allocator.root()->Realloc(ptr, size - SystemPageSize(), type_name);
SlotStart slot_start2 = SlotStart::Unchecked(ptr2);
EXPECT_EQ(slot_start.Untag(), slot_start2.Untag());
EXPECT_EQ(
actual_capacity - SystemPageSize(),
allocator.root()->AllocationCapacityFromSlotStart(slot_start2.Untag()));
void* ptr3 = allocator.root()->Realloc(ptr2, size - 32 * SystemPageSize(),
type_name);
SlotStart slot_start3 = SlotStart::Unchecked(ptr3);
EXPECT_EQ(slot_start2.Untag(), slot_start3.Untag());
EXPECT_EQ(
actual_capacity - 32 * SystemPageSize(),
allocator.root()->AllocationCapacityFromSlotStart(slot_start3.Untag()));
ptr = allocator.root()->Realloc(ptr3, size, type_name);
slot_start = SlotStart::Unchecked(ptr);
EXPECT_EQ(slot_start3.Untag(), slot_start.Untag());
EXPECT_EQ(
actual_capacity,
allocator.root()->AllocationCapacityFromSlotStart(slot_start.Untag()));
ptr2 = allocator.root()->Realloc(ptr, actual_capacity, type_name);
slot_start2 = SlotStart::Unchecked(ptr2);
EXPECT_EQ(slot_start.Untag(), slot_start2.Untag());
EXPECT_EQ(
actual_capacity,
allocator.root()->AllocationCapacityFromSlotStart(slot_start2.Untag()));
ptr3 = allocator.root()->Realloc(ptr2, SystemPageSize(), type_name);
slot_start3 = SlotStart::Unchecked(ptr3);
EXPECT_NE(slot_start2.Untag(), slot_start3.Untag());
allocator.root()->Free(ptr3);
}
}
TEST_P(PartitionAllocTest, ReallocDirectMapAlignedRelocate) {
size_t size = 2 * kSuperPageSize - kMaxSupportedAlignment + SystemPageSize();
ASSERT_GT(size, BucketIndexLookup::kMaxBucketSize);
void* ptr = allocator.root()->AllocInternalForTesting(
size, kMaxSupportedAlignment, type_name);
void* ptr2 = allocator.root()->Realloc(ptr, size, type_name);
PA_EXPECT_PTR_NE(ptr, ptr2);
allocator.root()->Free(ptr2);
size = 10 * kSuperPageSize - kMaxSupportedAlignment + SystemPageSize();
ASSERT_GT(size, BucketIndexLookup::kMaxBucketSize);
ptr = allocator.root()->AllocInternalForTesting(size, kMaxSupportedAlignment,
type_name);
ptr2 = allocator.root()->Realloc(ptr, size, type_name);
EXPECT_EQ(ptr, ptr2);
allocator.root()->Free(ptr2);
}
TEST_P(PartitionAllocTest, PartialPageFreelists) {
size_t big_size = SystemPageSize() - ExtraAllocSize(allocator);
size_t bucket_index = SizeToIndex(big_size + ExtraAllocSize(allocator));
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
void* ptr = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(ptr);
auto* slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
size_t total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
(big_size + ExtraAllocSize(allocator));
EXPECT_EQ(4u, total_slots);
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
EXPECT_EQ(3u, slot_span->num_unprovisioned_slots);
void* ptr2 = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(ptr2);
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_EQ(2u, slot_span->num_allocated_slots);
EXPECT_EQ(2u, slot_span->num_unprovisioned_slots);
void* ptr3 = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(ptr3);
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_EQ(3u, slot_span->num_allocated_slots);
EXPECT_EQ(1u, slot_span->num_unprovisioned_slots);
void* ptr4 = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(ptr4);
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_EQ(4u, slot_span->num_allocated_slots);
EXPECT_EQ(0u, slot_span->num_unprovisioned_slots);
void* ptr5 = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(ptr5);
auto* slot_span2 = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr5).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span2->num_allocated_slots);
allocator.root()->Free(ptr);
ptr = allocator.root()->Alloc(big_size, type_name);
void* ptr6 = allocator.root()->Alloc(big_size, type_name);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr4);
allocator.root()->Free(ptr5);
allocator.root()->Free(ptr6);
EXPECT_TRUE(slot_span->in_empty_cache());
EXPECT_TRUE(slot_span2->in_empty_cache());
EXPECT_TRUE(slot_span2->get_freelist_head());
EXPECT_EQ(0u, slot_span2->num_allocated_slots);
size_t non_dividing_size =
SystemPageSize() / 2 + 1 - ExtraAllocSize(allocator);
bucket_index = SizeToIndex(non_dividing_size + ExtraAllocSize(allocator));
bucket = PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
ptr = allocator.root()->Alloc(non_dividing_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
bucket->slot_size;
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
EXPECT_EQ(total_slots - 1, slot_span->num_unprovisioned_slots);
ptr2 = allocator.root()->Alloc(non_dividing_size, type_name);
EXPECT_TRUE(ptr2);
EXPECT_TRUE(slot_span->get_freelist_head());
EXPECT_EQ(2u, slot_span->num_allocated_slots);
EXPECT_EQ(total_slots - 3, slot_span->num_unprovisioned_slots);
ptr3 = allocator.root()->Alloc(non_dividing_size, type_name);
EXPECT_TRUE(ptr3);
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_EQ(3u, slot_span->num_allocated_slots);
EXPECT_EQ(total_slots - 3, slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr3);
EXPECT_TRUE(slot_span->in_empty_cache());
EXPECT_TRUE(slot_span2->get_freelist_head());
EXPECT_EQ(0u, slot_span2->num_allocated_slots);
size_t medium_size = (SystemPageSize() / 2) - ExtraAllocSize(allocator);
bucket_index = SizeToIndex(medium_size + ExtraAllocSize(allocator));
bucket = PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
ptr = allocator.root()->Alloc(medium_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
(medium_size + ExtraAllocSize(allocator));
size_t first_slot_span_slots =
SystemPageSize() / (medium_size + ExtraAllocSize(allocator));
EXPECT_EQ(2u, first_slot_span_slots);
EXPECT_EQ(total_slots - first_slot_span_slots,
slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr);
size_t small_size = (SystemPageSize() / 4) - ExtraAllocSize(allocator);
bucket_index = SizeToIndex(small_size + ExtraAllocSize(allocator));
bucket = PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
ptr = allocator.root()->Alloc(small_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
(small_size + ExtraAllocSize(allocator));
first_slot_span_slots =
SystemPageSize() / (small_size + ExtraAllocSize(allocator));
EXPECT_EQ(total_slots - first_slot_span_slots,
slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr);
EXPECT_TRUE(slot_span->get_freelist_head());
EXPECT_EQ(0u, slot_span->num_allocated_slots);
ASSERT_LT(ExtraAllocSize(allocator), 64u);
size_t very_small_size = (ExtraAllocSize(allocator) <= 32)
? (32 - ExtraAllocSize(allocator))
: (64 - ExtraAllocSize(allocator));
size_t very_small_adjusted_size =
allocator.root()->AdjustSize0IfNeeded(very_small_size);
bucket_index =
SizeToIndex(very_small_adjusted_size + ExtraAllocSize(allocator));
bucket = PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
ptr = allocator.root()->Alloc(very_small_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
size_t very_small_actual_size = PartitionRoot::GetUsableSize(ptr);
total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
(very_small_actual_size + ExtraAllocSize(allocator));
first_slot_span_slots =
SystemPageSize() / (very_small_actual_size + ExtraAllocSize(allocator));
EXPECT_EQ(total_slots - first_slot_span_slots,
slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr);
EXPECT_TRUE(slot_span->get_freelist_head());
EXPECT_EQ(0u, slot_span->num_allocated_slots);
size_t page_and_a_half_size =
(SystemPageSize() + (SystemPageSize() / 2)) - ExtraAllocSize(allocator);
ptr = allocator.root()->Alloc(page_and_a_half_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
EXPECT_TRUE(!slot_span->get_freelist_head());
total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
(page_and_a_half_size + ExtraAllocSize(allocator));
EXPECT_EQ(total_slots - 1, slot_span->num_unprovisioned_slots);
ptr2 = allocator.root()->Alloc(page_and_a_half_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(2u, slot_span->num_allocated_slots);
EXPECT_TRUE(!slot_span->get_freelist_head());
EXPECT_EQ(total_slots - 2, slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
size_t page_size = SystemPageSize() - ExtraAllocSize(allocator);
ptr = allocator.root()->Alloc(page_size, type_name);
EXPECT_TRUE(ptr);
slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
EXPECT_TRUE(slot_span->get_freelist_head());
total_slots =
(slot_span->bucket->num_system_pages_per_slot_span * SystemPageSize()) /
(page_size + ExtraAllocSize(allocator));
EXPECT_EQ(total_slots - 2, slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, SlotSpanRefilling) {
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[test_bucket_index_]);
auto* slot_span1 = GetFullSlotSpan(kTestAllocSize);
auto* slot_span2 = GetFullSlotSpan(kTestAllocSize);
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
EXPECT_NE(slot_span1, bucket->active_slot_spans_head);
EXPECT_NE(slot_span2, bucket->active_slot_spans_head);
auto* slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(1u, slot_span->num_allocated_slots);
void* ptr2 = SlotSpan::ToSlotSpanStart(slot_span1, allocator.root())
.AsSlotStart()
.Tag()
.ToObject();
allocator.root()->Free(ptr2);
ptr2 = SlotSpan::ToSlotSpanStart(slot_span2, allocator.root())
.AsSlotStart()
.Tag()
.ToObject();
allocator.root()->Free(ptr2);
std::ignore = allocator.root()->Alloc(kTestAllocSize, type_name);
std::ignore = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_EQ(1u, slot_span->num_allocated_slots);
FreeFullSlotSpan(allocator.root(), slot_span2);
FreeFullSlotSpan(allocator.root(), slot_span1);
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, PartialPages) {
size_t size = sizeof(void*);
size_t bucket_index;
PartitionRoot::Bucket* bucket = nullptr;
constexpr size_t kMaxSize = 4000u;
while (size < kMaxSize) {
bucket_index = SizeToIndex(size + ExtraAllocSize(allocator));
bucket = PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
if (bucket->num_system_pages_per_slot_span %
NumSystemPagesPerPartitionPage()) {
break;
}
size += sizeof(void*);
}
EXPECT_LT(size, kMaxSize);
auto* slot_span1 = GetFullSlotSpan(size);
auto* slot_span2 = GetFullSlotSpan(size);
FreeFullSlotSpan(allocator.root(), slot_span2);
FreeFullSlotSpan(allocator.root(), slot_span1);
}
TEST_P(PartitionAllocTest, MappingCollision) {
size_t num_pages_per_slot_span = GetNumPagesPerSlotSpan(kTestAllocSize);
size_t num_slot_span_needed =
(NumPartitionPagesPerSuperPage() - 2) / num_pages_per_slot_span;
size_t num_partition_pages_needed =
num_slot_span_needed * num_pages_per_slot_span;
auto first_super_page_pages =
std::make_unique<const SlotSpan*[]>(num_partition_pages_needed);
auto second_super_page_pages =
std::make_unique<const SlotSpan*[]>(num_partition_pages_needed);
size_t i;
for (i = 0; i < num_partition_pages_needed; ++i) {
PA_UNSAFE_TODO(first_super_page_pages[i]) = GetFullSlotSpan(kTestAllocSize);
}
uintptr_t slot_span_start =
SlotSpan::ToSlotSpanStart(first_super_page_pages[0], allocator.root())
.value();
EXPECT_EQ(PartitionPageSize(), slot_span_start & kSuperPageOffsetMask);
uintptr_t super_page = slot_span_start - PartitionPageSize();
uintptr_t map1 =
AllocPages(super_page - PageAllocationGranularity(),
PageAllocationGranularity(), PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
EXPECT_TRUE(map1);
uintptr_t map2 =
AllocPages(super_page + kSuperPageSize, PageAllocationGranularity(),
PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
EXPECT_TRUE(map2);
for (i = 0; i < num_partition_pages_needed; ++i) {
PA_UNSAFE_TODO(second_super_page_pages[i]) =
GetFullSlotSpan(kTestAllocSize);
}
FreePages(map1, PageAllocationGranularity());
FreePages(map2, PageAllocationGranularity());
super_page =
SlotSpan::ToSlotSpanStart(second_super_page_pages[0], allocator.root())
.value();
EXPECT_EQ(PartitionPageSize(), super_page & kSuperPageOffsetMask);
super_page -= PartitionPageSize();
map1 = AllocPages(super_page - PageAllocationGranularity(),
PageAllocationGranularity(), PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kReadWriteTagged),
PageTag::kPartitionAlloc);
EXPECT_TRUE(map1);
map2 = AllocPages(super_page + kSuperPageSize, PageAllocationGranularity(),
PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kReadWriteTagged),
PageTag::kPartitionAlloc);
EXPECT_TRUE(map2);
EXPECT_TRUE(TrySetSystemPagesAccess(
map1, PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible)));
EXPECT_TRUE(TrySetSystemPagesAccess(
map2, PageAllocationGranularity(),
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible)));
auto* slot_span_in_third_super_page = GetFullSlotSpan(kTestAllocSize);
FreePages(map1, PageAllocationGranularity());
FreePages(map2, PageAllocationGranularity());
EXPECT_EQ(0u, SlotSpan::ToSlotSpanStart(slot_span_in_third_super_page,
allocator.root())
.value() &
PartitionPageOffsetMask());
EXPECT_NE(
SlotSpan::ToSlotSpanStart(first_super_page_pages[0], allocator.root())
.value() &
kSuperPageBaseMask,
SlotSpan::ToSlotSpanStart(slot_span_in_third_super_page, allocator.root())
.value() &
kSuperPageBaseMask);
EXPECT_NE(
SlotSpan::ToSlotSpanStart(second_super_page_pages[0], allocator.root())
.value() &
kSuperPageBaseMask,
SlotSpan::ToSlotSpanStart(slot_span_in_third_super_page, allocator.root())
.value() &
kSuperPageBaseMask);
FreeFullSlotSpan(allocator.root(), slot_span_in_third_super_page);
for (i = 0; i < num_partition_pages_needed; ++i) {
FreeFullSlotSpan(allocator.root(),
PA_UNSAFE_TODO(first_super_page_pages[i]));
FreeFullSlotSpan(allocator.root(),
PA_UNSAFE_TODO(second_super_page_pages[i]));
}
}
TEST_P(PartitionAllocTest, FreeCache) {
EXPECT_EQ(0U, allocator.root()->get_total_size_of_committed_pages());
size_t big_size = 1000 - ExtraAllocSize(allocator);
size_t bucket_index = SizeToIndex(big_size + ExtraAllocSize(allocator));
PartitionBucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
void* ptr = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(ptr);
auto* slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
EXPECT_EQ(1u, slot_span->num_allocated_slots);
size_t expected_committed_size =
kUseLazyCommit ? SystemPageSize() : PartitionPageSize();
EXPECT_EQ(expected_committed_size,
allocator.root()->get_total_size_of_committed_pages());
allocator.root()->Free(ptr);
EXPECT_EQ(0u, slot_span->num_allocated_slots);
EXPECT_TRUE(slot_span->in_empty_cache());
EXPECT_TRUE(slot_span->get_freelist_head());
ClearEmptySlotSpanCache();
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_FALSE(slot_span->in_empty_cache());
EXPECT_EQ(0u, slot_span->num_allocated_slots);
EXPECT_EQ(0u, allocator.root()->get_total_size_of_committed_pages());
ptr = allocator.root()->Alloc(big_size, type_name);
EXPECT_FALSE(bucket->empty_slot_spans_head);
allocator.root()->Free(ptr);
for (size_t i = 0; i < kMaxEmptySlotSpanRingSize * 2; ++i) {
ptr = allocator.root()->Alloc(big_size, type_name);
EXPECT_TRUE(slot_span->get_freelist_head());
allocator.root()->Free(ptr);
EXPECT_TRUE(slot_span->get_freelist_head());
}
EXPECT_EQ(expected_committed_size,
allocator.root()->get_total_size_of_committed_pages());
}
TEST_P(PartitionAllocTest, LostFreeSlotSpansBug) {
size_t size = PartitionPageSize() - ExtraAllocSize(allocator);
void* ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
void* ptr2 = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr2);
const SlotSpan* slot_span = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr).Untag(), allocator.root());
const SlotSpan* slot_span2 = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr2).Untag(), allocator.root());
PartitionBucket* bucket = slot_span->bucket;
EXPECT_EQ(nullptr, bucket->empty_slot_spans_head);
EXPECT_EQ(1u, slot_span->num_allocated_slots);
EXPECT_EQ(1u, slot_span2->num_allocated_slots);
EXPECT_TRUE(slot_span->is_full());
EXPECT_TRUE(slot_span2->is_full());
EXPECT_TRUE(slot_span->marked_full);
EXPECT_FALSE(slot_span2->marked_full);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_TRUE(bucket->empty_slot_spans_head->next_slot_span);
EXPECT_EQ(0u, slot_span->num_allocated_slots);
EXPECT_EQ(0u, slot_span2->num_allocated_slots);
EXPECT_FALSE(slot_span->is_full());
EXPECT_FALSE(slot_span->is_full());
EXPECT_FALSE(slot_span->marked_full);
EXPECT_FALSE(slot_span2->marked_full);
EXPECT_TRUE(slot_span->get_freelist_head());
EXPECT_TRUE(slot_span2->get_freelist_head());
ClearEmptySlotSpanCache();
EXPECT_FALSE(slot_span->get_freelist_head());
EXPECT_FALSE(slot_span2->get_freelist_head());
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_TRUE(bucket->empty_slot_spans_head->next_slot_span);
EXPECT_EQ(SlotSpan::get_sentinel_slot_span(), bucket->active_slot_spans_head);
ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
EXPECT_EQ(SlotSpan::get_sentinel_slot_span(), bucket->active_slot_spans_head);
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_TRUE(bucket->decommitted_slot_spans_head);
ClearEmptySlotSpanCache();
ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
EXPECT_TRUE(bucket->is_valid());
EXPECT_TRUE(bucket->empty_slot_spans_head);
EXPECT_TRUE(bucket->decommitted_slot_spans_head);
}
TEST_P(PartitionAllocTest, CheckMetadataIntegrityPass) {
char* const small_ptr =
static_cast<char*>(allocator.root()->Alloc(kTestAllocSize));
ASSERT_TRUE(small_ptr);
PartitionRoot::CheckMetadataIntegrity(small_ptr);
PartitionRoot::CheckMetadataIntegrity(
PA_UNSAFE_TODO(small_ptr + kTestAllocSize - 1));
allocator.root()->Free(small_ptr);
constexpr size_t kDirectMapSize = BucketIndexLookup::kMaxBucketSize + 1;
char* const large_ptr =
static_cast<char*>(allocator.root()->Alloc(kDirectMapSize));
ASSERT_TRUE(large_ptr);
PartitionRoot::CheckMetadataIntegrity(large_ptr);
PartitionRoot::CheckMetadataIntegrity(
PA_UNSAFE_TODO(large_ptr + kDirectMapSize - 1));
allocator.root()->Free(large_ptr);
}
#if PA_USE_DEATH_TESTS()
#if !PA_BUILDFLAG(IS_WIN) && \
(!PA_BUILDFLAG(PA_ARCH_CPU_64_BITS) || \
(PA_BUILDFLAG(IS_POSIX) && \
!(PA_BUILDFLAG(IS_APPLE) || PA_BUILDFLAG(IS_ANDROID)))) || \
PA_BUILDFLAG(IS_FUCHSIA)
#define MAYBE_RepeatedAllocReturnNullDirect RepeatedAllocReturnNullDirect
#define MAYBE_RepeatedReallocReturnNullDirect RepeatedReallocReturnNullDirect
#else
#define MAYBE_RepeatedAllocReturnNullDirect \
DISABLED_RepeatedAllocReturnNullDirect
#define MAYBE_RepeatedReallocReturnNullDirect \
DISABLED_RepeatedReallocReturnNullDirect
#endif
TEST_P(PartitionAllocDeathTest, MAYBE_RepeatedAllocReturnNullDirect) {
size_t direct_map_size = 32 * 1024 * 1024;
ASSERT_GT(direct_map_size, BucketIndexLookup::kMaxBucketSize);
EXPECT_DEATH(DoReturnNullTest(direct_map_size, kPartitionAlloc),
"Passed DoReturnNullTest");
}
TEST_P(PartitionAllocDeathTest, MAYBE_RepeatedReallocReturnNullDirect) {
size_t direct_map_size = 32 * 1024 * 1024;
ASSERT_GT(direct_map_size, BucketIndexLookup::kMaxBucketSize);
EXPECT_DEATH(DoReturnNullTest(direct_map_size, kPartitionRealloc),
"Passed DoReturnNullTest");
}
TEST_P(PartitionAllocDeathTest, DISABLED_RepeatedAllocReturnNull) {
size_t single_slot_size = 512 * 1024;
ASSERT_GT(single_slot_size, MaxRegularSlotSpanSize());
ASSERT_LE(single_slot_size, BucketIndexLookup::kMaxBucketSize);
EXPECT_DEATH(DoReturnNullTest(single_slot_size, kPartitionAlloc),
"Passed DoReturnNullTest");
}
TEST_P(PartitionAllocDeathTest, DISABLED_RepeatedReallocReturnNull) {
size_t single_slot_size = 512 * 1024;
ASSERT_GT(single_slot_size, MaxRegularSlotSpanSize());
ASSERT_LE(single_slot_size, BucketIndexLookup::kMaxBucketSize);
EXPECT_DEATH(DoReturnNullTest(single_slot_size, kPartitionRealloc),
"Passed DoReturnNullTest");
}
#if PA_BUILDFLAG(HAS_MEMORY_TAGGING)
TEST_P(PartitionAllocDeathTest, MTEProtectsFreedPtr) {
base::CPU cpu;
if (!cpu.has_mte()) {
GTEST_SKIP();
}
ChangeMemoryTaggingModeForCurrentThread(
TagViolationReportingMode::kSynchronous);
ASSERT_TRUE(GetMemoryTaggingModeForCurrentThread() !=
TagViolationReportingMode::kDisabled)
<< "Test was built with MTE enabled and the CPU supports it, but MTE is "
"currently disabled in the device.";
constexpr uint64_t kCookie = 0x1234567890ABCDEF;
constexpr uint64_t kQuarantined = 0xEFEFEFEFEFEFEFEF;
size_t alloc_size = 64 - ExtraAllocSize(allocator);
uint64_t* ptr =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
EXPECT_TRUE(ptr);
*ptr = kCookie;
allocator.root()->Free(ptr);
EXPECT_EXIT(
{
*ptr = kQuarantined;
},
testing::KilledBySignal(SIGSEGV), "");
}
TEST_P(PartitionAllocDeathTest, SuspendTagCheckingScope) {
base::CPU cpu;
if (!cpu.has_mte()) {
GTEST_SKIP();
}
ChangeMemoryTaggingModeForCurrentThread(
TagViolationReportingMode::kSynchronous);
ASSERT_TRUE(GetMemoryTaggingModeForCurrentThread() !=
TagViolationReportingMode::kDisabled)
<< "Test was built with MTE enabled and the CPU supports it, but MTE is "
"currently disabled in the device.";
constexpr uint64_t kQuarantined = 0xEFEFEFEFEFEFEFEF;
size_t alloc_size = 64 - ExtraAllocSize(allocator);
uint64_t* ptr =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
{
partition_alloc::SuspendTagCheckingScope scope;
*ptr = kQuarantined;
}
EXPECT_EXIT(
{
*ptr = kQuarantined;
},
testing::KilledBySignal(SIGSEGV), "");
}
#endif
TEST_P(PartitionAllocDeathTest, LargeAllocs) {
EXPECT_DEATH(allocator.root()->Alloc(static_cast<size_t>(-1), type_name), "");
EXPECT_DEATH(allocator.root()->Alloc(MaxDirectMapped() + 1, type_name), "");
}
#if !PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) || \
(PA_BUILDFLAG(HAS_64_BIT_POINTERS) && \
PA_BUILDFLAG(PA_ARCH_CPU_LITTLE_ENDIAN))
TEST_P(PartitionAllocDeathTest, ImmediateDoubleFree) {
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
EXPECT_DEATH(allocator.root()->Free(ptr), "");
if (
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
allocator.root()->brp_enabled() ||
#endif allocator.root()->settings_.use_cookie) {
EXPECT_DEATH(allocator.root()->CheckMetadataIntegrity(ptr), "");
}
}
TEST_P(PartitionAllocDeathTest, ImmediateDoubleFree2ndSlot) {
void* ptr0 = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr0);
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
EXPECT_DEATH(allocator.root()->Free(ptr), "");
if (
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
allocator.root()->brp_enabled() ||
#endif allocator.root()->settings_.use_cookie) {
EXPECT_DEATH(allocator.root()->CheckMetadataIntegrity(ptr), "");
}
allocator.root()->Free(ptr0);
}
TEST_P(PartitionAllocDeathTest, NumAllocatedSlotsDoubleFree) {
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
void* ptr2 = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr2);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
EXPECT_DEATH(allocator.root()->Free(ptr), "");
}
#endif
TEST_P(PartitionAllocDeathTest, DirectMapGuardPages) {
const size_t kSizes[] = {
BucketIndexLookup::kMaxBucketSize + ExtraAllocSize(allocator) + 1,
BucketIndexLookup::kMaxBucketSize + SystemPageSize(),
BucketIndexLookup::kMaxBucketSize + PartitionPageSize(),
partition_alloc::internal::base::bits::AlignUp(
BucketIndexLookup::kMaxBucketSize + kSuperPageSize, kSuperPageSize) -
PartitionRoot::GetDirectMapMetadataAndGuardPagesSize()};
for (size_t size : kSizes) {
ASSERT_GT(size, BucketIndexLookup::kMaxBucketSize);
size -= ExtraAllocSize(allocator);
EXPECT_GT(size, BucketIndexLookup::kMaxBucketSize)
<< "allocation not large enough for direct allocation";
void* ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
char* char_ptr = PA_UNSAFE_TODO(static_cast<char*>(ptr) - kPointerOffset);
EXPECT_DEATH(*(PA_UNSAFE_TODO(char_ptr - 1)) = 'A', "");
EXPECT_DEATH(*(PA_UNSAFE_TODO(
char_ptr + partition_alloc::internal::base::bits::AlignUp(
size, SystemPageSize()))) = 'A',
"");
allocator.root()->Free(ptr);
}
}
#if !PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) && \
PA_CONFIG(HAS_FREELIST_SHADOW_ENTRY)
TEST_P(PartitionAllocDeathTest, UseAfterFreeDetection) {
base::CPU cpu;
void* data = allocator.root()->Alloc(100);
allocator.root()->Free(data);
PA_UNSAFE_TODO(memset(data, 0x42, 100));
EXPECT_DEATH(allocator.root()->Alloc(100), "");
}
TEST_P(PartitionAllocDeathTest, FreelistCorruption) {
base::CPU cpu;
const size_t alloc_size = 2 * sizeof(void*);
void** fake_freelist_entry =
static_cast<void**>(allocator.root()->Alloc(alloc_size));
fake_freelist_entry[0] = nullptr;
PA_UNSAFE_TODO(fake_freelist_entry[1]) = nullptr;
void** uaf_data = static_cast<void**>(allocator.root()->Alloc(alloc_size));
allocator.root()->Free(uaf_data);
void* previous_uaf_data = PA_UNSAFE_TODO(uaf_data[0]);
PA_UNSAFE_TODO(uaf_data[0]) = fake_freelist_entry;
EXPECT_DEATH(allocator.root()->Alloc(alloc_size), "");
PA_UNSAFE_TODO(uaf_data[0]) = previous_uaf_data;
allocator.root()->Free(fake_freelist_entry);
}
#if !PA_BUILDFLAG(USE_PARTITION_COOKIE)
TEST_P(PartitionAllocDeathTest, OffByOneDetection) {
base::CPU cpu;
const size_t alloc_size = 2 * sizeof(void*);
char* array = static_cast<char*>(allocator.root()->Alloc(alloc_size));
if (cpu.has_mte()) {
EXPECT_DEATH(PA_UNSAFE_TODO(array[alloc_size]) = 'A', "");
} else {
char previous_value = PA_UNSAFE_TODO(array[alloc_size]);
*const_cast<volatile char*>(PA_UNSAFE_TODO(&array[alloc_size])) = 'A';
EXPECT_DEATH(allocator.root()->Alloc(alloc_size), "");
PA_UNSAFE_TODO(array[alloc_size]) = previous_value;
}
}
TEST_P(PartitionAllocDeathTest, OffByOneDetectionWithRealisticData) {
base::CPU cpu;
const size_t alloc_size = 2 * sizeof(void*);
void** array = static_cast<void**>(allocator.root()->Alloc(alloc_size));
char valid;
if (cpu.has_mte()) {
EXPECT_DEATH(PA_UNSAFE_TODO(array[2]) = &valid, "");
} else {
void* previous_value = PA_UNSAFE_TODO(array[2]);
*const_cast<void* volatile*>(PA_UNSAFE_TODO(&array[2])) = &valid;
EXPECT_DEATH(allocator.root()->Alloc(alloc_size), "");
PA_UNSAFE_TODO(array[2]) = previous_value;
}
}
#endif
#endif
#if PA_BUILDFLAG(USE_PARTITION_COOKIE)
TEST_P(PartitionAllocDeathTest, OffByOneDetectionByCookie) {
base::CPU cpu;
const size_t alloc_size = 2 * sizeof(void*);
char* array = static_cast<char*>(allocator.root()->Alloc(alloc_size));
auto* slot_span = PartitionRoot::SlotSpanMetadata::FromObjectInnerPtr(
array, allocator.root());
size_t usable_size = allocator.root()->GetSlotUsableSize(slot_span);
char previous_value = PA_UNSAFE_TODO(array[usable_size]);
*const_cast<volatile char*>(PA_UNSAFE_TODO(&array[usable_size])) = 'A';
EXPECT_DEATH(allocator.root()->Free(array), "");
EXPECT_DEATH(allocator.root()->CheckMetadataIntegrity(array), "");
PA_UNSAFE_TODO(array[usable_size] = previous_value);
allocator.root()->Free(array);
}
TEST_P(PartitionAllocDeathTest, OffByOneDetectionByCookieWithRealisticData) {
base::CPU cpu;
const size_t alloc_size = 2 * sizeof(void*);
void** array = static_cast<void**>(allocator.root()->Alloc(alloc_size));
char valid;
auto* slot_span = PartitionRoot::SlotSpanMetadata::FromObjectInnerPtr(
array, allocator.root());
size_t usable_size =
allocator.root()->GetSlotUsableSize(slot_span) / sizeof(void*);
void* previous_value = PA_UNSAFE_TODO(array[usable_size]);
*const_cast<void* volatile*>(PA_UNSAFE_TODO(&array[usable_size])) = &valid;
EXPECT_DEATH(allocator.root()->Free(array), "");
EXPECT_DEATH(allocator.root()->CheckMetadataIntegrity(array), "");
PA_UNSAFE_TODO(array[usable_size] = previous_value);
allocator.root()->Free(array);
}
#endif
#endif
TEST_P(PartitionAllocTest, DumpMemoryStats) {
{
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
MockPartitionStatsDumper mock_stats_dumper;
allocator.root()->DumpStats("mock_allocator", false,
true,
&mock_stats_dumper);
EXPECT_TRUE(mock_stats_dumper.IsMemoryAllocationRecorded());
allocator.root()->Free(ptr);
}
{
{
void* ptr =
allocator.root()->Alloc(2048 - ExtraAllocSize(allocator), type_name);
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats = dumper.GetBucketStats(2048);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(2048u, stats->bucket_slot_size);
EXPECT_EQ(2048u, stats->active_bytes);
EXPECT_EQ(1u, stats->active_count);
EXPECT_EQ(SystemPageSize(), stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->discardable_bytes);
EXPECT_EQ(0u, stats->num_full_slot_spans);
EXPECT_EQ(1u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
allocator.root()->Free(ptr);
}
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_FALSE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats = dumper.GetBucketStats(2048);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(2048u, stats->bucket_slot_size);
EXPECT_EQ(0u, stats->active_bytes);
EXPECT_EQ(0u, stats->active_count);
EXPECT_EQ(SystemPageSize(), stats->resident_bytes);
EXPECT_EQ(SystemPageSize(), stats->decommittable_bytes);
EXPECT_EQ(0u, stats->discardable_bytes);
EXPECT_EQ(0u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(1u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
}
ClearEmptySlotSpanCache();
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_FALSE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats = dumper.GetBucketStats(2048);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(2048u, stats->bucket_slot_size);
EXPECT_EQ(0u, stats->active_bytes);
EXPECT_EQ(0u, stats->active_count);
EXPECT_EQ(0u, stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->discardable_bytes);
EXPECT_EQ(0u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(1u, stats->num_decommitted_slot_spans);
}
}
{
size_t size = PartitionPageSize() - ExtraAllocSize(allocator);
void* ptr1 = allocator.root()->Alloc(size, type_name);
void* ptr2 = allocator.root()->Alloc(size, type_name);
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
ClearEmptySlotSpanCache();
ptr1 = allocator.root()->Alloc(size, type_name);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(PartitionPageSize());
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(PartitionPageSize(), stats->bucket_slot_size);
EXPECT_EQ(PartitionPageSize(), stats->active_bytes);
EXPECT_EQ(1u, stats->active_count);
EXPECT_EQ(PartitionPageSize(), stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->discardable_bytes);
EXPECT_EQ(1u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(1u, stats->num_decommitted_slot_spans);
}
allocator.root()->Free(ptr1);
}
{
size_t size_smaller = BucketIndexLookup::kMaxBucketSize + 1;
size_t size_bigger = (BucketIndexLookup::kMaxBucketSize * 2) + 1;
size_t real_size_smaller =
(size_smaller + SystemPageOffsetMask()) & SystemPageBaseMask();
size_t real_size_bigger =
(size_bigger + SystemPageOffsetMask()) & SystemPageBaseMask();
void* ptr = allocator.root()->Alloc(size_smaller, type_name);
void* ptr2 = allocator.root()->Alloc(size_bigger, type_name);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(real_size_smaller);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_TRUE(stats->is_direct_map);
EXPECT_EQ(real_size_smaller, stats->bucket_slot_size);
EXPECT_EQ(real_size_smaller, stats->active_bytes);
EXPECT_EQ(1u, stats->active_count);
EXPECT_EQ(real_size_smaller, stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->discardable_bytes);
EXPECT_EQ(1u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
stats = dumper.GetBucketStats(real_size_bigger);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_TRUE(stats->is_direct_map);
EXPECT_EQ(real_size_bigger, stats->bucket_slot_size);
EXPECT_EQ(real_size_bigger, stats->active_bytes);
EXPECT_EQ(1u, stats->active_count);
EXPECT_EQ(real_size_bigger, stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->discardable_bytes);
EXPECT_EQ(1u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
}
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr);
ptr = allocator.root()->Alloc(size_smaller, type_name);
ptr2 = allocator.root()->Alloc(size_bigger, type_name);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
}
{
size_t requested_size = 16 * SystemPageSize() + 1;
void* ptr = allocator.root()->Alloc(requested_size, type_name);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
size_t slot_size = SizeToBucketSize(requested_size);
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(slot_size);
ASSERT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_FALSE(stats->is_direct_map);
EXPECT_EQ(slot_size, stats->bucket_slot_size);
EXPECT_EQ(requested_size + ExtraAllocSize(allocator),
stats->active_bytes);
EXPECT_EQ(1u, stats->active_count);
EXPECT_EQ(slot_size, stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(
base::bits::AlignDown(slot_size - requested_size, SystemPageSize()),
stats->discardable_bytes);
EXPECT_EQ(1u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
}
allocator.root()->Free(ptr);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_FALSE(dumper.IsMemoryAllocationRecorded());
size_t slot_size = SizeToBucketSize(requested_size);
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(slot_size);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_FALSE(stats->is_direct_map);
EXPECT_EQ(slot_size, stats->bucket_slot_size);
EXPECT_EQ(0u, stats->active_bytes);
EXPECT_EQ(0u, stats->active_count);
EXPECT_EQ(slot_size, stats->resident_bytes);
EXPECT_EQ(slot_size, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(1u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
}
requested_size = 17 * SystemPageSize() + 1;
void* ptr2 = allocator.root()->Alloc(requested_size, type_name);
EXPECT_EQ(ptr, ptr2);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
size_t slot_size = SizeToBucketSize(requested_size);
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(slot_size);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_FALSE(stats->is_direct_map);
EXPECT_EQ(slot_size, stats->bucket_slot_size);
EXPECT_EQ(requested_size + ExtraAllocSize(allocator),
stats->active_bytes);
EXPECT_EQ(1u, stats->active_count);
EXPECT_EQ(slot_size, stats->resident_bytes);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(
base::bits::AlignDown(slot_size - requested_size, SystemPageSize()),
stats->discardable_bytes);
EXPECT_EQ(1u, stats->num_full_slot_spans);
EXPECT_EQ(0u, stats->num_active_slot_spans);
EXPECT_EQ(0u, stats->num_empty_slot_spans);
EXPECT_EQ(0u, stats->num_decommitted_slot_spans);
}
allocator.root()->Free(ptr2);
}
}
TEST_P(PartitionAllocTest, Purge) {
char* ptr = static_cast<char*>(
allocator.root()->Alloc(2048 - ExtraAllocSize(allocator), type_name));
allocator.root()->Free(ptr);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_FALSE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats = dumper.GetBucketStats(2048);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(SystemPageSize(), stats->decommittable_bytes);
EXPECT_EQ(SystemPageSize(), stats->resident_bytes);
}
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_FALSE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats = dumper.GetBucketStats(2048);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(0u, stats->resident_bytes);
}
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
size_t single_slot_size = 512 * 1024;
ASSERT_GT(single_slot_size, MaxRegularSlotSpanSize());
ASSERT_LE(single_slot_size, BucketIndexLookup::kMaxBucketSize);
char* big_ptr =
static_cast<char*>(allocator.root()->Alloc(single_slot_size, type_name));
allocator.root()->Free(big_ptr);
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr - kPointerOffset), false);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(big_ptr - kPointerOffset), false);
}
TEST_P(PartitionAllocTest, PreferActiveOverEmpty) {
size_t size = (SystemPageSize() * 2) - ExtraAllocSize(allocator);
void* ptr1 = allocator.root()->Alloc(size, type_name);
void* ptr2 = allocator.root()->Alloc(size, type_name);
void* ptr3 = allocator.root()->Alloc(size, type_name);
void* ptr4 = allocator.root()->Alloc(size, type_name);
void* ptr5 = allocator.root()->Alloc(size, type_name);
void* ptr6 = allocator.root()->Alloc(size, type_name);
const SlotSpan* slot_span1 = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr1).Untag(), allocator.root());
const SlotSpan* slot_span2 = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr3).Untag(), allocator.root());
const SlotSpan* slot_span3 = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr6).Untag(), allocator.root());
EXPECT_NE(slot_span1, slot_span2);
EXPECT_NE(slot_span2, slot_span3);
PartitionBucket* bucket = slot_span1->bucket;
EXPECT_EQ(slot_span3, bucket->active_slot_spans_head);
allocator.root()->Free(ptr6);
allocator.root()->Free(ptr4);
allocator.root()->Free(ptr2);
EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
allocator.root()->Free(ptr3);
EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
allocator.root()->Free(ptr1);
void* ptr7 = allocator.root()->Alloc(size, type_name);
PA_EXPECT_PTR_EQ(ptr6, ptr7);
EXPECT_EQ(slot_span3, bucket->active_slot_spans_head);
allocator.root()->Free(ptr5);
allocator.root()->Free(ptr7);
}
TEST_P(PartitionAllocTest, PurgeDiscardableSecondPage) {
void* ptr1 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
char* ptr2 = static_cast<char*>(allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name));
allocator.root()->Free(ptr2);
const SlotSpan* slot_span = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr1).Untag(), allocator.root());
EXPECT_EQ(2u, slot_span->num_unprovisioned_slots);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(SystemPageSize());
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(SystemPageSize(), stats->discardable_bytes);
EXPECT_EQ(SystemPageSize(), stats->active_bytes);
EXPECT_EQ(2 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr2 - kPointerOffset), true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr2 - kPointerOffset), false);
EXPECT_EQ(3u, slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr1);
}
TEST_P(PartitionAllocTest, PurgeDiscardableFirstPage) {
char* ptr1 = static_cast<char*>(allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name));
void* ptr2 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
allocator.root()->Free(ptr1);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(SystemPageSize());
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
#if PA_BUILDFLAG(IS_WIN)
EXPECT_EQ(0u, stats->discardable_bytes);
#else
EXPECT_EQ(SystemPageSize(), stats->discardable_bytes);
#endif
EXPECT_EQ(SystemPageSize(), stats->active_bytes);
EXPECT_EQ(2 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), false);
allocator.root()->Free(ptr2);
}
TEST_P(PartitionAllocTest, PurgeDiscardableNonPageSizedAlloc) {
const size_t requested_size = 2.5 * SystemPageSize();
char* ptr1 = static_cast<char*>(allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name));
void* ptr2 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
void* ptr3 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
void* ptr4 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
PA_UNSAFE_TODO(memset(ptr1, 'A', requested_size - ExtraAllocSize(allocator)));
PA_UNSAFE_TODO(memset(ptr2, 'A', requested_size - ExtraAllocSize(allocator)));
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(requested_size);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
#if PA_BUILDFLAG(IS_WIN)
EXPECT_EQ(3 * SystemPageSize(), stats->discardable_bytes);
#else
EXPECT_EQ(4 * SystemPageSize(), stats->discardable_bytes);
#endif
EXPECT_EQ(requested_size * 2, stats->active_bytes);
EXPECT_EQ(10 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 4)), true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), false);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 4)), false);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr4);
}
TEST_P(PartitionAllocTest, PurgeDiscardableNonPageSizedAllocOnSlotBoundary) {
const size_t requested_size = 2.5 * SystemPageSize();
char* ptr1 = static_cast<char*>(allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name));
void* ptr2 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
void* ptr3 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
void* ptr4 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
PA_UNSAFE_TODO(memset(ptr1, 'A', requested_size - ExtraAllocSize(allocator)));
PA_UNSAFE_TODO(memset(ptr2, 'A', requested_size - ExtraAllocSize(allocator)));
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr1);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(requested_size);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
#if PA_BUILDFLAG(IS_WIN)
EXPECT_EQ(3 * SystemPageSize(), stats->discardable_bytes);
#else
EXPECT_EQ(4 * SystemPageSize(), stats->discardable_bytes);
#endif
EXPECT_EQ(requested_size * 2, stats->active_bytes);
EXPECT_EQ(10 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 4)), true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 4)), false);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr4);
}
TEST_P(PartitionAllocTest, PurgeDiscardableManyPages) {
const bool kHasLargePages = SystemPageSize() > 4096;
const size_t kFirstAllocPages = kHasLargePages ? 32 : 64;
const size_t kSecondAllocPages = kHasLargePages ? 31 : 61;
PA_DCHECK(kFirstAllocPages * SystemPageSize() <=
BucketIndexLookup::kMaxBucketSize);
const size_t kDeltaPages = kFirstAllocPages - kSecondAllocPages;
{
ScopedPageAllocation p(allocator, kFirstAllocPages);
p.TouchAllPages();
}
ScopedPageAllocation p(allocator, kSecondAllocPages);
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(kFirstAllocPages * SystemPageSize());
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(kDeltaPages * SystemPageSize(), stats->discardable_bytes);
EXPECT_EQ(kSecondAllocPages * SystemPageSize(), stats->active_bytes);
EXPECT_EQ(kFirstAllocPages * SystemPageSize(), stats->resident_bytes);
for (size_t i = 0; i < kFirstAllocPages; i++) {
CHECK_PAGE_IN_CORE(p.PageAtIndex(i), true);
}
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
for (size_t i = 0; i < kSecondAllocPages; i++) {
CHECK_PAGE_IN_CORE(p.PageAtIndex(i), true);
}
for (size_t i = kSecondAllocPages; i < kFirstAllocPages; i++) {
CHECK_PAGE_IN_CORE(p.PageAtIndex(i), false);
}
}
TEST_P(PartitionAllocTest, PurgeDiscardableWithFreeListStraightening) {
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
char* ptr1 = static_cast<char*>(allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name));
void* ptr2 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
void* ptr3 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
void* ptr4 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
PA_UNSAFE_TODO(ptr1[0]) = 'A';
PA_UNSAFE_TODO(ptr1[SystemPageSize()]) = 'A';
PA_UNSAFE_TODO(ptr1[SystemPageSize() * 2]) = 'A';
PA_UNSAFE_TODO(ptr1[SystemPageSize() * 3]) = 'A';
const SlotSpan* slot_span = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr1).Untag(), allocator.root());
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr4);
allocator.root()->Free(ptr1);
EXPECT_EQ(0u, slot_span->num_unprovisioned_slots);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(SystemPageSize());
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
#if PA_BUILDFLAG(IS_WIN)
EXPECT_EQ(SystemPageSize(), stats->discardable_bytes);
#else
EXPECT_EQ(2 * SystemPageSize(), stats->discardable_bytes);
#endif
EXPECT_EQ(SystemPageSize(), stats->active_bytes);
EXPECT_EQ(4 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
EXPECT_EQ(1u, slot_span->num_unprovisioned_slots);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), false);
void* ptr1b = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
PA_EXPECT_PTR_EQ(ptr1, ptr1b);
void* ptr2b = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
PA_EXPECT_PTR_EQ(ptr2, ptr2b);
EXPECT_FALSE(slot_span->get_freelist_head()); void* ptr4b = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
PA_EXPECT_PTR_EQ(ptr4, ptr4b);
EXPECT_FALSE(slot_span->get_freelist_head());
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr2);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
ptr2b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr2, ptr2b);
void* ptr3b = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
PA_EXPECT_PTR_EQ(ptr3, ptr3b);
ptr1b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr1, ptr1b);
EXPECT_FALSE(slot_span->get_freelist_head());
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr2);
PartitionRoot::SetStraightenLargerSlotSpanFreeListsMode(
StraightenLargerSlotSpanFreeListsMode::kAlways);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
ptr1b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr1, ptr1b);
ptr2b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr2, ptr2b);
ptr3b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr3, ptr3b);
EXPECT_FALSE(slot_span->get_freelist_head());
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr4);
allocator.root()->Free(ptr2);
PartitionRoot::SetStraightenLargerSlotSpanFreeListsMode(
StraightenLargerSlotSpanFreeListsMode::kNever);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
ptr2b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr2, ptr2b);
ptr1b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr1, ptr1b);
EXPECT_FALSE(slot_span->get_freelist_head());
ptr4b = allocator.root()->Alloc(SystemPageSize() - ExtraAllocSize(allocator),
type_name);
PA_EXPECT_PTR_EQ(ptr4, ptr4b);
EXPECT_FALSE(slot_span->get_freelist_head());
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr4);
}
TEST_P(PartitionAllocTest, PurgeDiscardableDoubleTruncateFreeList) {
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
char* ptr1 = static_cast<char*>(allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name));
void* ptr2 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
void* ptr3 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
void* ptr4 = allocator.root()->Alloc(
SystemPageSize() - ExtraAllocSize(allocator), type_name);
PA_UNSAFE_TODO(ptr1[0]) = 'A';
PA_UNSAFE_TODO(ptr1[SystemPageSize()]) = 'A';
PA_UNSAFE_TODO(ptr1[SystemPageSize() * 2]) = 'A';
PA_UNSAFE_TODO(ptr1[SystemPageSize() * 3]) = 'A';
const SlotSpan* slot_span = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr1).Untag(), allocator.root());
allocator.root()->Free(ptr4);
allocator.root()->Free(ptr3);
EXPECT_EQ(0u, slot_span->num_unprovisioned_slots);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(SystemPageSize());
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(2 * SystemPageSize(), stats->discardable_bytes);
EXPECT_EQ(2 * SystemPageSize(), stats->active_bytes);
EXPECT_EQ(4 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
EXPECT_EQ(2u, slot_span->num_unprovisioned_slots);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
true);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 2)), false);
CHECK_PAGE_IN_CORE(
PA_UNSAFE_TODO(ptr1 - kPointerOffset + (SystemPageSize() * 3)), false);
EXPECT_FALSE(slot_span->get_freelist_head());
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
}
TEST_P(PartitionAllocTest, PurgeDiscardableSmallSlotsWithTruncate) {
size_t requested_size = 0.5 * SystemPageSize();
char* ptr1 = static_cast<char*>(allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name));
void* ptr2 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
void* ptr3 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
void* ptr4 = allocator.root()->Alloc(
requested_size - ExtraAllocSize(allocator), type_name);
allocator.root()->Free(ptr3);
allocator.root()->Free(ptr4);
const SlotSpan* slot_span = SlotSpan::FromSlotStart(
SlotStart::Unchecked(ptr1).Untag(), allocator.root());
EXPECT_EQ(4u, slot_span->num_unprovisioned_slots);
{
MockPartitionStatsDumper dumper;
allocator.root()->DumpStats("mock_allocator", false,
true, &dumper);
EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
const PartitionBucketMemoryStats* stats =
dumper.GetBucketStats(requested_size);
EXPECT_TRUE(stats);
EXPECT_TRUE(stats->is_valid);
EXPECT_EQ(0u, stats->decommittable_bytes);
EXPECT_EQ(SystemPageSize(), stats->discardable_bytes);
EXPECT_EQ(requested_size * 2, stats->active_bytes);
EXPECT_EQ(2 * SystemPageSize(), stats->resident_bytes);
}
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
true);
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset), true);
CHECK_PAGE_IN_CORE(PA_UNSAFE_TODO(ptr1 - kPointerOffset + SystemPageSize()),
false);
EXPECT_EQ(6u, slot_span->num_unprovisioned_slots);
allocator.root()->Free(ptr1);
allocator.root()->Free(ptr2);
}
TEST_P(PartitionAllocTest, ActiveListMaintenance) {
size_t size = SystemPageSize() - ExtraAllocSize(allocator);
size_t real_size = size + ExtraAllocSize(allocator);
size_t bucket_index =
PartitionRoot::SizeToBucketIndex(real_size, GetBucketDistribution());
PartitionRoot::Bucket* bucket =
PA_UNSAFE_TODO(&allocator.root()->buckets_[bucket_index]);
ASSERT_EQ(bucket->slot_size, real_size);
size_t slots_per_span = bucket->num_system_pages_per_slot_span;
constexpr int kSpans = 10;
std::vector<std::vector<void*>> allocated_memory_spans(kSpans);
for (int span_index = 0; span_index < kSpans; span_index++) {
for (size_t i = 0; i < slots_per_span; i++) {
allocated_memory_spans[span_index].push_back(
allocator.root()->Alloc(size));
}
}
constexpr size_t kSpanIndex = 5;
allocator.root()->Free(allocated_memory_spans[kSpanIndex].back());
allocated_memory_spans[kSpanIndex].pop_back();
for (void* ptr : allocated_memory_spans[kSpans - 1]) {
allocator.root()->Free(ptr);
}
allocated_memory_spans.pop_back();
bucket->MaintainActiveList();
ASSERT_NE(bucket->active_slot_spans_head, SlotSpan::get_sentinel_slot_span());
EXPECT_FALSE(bucket->active_slot_spans_head->next_slot_span);
ASSERT_NE(bucket->empty_slot_spans_head, SlotSpan::get_sentinel_slot_span());
EXPECT_FALSE(bucket->empty_slot_spans_head->next_slot_span);
EXPECT_EQ(8u, bucket->num_full_slot_spans);
for (const auto& span : allocated_memory_spans) {
for (void* ptr : span) {
allocator.root()->Free(ptr);
}
}
}
TEST_P(PartitionAllocTest, ReallocMovesCookie) {
static const size_t kSize = MaxRegularSlotSpanSize();
void* ptr = allocator.root()->Alloc(kSize + 1, type_name);
EXPECT_TRUE(ptr);
PA_UNSAFE_TODO(memset(ptr, 0xbd, kSize + 1));
ptr = allocator.root()->Realloc(ptr, kSize + 2, type_name);
EXPECT_TRUE(ptr);
PA_UNSAFE_TODO(memset(ptr, 0xbd, kSize + 2));
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, SmallReallocDoesNotMoveTrailingCookie) {
static constexpr size_t kSize = 264;
void* ptr = allocator.root()->Alloc(kSize, type_name);
EXPECT_TRUE(ptr);
ptr = allocator.root()->Realloc(ptr, kSize + 16, type_name);
EXPECT_TRUE(ptr);
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, ZeroFill) {
static constexpr size_t kAllZerosSentinel =
std::numeric_limits<size_t>::max();
for (size_t size : kTestSizes) {
char* p = static_cast<char*>(
allocator.root()->Alloc<AllocFlags::kZeroFill>(size));
size_t non_zero_position = kAllZerosSentinel;
for (size_t i = 0; i < size; ++i) {
if (0 != PA_UNSAFE_TODO(p[i])) {
non_zero_position = i;
break;
}
}
EXPECT_EQ(kAllZerosSentinel, non_zero_position)
<< "test allocation size: " << size;
allocator.root()->Free(p);
}
for (int i = 0; i < 10; ++i) {
SCOPED_TRACE(i);
AllocateRandomly<AllocFlags::kZeroFill>(allocator.root(), 250,
GetParam().free_func);
}
}
TEST_P(PartitionAllocWithSizedFreeTest, SchedulerLoopQuarantine) {
internal::ScopedSchedulerLoopQuarantineBranchAccessorForTesting branch(
allocator.root());
for (size_t size : kTestSizes) {
SCOPED_TRACE(size);
ASSERT_GT(branch.GetCapacityInBytes(), size);
void* object = allocator.root()->Alloc(size);
allocator.root()->Free<FreeFlags::kSchedulerLoopQuarantine>(object);
if (size <= BucketIndexLookup::kMaxBucketSize) {
ASSERT_TRUE(branch.IsQuarantined(object));
} else {
ASSERT_FALSE(branch.IsQuarantined(object));
}
}
for (int i = 0; i < 10; ++i) {
SCOPED_TRACE(i);
AllocateRandomly<AllocFlags::kNone, FreeFlags::kSchedulerLoopQuarantine>(
allocator.root(), 250, GetParam().free_func);
}
branch.Purge();
}
TEST_P(PartitionAllocTest, SchedulerLoopQuarantineDisabled) {
PartitionOptions opts = GetCommonPartitionOptions();
opts.scheduler_loop_quarantine_global_config = {};
opts.thread_cache = PartitionOptions::kDisabled;
std::unique_ptr<PartitionRoot> root = CreateCustomTestRoot(opts, {});
void* ptr_to_keep_slot_span = root->Alloc(kTestAllocSize, type_name);
void* ptr = root->Alloc(kTestAllocSize, type_name);
auto* slot_span =
SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(), root.get());
root->Free<FreeFlags::kSchedulerLoopQuarantine>(ptr);
EXPECT_EQ(SlotStart::Unchecked(ptr).Untag().value(),
UntagPtr(slot_span->get_freelist_head()));
root->Free(ptr_to_keep_slot_span);
}
TEST_P(PartitionAllocTest, IntendedLeak) {
PartitionOptions opts = GetCommonPartitionOptions();
opts.thread_cache = PartitionOptions::kDisabled;
opts.backup_ref_ptr = PartitionOptions::kDisabled;
std::unique_ptr<PartitionRoot> root = CreateCustomTestRoot(opts, {});
void* ptr_to_keep_slot_span = root->Alloc(kTestAllocSize, type_name);
void* ptr = root->Alloc(kTestAllocSize, type_name);
SimplePartitionStatsDumper dumper;
root->DumpStats("CustomTestRoot", true, false, &dumper);
uint64_t total_intended_leak_bytes = dumper.stats().total_intended_leak_bytes;
auto* slot_span =
SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(), root.get());
root->Free<FreeFlags::kIntendedLeak>(ptr);
EXPECT_NE(SlotStart::Unchecked(ptr).Untag().value(),
UntagPtr(slot_span->get_freelist_head()));
root->DumpStats("CustomTestRoot", true, false, &dumper);
EXPECT_EQ(dumper.stats().total_intended_leak_bytes,
total_intended_leak_bytes + slot_span->bucket->slot_size);
root->Free(ptr_to_keep_slot_span);
EXPECT_EQ(SlotStart::Unchecked(ptr_to_keep_slot_span).Untag().value(),
UntagPtr(slot_span->get_freelist_head()));
}
TEST_P(PartitionAllocTest, ZapOnFree) {
void* ptr = allocator.root()->Alloc(1, type_name);
EXPECT_TRUE(ptr);
PA_UNSAFE_TODO(memset(ptr, 'A', 1));
constexpr auto kFlags = FreeFlags::kSchedulerLoopQuarantine;
allocator.root()->Free<kFlags>(ptr);
ptr = TagPtr(ptr);
EXPECT_NE('A', *static_cast<unsigned char*>(ptr));
constexpr size_t size = 1024;
ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
PA_UNSAFE_TODO(memset(ptr, 'A', size));
allocator.root()->Free<kFlags>(ptr);
ptr = TagPtr(ptr);
EXPECT_NE('A', *static_cast<unsigned char*>(ptr));
EXPECT_EQ(kFreedByte, *(PA_UNSAFE_TODO(static_cast<unsigned char*>(ptr) +
2 * sizeof(void*))));
EXPECT_EQ(kFreedByte,
*(PA_UNSAFE_TODO(static_cast<unsigned char*>(ptr) + size - 1)));
internal::ScopedSchedulerLoopQuarantineBranchAccessorForTesting branch(
allocator.root());
branch.Purge();
}
#if PA_BUILDFLAG(IS_LINUX) || PA_BUILDFLAG(IS_ANDROID) || \
PA_BUILDFLAG(IS_CHROMEOS)
TEST_P(PartitionAllocTest, InaccessibleRegionAfterSlotSpans) {
if (kUseFewerMemoryRegions) {
GTEST_SKIP();
}
auto* root = allocator.root();
PartitionBucket* incomplete_bucket = nullptr;
for (size_t alloc_size = 0;
alloc_size < MaxRegularSlotSpanSize() - ExtraAllocSize(allocator);
alloc_size++) {
size_t index = SizeToIndex(alloc_size + ExtraAllocSize(allocator));
auto& bucket = PA_UNSAFE_TODO(root->buckets_[index]);
if (bucket.get_bytes_per_span() != bucket.get_pages_per_slot_span()
<< PartitionPageShift()) {
incomplete_bucket = &bucket;
break;
}
}
if (!incomplete_bucket) {
GTEST_SKIP();
}
void* ptr =
root->Alloc(incomplete_bucket->slot_size - ExtraAllocSize(allocator), "");
ASSERT_TRUE(ptr);
uintptr_t start =
SlotSpan::ToSlotSpanStart(SlotSpan::FromAddr(UntagPtr(ptr), root), root)
.value();
uintptr_t end = start + incomplete_bucket->get_bytes_per_span();
std::string proc_maps;
std::vector<base::debug::MappedMemoryRegion> regions;
ASSERT_TRUE(base::debug::ReadProcMaps(&proc_maps));
ASSERT_TRUE(base::debug::ParseProcMaps(proc_maps, ®ions));
bool found = false;
for (const auto& region : regions) {
if (region.start == end) {
found = true;
EXPECT_EQ(region.permissions, base::debug::MappedMemoryRegion::PRIVATE);
break;
}
}
EXPECT_TRUE(found);
root->Free(ptr);
}
TEST_P(PartitionAllocTest, FewerMemoryRegions) {
static_assert(kUseFewerMemoryRegions);
auto* root = allocator.root();
PartitionBucket* incomplete_bucket = nullptr;
for (size_t alloc_size = 0;
alloc_size < MaxRegularSlotSpanSize() - ExtraAllocSize(allocator);
alloc_size++) {
size_t index = SizeToIndex(alloc_size + ExtraAllocSize(allocator));
auto& bucket = PA_UNSAFE_TODO(root->buckets_[index]);
if (bucket.get_bytes_per_span() != bucket.get_pages_per_slot_span()
<< PartitionPageShift()) {
incomplete_bucket = &bucket;
break;
}
}
if (!incomplete_bucket) {
GTEST_SKIP();
}
void* ptr =
root->Alloc(incomplete_bucket->slot_size - ExtraAllocSize(allocator), "");
ASSERT_TRUE(ptr);
uintptr_t start =
SlotSpan::ToSlotSpanStart(SlotSpan::FromAddr(UntagPtr(ptr), root), root)
.value();
uintptr_t end = start + incomplete_bucket->get_bytes_per_span();
std::string proc_maps;
std::vector<base::debug::MappedMemoryRegion> regions;
ASSERT_TRUE(base::debug::ReadProcMaps(&proc_maps));
ASSERT_TRUE(base::debug::ParseProcMaps(proc_maps, ®ions));
bool found = false;
for (const auto& region : regions) {
if (region.start == end) {
found = true;
break;
}
}
EXPECT_FALSE(found);
found = false;
for (const auto& region : regions) {
if (region.start <= end && region.end >= end) {
EXPECT_EQ(region.permissions,
base::debug::MappedMemoryRegion::READ |
base::debug::MappedMemoryRegion::WRITE |
base::debug::MappedMemoryRegion::PRIVATE);
found = true;
break;
}
}
EXPECT_TRUE(found);
root->Free(ptr);
}
#endif
TEST_P(PartitionAllocTest, ZeroFreedMemory) {
auto* root = allocator.root();
ASSERT_TRUE(root->settings_.eventually_zero_freed_memory);
constexpr int kByte = 'A';
auto alloc_and_return_freed_pointer = [&](size_t size) {
void* ptr = allocator.root()->Alloc(size, type_name);
EXPECT_TRUE(ptr);
PA_UNSAFE_TODO(memset(ptr, kByte, size));
allocator.root()->Free(ptr);
ptr = TagPtr(ptr);
EXPECT_NE(kByte, *static_cast<unsigned char*>(ptr));
return ptr;
};
size_t size = 1024;
void* ptr = alloc_and_return_freed_pointer(size);
EXPECT_EQ(0, *(PA_UNSAFE_TODO(static_cast<unsigned char*>(ptr) +
2 * sizeof(void*))));
EXPECT_EQ(0, *(PA_UNSAFE_TODO(static_cast<unsigned char*>(ptr) + size - 1)));
size = MaxRegularSlotSpanSize() + 1;
ptr = alloc_and_return_freed_pointer(size);
EXPECT_NE(0, *(PA_UNSAFE_TODO(static_cast<unsigned char*>(ptr) +
2 * sizeof(void*))));
EXPECT_NE(0, *(PA_UNSAFE_TODO(static_cast<unsigned char*>(ptr) + size - 1)));
}
TEST_P(PartitionAllocTest, Bug_897585) {
size_t kInitialSize = 983050;
size_t kDesiredSize = 983100;
ASSERT_GT(kInitialSize, BucketIndexLookup::kMaxBucketSize);
ASSERT_GT(kDesiredSize, BucketIndexLookup::kMaxBucketSize);
void* ptr = allocator.root()->Alloc<AllocFlags::kReturnNull>(kInitialSize);
ASSERT_NE(nullptr, ptr);
ptr = allocator.root()->Realloc<AllocFlags::kReturnNull>(ptr, kDesiredSize,
nullptr);
ASSERT_NE(nullptr, ptr);
PA_UNSAFE_TODO(memset(ptr, 0xbd, kDesiredSize));
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, OverrideHooks) {
constexpr size_t kOverriddenSize = 1234;
constexpr const char* kOverriddenType = "Overridden type";
constexpr unsigned char kOverriddenChar = 'A';
static volatile bool free_called = false;
static void* overridden_allocation = nullptr;
overridden_allocation = malloc(kOverriddenSize);
PA_UNSAFE_TODO(
memset(overridden_allocation, kOverriddenChar, kOverriddenSize));
PartitionAllocHooks::SetOverrideHooks(
[](void** out, AllocFlags flags, size_t size,
const char* type_name) -> bool {
if (size == kOverriddenSize && type_name == kOverriddenType) {
*out = overridden_allocation;
return true;
}
return false;
},
[](void* address) -> bool {
if (address == overridden_allocation) {
free_called = true;
return true;
}
return false;
},
[](size_t* out, void* address) -> bool {
if (address == overridden_allocation) {
*out = kOverriddenSize;
return true;
}
return false;
});
void* ptr = allocator.root()->Alloc<AllocFlags::kReturnNull>(kOverriddenSize,
kOverriddenType);
ASSERT_EQ(ptr, overridden_allocation);
allocator.root()->Free(ptr);
EXPECT_TRUE(free_called);
free_called = false;
ptr = allocator.root()->Realloc<AllocFlags::kReturnNull>(ptr, 1, nullptr);
ASSERT_NE(ptr, nullptr);
EXPECT_NE(ptr, overridden_allocation);
EXPECT_TRUE(free_called);
EXPECT_EQ(*(char*)ptr, kOverriddenChar);
allocator.root()->Free(ptr);
PartitionAllocHooks::SetOverrideHooks(nullptr, nullptr, nullptr);
free(overridden_allocation);
}
TEST_P(PartitionAllocTest, Alignment) {
std::vector<void*> allocated_ptrs;
for (size_t size = 1; size <= PartitionPageSize(); size <<= 1) {
if (size <= ExtraAllocSize(allocator)) {
continue;
}
size_t requested_size = size - ExtraAllocSize(allocator);
size_t expected_alignment = size;
for (int index = 0; index < 3; index++) {
void* ptr = allocator.root()->Alloc(requested_size);
allocated_ptrs.push_back(ptr);
EXPECT_EQ(0u,
SlotStart::Unchecked(ptr).Untag().value() % expected_alignment)
<< (index + 1) << "-th allocation of size=" << size;
}
}
for (void* ptr : allocated_ptrs) {
allocator.root()->Free(ptr);
}
}
TEST_P(PartitionAllocTest, FundamentalAlignment) {
size_t fundamental_alignment = kAlignment;
for (size_t size = 0; size < SystemPageSize(); size++) {
void* ptr = allocator.root()->Alloc(size);
void* ptr2 = allocator.root()->Alloc(size);
void* ptr3 = allocator.root()->Alloc(size);
EXPECT_EQ(UntagPtr(ptr) % fundamental_alignment, 0u);
EXPECT_EQ(UntagPtr(ptr2) % fundamental_alignment, 0u);
EXPECT_EQ(UntagPtr(ptr3) % fundamental_alignment, 0u);
uintptr_t slot_start = SlotStart::Unchecked(ptr).Untag().value();
EXPECT_EQ(allocator.root()->AllocationCapacityFromSlotStart(
internal::UntaggedSlotStart::Unchecked(slot_start)) %
fundamental_alignment,
-ExtraAllocSize(allocator) % fundamental_alignment);
allocator.root()->Free(ptr);
allocator.root()->Free(ptr2);
allocator.root()->Free(ptr3);
}
}
void VerifyAlignment(PartitionRoot* root,
size_t size,
size_t alignment,
FreeFunction free_func) {
std::vector<void*> allocated_ptrs;
for (int index = 0; index < 3; index++) {
void* ptr = root->AlignedAlloc(alignment, size);
ASSERT_TRUE(ptr);
allocated_ptrs.push_back(ptr);
EXPECT_EQ(0ull, UntagPtr(ptr) % alignment)
<< (index + 1) << "-th allocation of size=" << size
<< ", alignment=" << alignment;
}
for (void* ptr : allocated_ptrs) {
free_func(root, ptr, size, alignment);
}
}
TEST_P(PartitionAllocWithFreeWithSizeAndAlignmentTest, AlignedAlloc) {
size_t alloc_sizes[] = {1,
10,
100,
1000,
10000,
60000,
70000,
130000,
500000,
900000,
BucketIndexLookup::kMaxBucketSize + 1,
2 * BucketIndexLookup::kMaxBucketSize,
kSuperPageSize - 2 * PartitionPageSize(),
4 * BucketIndexLookup::kMaxBucketSize};
for (size_t alloc_size : alloc_sizes) {
for (size_t alignment = 1; alignment <= kMaxSupportedAlignment;
alignment <<= 1) {
VerifyAlignment(allocator.root(), alloc_size, alignment,
GetParam().free_func);
}
}
}
TEST_P(PartitionAllocTest, OptimizedGetSlotNumber) {
for (size_t i = 0; i < BucketIndexLookup::kNumBuckets; ++i) {
auto& bucket = PA_UNSAFE_TODO(allocator.root()->buckets_[i]);
if (SizeToIndex(bucket.slot_size) != i) {
continue;
}
for (size_t slot = 0, offset = 0; slot < bucket.get_slots_per_span();
++slot, offset += bucket.slot_size) {
EXPECT_EQ(slot, bucket.GetSlotNumber(offset));
EXPECT_EQ(slot, bucket.GetSlotNumber(offset + bucket.slot_size / 2));
EXPECT_EQ(slot, bucket.GetSlotNumber(offset + bucket.slot_size - 1));
}
}
}
TEST_P(PartitionAllocTest, GetUsableSizeNull) {
EXPECT_EQ(0ULL, PartitionRoot::GetUsableSize(nullptr));
}
TEST_P(PartitionAllocTest, GetUsableSize) {
size_t delta = 31;
for (size_t size = 1; size <= kMinDirectMappedDownsize; size += delta) {
void* ptr = allocator.root()->Alloc(size);
EXPECT_TRUE(ptr);
size_t usable_size = PartitionRoot::GetUsableSize(ptr);
EXPECT_LE(size, usable_size);
PA_UNSAFE_TODO(memset(ptr, 0xDE, usable_size));
allocator.root()->Free(ptr);
}
}
TEST_P(PartitionAllocTest, Bookkeeping) {
auto& root = *allocator.root();
EXPECT_EQ(0U, root.total_size_of_committed_pages_);
EXPECT_EQ(0U, root.max_size_of_committed_pages_);
EXPECT_EQ(0U, root.get_total_size_of_allocated_bytes());
EXPECT_EQ(0U, root.get_max_size_of_allocated_bytes());
EXPECT_EQ(0U, root.total_size_of_super_pages_);
size_t small_size = 1000;
void* ptr = root.Alloc(small_size - ExtraAllocSize(allocator), type_name);
size_t expected_committed_size =
kUseLazyCommit ? SystemPageSize() : PartitionPageSize();
size_t expected_super_pages_size = kSuperPageSize;
size_t expected_max_committed_size = expected_committed_size;
size_t bucket_index = SizeToIndex(small_size - ExtraAllocSize(allocator));
PartitionBucket* bucket = PA_UNSAFE_TODO(&root.buckets_[bucket_index]);
size_t expected_total_allocated_size = bucket->slot_size;
size_t expected_max_allocated_size = expected_total_allocated_size;
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
root.Free(ptr);
expected_total_allocated_size = 0U;
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
ptr = root.Alloc(small_size - ExtraAllocSize(allocator), type_name);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
root.Free(ptr);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
ptr = root.Alloc(2 * small_size - ExtraAllocSize(allocator), type_name);
expected_committed_size +=
kUseLazyCommit ? SystemPageSize() : PartitionPageSize();
expected_max_committed_size =
std::max(expected_max_committed_size, expected_committed_size);
expected_max_allocated_size =
std::max(expected_max_allocated_size, static_cast<size_t>(2048));
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
root.Free(ptr);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
size_t big_size =
BucketIndexLookup::kMaxBucketSize * 4 / 5 - SystemPageSize();
ASSERT_GT(big_size, MaxRegularSlotSpanSize());
ASSERT_LE(big_size, BucketIndexLookup::kMaxBucketSize);
bucket_index = SizeToIndex(big_size - ExtraAllocSize(allocator));
bucket = PA_UNSAFE_TODO(&root.buckets_[bucket_index]);
ASSERT_LT(big_size, bucket->get_bytes_per_span());
ASSERT_NE(big_size % PartitionPageSize(), 0U);
ptr = root.Alloc(big_size - ExtraAllocSize(allocator), type_name);
expected_committed_size += bucket->get_bytes_per_span();
expected_max_committed_size =
std::max(expected_max_committed_size, expected_committed_size);
expected_total_allocated_size += bucket->get_bytes_per_span();
expected_max_allocated_size =
std::max(expected_max_allocated_size, expected_total_allocated_size);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
void* ptr2 = root.Alloc(big_size - ExtraAllocSize(allocator), type_name);
expected_committed_size += bucket->get_bytes_per_span();
expected_max_committed_size =
std::max(expected_max_committed_size, expected_committed_size);
expected_total_allocated_size += bucket->get_bytes_per_span();
expected_max_allocated_size =
std::max(expected_max_allocated_size, expected_total_allocated_size);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
void* ptr3 = root.Alloc(big_size - ExtraAllocSize(allocator), type_name);
expected_committed_size += bucket->get_bytes_per_span();
expected_max_committed_size =
std::max(expected_max_committed_size, expected_committed_size);
expected_total_allocated_size += bucket->get_bytes_per_span();
expected_max_allocated_size =
std::max(expected_max_allocated_size, expected_total_allocated_size);
expected_super_pages_size += kSuperPageSize;
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
root.Free(ptr);
root.Free(ptr2);
root.Free(ptr3);
expected_total_allocated_size -= 3 * bucket->get_bytes_per_span();
expected_max_allocated_size =
std::max(expected_max_allocated_size, expected_total_allocated_size);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
root.PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
expected_committed_size = 0;
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
EXPECT_EQ(0U, root.total_size_of_direct_mapped_pages_);
size_t huge_sizes[] = {
BucketIndexLookup::kMaxBucketSize + SystemPageSize(),
BucketIndexLookup::kMaxBucketSize + SystemPageSize() + 123,
kSuperPageSize - PageAllocationGranularity(),
kSuperPageSize - SystemPageSize() - PartitionPageSize(),
kSuperPageSize - PartitionPageSize(),
kSuperPageSize - SystemPageSize(),
kSuperPageSize,
kSuperPageSize + SystemPageSize(),
kSuperPageSize + PartitionPageSize(),
kSuperPageSize + SystemPageSize() + PartitionPageSize(),
kSuperPageSize + PageAllocationGranularity(),
kSuperPageSize + DirectMapAllocationGranularity(),
};
size_t alignments[] = {
PartitionPageSize(),
2 * PartitionPageSize(),
kMaxSupportedAlignment / 2,
kMaxSupportedAlignment,
};
for (size_t huge_size : huge_sizes) {
ASSERT_GT(huge_size, BucketIndexLookup::kMaxBucketSize);
for (size_t alignment : alignments) {
size_t aligned_size = partition_alloc::internal::base::bits::AlignUp(
huge_size, SystemPageSize());
ptr = root.AllocInternalForTesting(huge_size - ExtraAllocSize(allocator),
alignment, type_name);
expected_committed_size += aligned_size;
expected_max_committed_size =
std::max(expected_max_committed_size, expected_committed_size);
expected_total_allocated_size += aligned_size;
expected_max_allocated_size =
std::max(expected_max_allocated_size, expected_total_allocated_size);
size_t surrounding_pages_size =
PartitionRoot::GetDirectMapMetadataAndGuardPagesSize() + alignment -
PartitionPageSize();
size_t expected_direct_map_size =
partition_alloc::internal::base::bits::AlignUp(
aligned_size + surrounding_pages_size,
DirectMapAllocationGranularity());
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
EXPECT_EQ(expected_direct_map_size,
root.total_size_of_direct_mapped_pages_);
root.Free(ptr);
expected_committed_size -= aligned_size;
expected_direct_map_size = 0;
expected_max_committed_size =
std::max(expected_max_committed_size, expected_committed_size);
expected_total_allocated_size -= aligned_size;
expected_max_allocated_size =
std::max(expected_max_allocated_size, expected_total_allocated_size);
EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages_);
EXPECT_EQ(expected_max_committed_size, root.max_size_of_committed_pages_);
EXPECT_EQ(expected_total_allocated_size,
root.get_total_size_of_allocated_bytes());
EXPECT_EQ(expected_max_allocated_size,
root.get_max_size_of_allocated_bytes());
EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages_);
EXPECT_EQ(expected_direct_map_size,
root.total_size_of_direct_mapped_pages_);
}
}
}
#if PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
TEST_P(PartitionAllocTest, RefCountBasic) {
if (!UseBRPPool()) {
return;
}
constexpr uint64_t kCookie = 0x1234567890ABCDEF;
constexpr uint64_t kQuarantined = 0xEFEFEFEFEFEFEFEF;
size_t alloc_size = 64 - ExtraAllocSize(allocator);
uint64_t* ptr1 =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
EXPECT_TRUE(ptr1);
*ptr1 = kCookie;
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr1);
EXPECT_TRUE(in_slot_metadata->IsAliveWithNoKnownRefs());
in_slot_metadata->Acquire();
EXPECT_FALSE(in_slot_metadata->Release());
EXPECT_TRUE(in_slot_metadata->IsAliveWithNoKnownRefs());
EXPECT_EQ(*ptr1, kCookie);
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_FALSE(in_slot_metadata->IsAliveWithNoKnownRefs());
allocator.root()->Free(ptr1);
ptr1 = TagPtr(ptr1);
EXPECT_NE(*ptr1, kCookie);
EXPECT_EQ(*ptr1, kQuarantined);
uint64_t* ptr2 =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
PA_EXPECT_PTR_NE(ptr1, ptr2);
allocator.root()->Free(ptr2);
in_slot_metadata = TagPtr(in_slot_metadata);
EXPECT_TRUE(in_slot_metadata->ReleaseFromUnprotectedPtr());
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr1));
PartitionRoot::FreeAfterBRPQuarantine(
internal::UntaggedSlotStart(slot_info.slot_start), slot_info.size);
uint64_t* ptr3 =
static_cast<uint64_t*>(allocator.root()->Alloc(alloc_size, type_name));
PA_EXPECT_PTR_EQ(ptr1, ptr3);
allocator.root()->Free(ptr3);
}
void PartitionAllocTest::RunRefCountReallocSubtest(size_t orig_size,
size_t new_size) {
void* ptr1 = allocator.root()->Alloc(orig_size, type_name);
EXPECT_TRUE(ptr1);
auto* in_slot_metadata1 =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr1);
EXPECT_TRUE(in_slot_metadata1->IsAliveWithNoKnownRefs());
in_slot_metadata1->AcquireFromUnprotectedPtr();
EXPECT_FALSE(in_slot_metadata1->IsAliveWithNoKnownRefs());
void* ptr2 = allocator.root()->Realloc(ptr1, new_size, type_name);
EXPECT_TRUE(ptr2);
in_slot_metadata1 = TagPtr(in_slot_metadata1);
auto* in_slot_metadata2 =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr2);
if (UntagPtr(ptr1) == UntagPtr(ptr2)) {
EXPECT_EQ(in_slot_metadata1, in_slot_metadata2);
EXPECT_FALSE(in_slot_metadata2->IsAliveWithNoKnownRefs());
EXPECT_FALSE(in_slot_metadata2->ReleaseFromUnprotectedPtr());
} else {
EXPECT_NE(in_slot_metadata1, in_slot_metadata2);
EXPECT_FALSE(in_slot_metadata1->IsAlive());
EXPECT_FALSE(in_slot_metadata1->IsAliveWithNoKnownRefs());
EXPECT_TRUE(in_slot_metadata2->IsAliveWithNoKnownRefs());
EXPECT_TRUE(in_slot_metadata1->ReleaseFromUnprotectedPtr());
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr1));
PartitionRoot::FreeAfterBRPQuarantine(
internal::UntaggedSlotStart(slot_info.slot_start), slot_info.size);
}
allocator.root()->Free(ptr2);
}
TEST_P(PartitionAllocTest, RefCountRealloc) {
if (!UseBRPPool()) {
return;
}
size_t raw_sizes[] = {500, 5000, 50000, 400000, 5000000};
for (size_t raw_size : raw_sizes) {
size_t alloc_size = raw_size - ExtraAllocSize(allocator);
RunRefCountReallocSubtest(alloc_size, alloc_size - 9);
RunRefCountReallocSubtest(alloc_size, alloc_size + 9);
RunRefCountReallocSubtest(alloc_size, alloc_size * 2);
RunRefCountReallocSubtest(alloc_size, alloc_size / 2);
RunRefCountReallocSubtest(alloc_size, alloc_size / 10 * 11);
RunRefCountReallocSubtest(alloc_size, alloc_size / 10 * 9);
}
}
TEST_P(PartitionAllocTest, ExtraExtrasSize) {
constexpr size_t kExtraExtrasSize = 4;
constexpr size_t kSlotSize = 64;
std::unique_ptr<PartitionRoot> root_with_extra = CreateCustomTestRoot(
[&]() {
PartitionOptions opts = GetCommonPartitionOptions();
opts.backup_ref_ptr = PartitionOptions::kEnabled;
opts.backup_ref_ptr_extra_extras_size = kExtraExtrasSize;
return opts;
}(),
{});
std::unique_ptr<PartitionRoot> root_no_extra = CreateCustomTestRoot(
[&]() {
PartitionOptions opts = GetCommonPartitionOptions();
opts.backup_ref_ptr = PartitionOptions::kEnabled;
return opts;
}(),
{});
const size_t alloc_size =
root_no_extra->AdjustSizeForExtrasSubtract(kSlotSize);
EXPECT_EQ(
root_with_extra->AdjustSizeForExtrasAdd(alloc_size),
root_no_extra->AdjustSizeForExtrasAdd(alloc_size) + kExtraExtrasSize);
void* ptr1 = root_with_extra->Alloc(alloc_size, type_name);
auto* slot_span1 = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr1).Untag(),
root_with_extra.get());
void* ptr2 = root_no_extra->Alloc(alloc_size, type_name);
auto* slot_span2 = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr2).Untag(),
root_no_extra.get());
EXPECT_NE(slot_span1->bucket->slot_size, slot_span2->bucket->slot_size);
root_no_extra->Free(ptr2);
}
TEST_P(PartitionAllocTest, ExtraExtrasNullfyOffByOneDetection) {
#if !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
GTEST_SKIP()
<< "This test is not compatible with 32-bit bucket distribution.";
#else
std::unique_ptr<PartitionRoot> root_no_extra = CreateCustomTestRoot(
[&]() {
PartitionOptions opts = GetCommonPartitionOptions();
opts.backup_ref_ptr = PartitionOptions::kEnabled;
return opts;
}(),
{});
int64_t* ptr1 = static_cast<int64_t*>(root_no_extra->Alloc(8));
int64_t* ptr2 = static_cast<int64_t*>(root_no_extra->Alloc(8));
EXPECT_DEATH_IF_SUPPORTED(
(PA_UNSAFE_TODO(ptr2[1]) = 0, root_no_extra->Free(ptr2)), "");
root_no_extra->Free(ptr2);
root_no_extra->Free(ptr1);
std::unique_ptr<PartitionRoot> root_with_extra = CreateCustomTestRoot(
[&]() {
PartitionOptions opts = GetCommonPartitionOptions();
opts.backup_ref_ptr = PartitionOptions::kEnabled;
opts.backup_ref_ptr_extra_extras_size = 8;
return opts;
}(),
{});
ptr1 = static_cast<int64_t*>(root_with_extra->Alloc(8));
ptr2 = static_cast<int64_t*>(root_with_extra->Alloc(8));
PA_UNSAFE_TODO(ptr2[1]) = 0;
root_with_extra->Free(ptr2);
root_with_extra->Free(ptr1);
#endif }
int g_unretained_dangling_raw_ptr_detected_count = 0;
class UnretainedDanglingRawPtrTest : public PartitionAllocTest {
public:
void SetUp() override {
PartitionAllocTest::SetUp();
g_unretained_dangling_raw_ptr_detected_count = 0;
old_detected_fn_ = partition_alloc::GetUnretainedDanglingRawPtrDetectedFn();
partition_alloc::SetUnretainedDanglingRawPtrDetectedFn(
&UnretainedDanglingRawPtrTest::DanglingRawPtrDetected);
old_unretained_dangling_ptr_enabled_ =
partition_alloc::SetUnretainedDanglingRawPtrCheckEnabled(true);
}
void TearDown() override {
partition_alloc::SetUnretainedDanglingRawPtrDetectedFn(old_detected_fn_);
partition_alloc::SetUnretainedDanglingRawPtrCheckEnabled(
old_unretained_dangling_ptr_enabled_);
PartitionAllocTest::TearDown();
}
private:
static void DanglingRawPtrDetected(uintptr_t) {
g_unretained_dangling_raw_ptr_detected_count++;
}
partition_alloc::DanglingRawPtrDetectedFn* old_detected_fn_;
bool old_unretained_dangling_ptr_enabled_;
};
INSTANTIATE_TEST_SUITE_P(AlternateTestParams,
UnretainedDanglingRawPtrTest,
testing::ValuesIn(GetPartitionAllocTestParams()));
TEST_P(UnretainedDanglingRawPtrTest, UnretainedDanglingPtrNoReport) {
if (!UseBRPPool()) {
return;
}
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->Acquire();
EXPECT_TRUE(in_slot_metadata->IsAlive());
in_slot_metadata->ReportIfDangling();
EXPECT_EQ(g_unretained_dangling_raw_ptr_detected_count, 0);
EXPECT_FALSE(in_slot_metadata->Release());
allocator.root()->Free(ptr);
}
TEST_P(UnretainedDanglingRawPtrTest, UnretainedDanglingPtrShouldReport) {
if (!UseBRPPool()) {
return;
}
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_TRUE(in_slot_metadata->IsAlive());
allocator.root()->Free(ptr);
EXPECT_FALSE(in_slot_metadata->IsAlive());
in_slot_metadata->ReportIfDangling();
EXPECT_EQ(g_unretained_dangling_raw_ptr_detected_count, 1);
EXPECT_TRUE(in_slot_metadata->ReleaseFromUnprotectedPtr());
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
#if !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
TEST_P(PartitionAllocTest, BackupRefPtrGuardRegion) {
if (!UseBRPPool()) {
return;
}
size_t alignment = internal::PageAllocationGranularity();
uintptr_t requested_address;
memset(&requested_address, internal::kQuarantinedByte,
sizeof(requested_address));
requested_address = RoundDownToPageAllocationGranularity(requested_address);
uintptr_t allocated_address =
AllocPages(requested_address, alignment, alignment,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kReadWrite),
PageTag::kPartitionAlloc);
EXPECT_NE(allocated_address, requested_address);
if (allocated_address) {
FreePages(allocated_address, alignment);
}
}
#endif #endif
#if PA_BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS)
TEST_P(PartitionAllocTest, DanglingPtr) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
uint64_t* ptr = static_cast<uint64_t*>(
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name));
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->Acquire();
in_slot_metadata->Acquire();
in_slot_metadata->Acquire();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 1);
EXPECT_TRUE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 2);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, DanglingDanglingPtr) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
uint64_t* ptr = static_cast<uint64_t*>(
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name));
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
in_slot_metadata->AcquireFromUnprotectedPtr();
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_TRUE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, DanglingMixedReleaseRawPtrFirst) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
uint64_t* ptr = static_cast<uint64_t*>(
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name));
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
in_slot_metadata->Acquire();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 1);
EXPECT_TRUE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 1);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, DanglingMixedReleaseDanglingPtrFirst) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
void* ptr =
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
in_slot_metadata->Acquire();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_TRUE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 1);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, DanglingPtrUsedToAcquireNewRawPtr) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
void* ptr =
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
in_slot_metadata->Acquire();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_TRUE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, DanglingPtrUsedToAcquireNewRawPtrVariant) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
void* ptr =
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
in_slot_metadata->Acquire();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_TRUE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, RawPtrReleasedBeforeFree) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
void* ptr =
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->Acquire();
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(in_slot_metadata->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
allocator.root()->Free(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_TRUE(in_slot_metadata->ReleaseFromUnprotectedPtr());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
}
TEST_P(PartitionAllocTest, DanglingPtrReleaseToSchedulerLoopQuarantine) {
if (!UseBRPPool()) {
return;
}
CountDanglingRawPtr dangling_checks;
uint64_t* ptr = static_cast<uint64_t*>(
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name));
auto* ref_count =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
ref_count->Acquire();
ref_count->Acquire();
ref_count->Acquire();
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(ref_count->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 0);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
internal::ScopedSchedulerLoopQuarantineBranchAccessorForTesting branch(
allocator.root());
allocator.root()->Free<FreeFlags::kSchedulerLoopQuarantine>(ptr);
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 0);
EXPECT_FALSE(branch.IsQuarantined(ptr));
EXPECT_FALSE(ref_count->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 1);
EXPECT_FALSE(branch.IsQuarantined(ptr));
EXPECT_TRUE(ref_count->Release());
EXPECT_EQ(g_dangling_raw_ptr_detected_count, 1);
EXPECT_EQ(g_dangling_raw_ptr_released_count, 2);
auto slot_info = partition_alloc::SlotAddressAndSize::FromBRPPool(
reinterpret_cast<uintptr_t>(ptr));
PartitionRoot::FreeAfterBRPQuarantine(slot_info.slot_start, slot_info.size);
EXPECT_TRUE(branch.IsQuarantined(ptr));
branch.Purge();
}
#if PA_USE_DEATH_TESTS()
#if !defined(OFFICIAL_BUILD) || PA_BUILDFLAG(IS_DEBUG)
TEST_P(PartitionAllocDeathTest, ReleaseUnderflowRawPtr) {
if (!UseBRPPool()) {
return;
}
void* ptr =
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->Acquire();
EXPECT_FALSE(in_slot_metadata->Release());
PA_EXPECT_DCHECK_DEATH(in_slot_metadata->Release());
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocDeathTest, ReleaseUnderflowDanglingPtr) {
if (!UseBRPPool()) {
return;
}
void* ptr =
allocator.root()->Alloc(64 - ExtraAllocSize(allocator), type_name);
auto* in_slot_metadata =
allocator.root()->InSlotMetadataPointerFromObjectForTesting(ptr);
in_slot_metadata->AcquireFromUnprotectedPtr();
EXPECT_FALSE(in_slot_metadata->ReleaseFromUnprotectedPtr());
PA_EXPECT_DCHECK_DEATH(in_slot_metadata->ReleaseFromUnprotectedPtr());
allocator.root()->Free(ptr);
}
#endif #endif #endif
TEST_P(PartitionAllocTest, ReservationOffset) {
static constexpr uint16_t kOffsetTagNotAllocated =
std::numeric_limits<uint16_t>::max();
static constexpr uint16_t kOffsetTagNormalBuckets =
std::numeric_limits<uint16_t>::max() - 1;
ReservationOffsetTable table = allocator.root()->GetReservationOffsetTable();
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
uintptr_t address = UntagPtr(ptr);
EXPECT_EQ(kOffsetTagNormalBuckets,
*table.GetOffsetPointerForTesting(address));
allocator.root()->Free(ptr);
size_t large_size = kSuperPageSize * 5 + PartitionPageSize() * .5f;
ASSERT_GT(large_size, BucketIndexLookup::kMaxBucketSize);
ptr = allocator.root()->Alloc(large_size, type_name);
EXPECT_TRUE(ptr);
address = UntagPtr(ptr);
EXPECT_EQ(0U, *table.GetOffsetPointerForTesting(address));
EXPECT_EQ(1U, *table.GetOffsetPointerForTesting(address + kSuperPageSize));
EXPECT_EQ(2U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 2));
EXPECT_EQ(3U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 3));
EXPECT_EQ(4U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 4));
EXPECT_EQ(5U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 5));
void* new_ptr = allocator.root()->Realloc(ptr, large_size * .8, type_name);
EXPECT_EQ(new_ptr, ptr);
EXPECT_EQ(0U, *table.GetOffsetPointerForTesting(address));
EXPECT_EQ(1U, *table.GetOffsetPointerForTesting(address + kSuperPageSize));
EXPECT_EQ(2U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 2));
EXPECT_EQ(3U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 3));
EXPECT_EQ(4U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 4));
EXPECT_EQ(5U,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 5));
allocator.root()->Free(ptr);
EXPECT_EQ(kOffsetTagNotAllocated, *table.GetOffsetPointerForTesting(address));
EXPECT_EQ(kOffsetTagNotAllocated,
*table.GetOffsetPointerForTesting(address + kSuperPageSize));
EXPECT_EQ(kOffsetTagNotAllocated,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 2));
EXPECT_EQ(kOffsetTagNotAllocated,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 3));
EXPECT_EQ(kOffsetTagNotAllocated,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 4));
EXPECT_EQ(kOffsetTagNotAllocated,
*table.GetOffsetPointerForTesting(address + kSuperPageSize * 5));
}
TEST_P(PartitionAllocTest, GetReservationStart) {
size_t large_size = kSuperPageSize * 3 + PartitionPageSize() * .5f;
ASSERT_GT(large_size, BucketIndexLookup::kMaxBucketSize);
void* ptr = allocator.root()->Alloc(large_size, type_name);
EXPECT_TRUE(ptr);
uintptr_t slot_start = SlotStart::Unchecked(ptr).Untag().value();
uintptr_t reservation_start = slot_start - PartitionPageSize();
EXPECT_EQ(0U, reservation_start & DirectMapAllocationGranularityOffsetMask());
ReservationOffsetTable table = allocator.root()->GetReservationOffsetTable();
uintptr_t address = UntagPtr(ptr);
for (uintptr_t a = address; a < address + large_size; ++a) {
uintptr_t address2 =
table.GetDirectMapReservationStart(a) + PartitionPageSize();
EXPECT_EQ(slot_start, address2);
}
EXPECT_EQ(reservation_start, table.GetDirectMapReservationStart(slot_start));
allocator.root()->Free(ptr);
}
TEST_P(PartitionAllocTest, CheckReservationType) {
ReservationOffsetTable table = allocator.root()->GetReservationOffsetTable();
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
EXPECT_TRUE(ptr);
uintptr_t address = UntagPtr(ptr);
uintptr_t address_to_check = address;
EXPECT_FALSE(table.IsReservationStart(address_to_check));
EXPECT_TRUE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_FALSE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
address_to_check = address + kTestAllocSize - 1;
EXPECT_FALSE(table.IsReservationStart(address_to_check));
EXPECT_TRUE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_FALSE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
address_to_check =
partition_alloc::internal::base::bits::AlignDown(address, kSuperPageSize);
EXPECT_TRUE(table.IsReservationStart(address_to_check));
EXPECT_TRUE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_FALSE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
allocator.root()->Free(ptr);
address_to_check =
partition_alloc::internal::base::bits::AlignDown(address, kSuperPageSize);
EXPECT_TRUE(table.IsReservationStart(address_to_check));
EXPECT_TRUE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_FALSE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
size_t large_size = 2 * kSuperPageSize;
ASSERT_GT(large_size, BucketIndexLookup::kMaxBucketSize);
ptr = allocator.root()->Alloc(large_size, type_name);
EXPECT_TRUE(ptr);
address = UntagPtr(ptr);
address_to_check = address;
EXPECT_FALSE(table.IsReservationStart(address_to_check));
EXPECT_FALSE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
address_to_check =
partition_alloc::internal::base::bits::AlignUp(address, kSuperPageSize);
EXPECT_FALSE(table.IsReservationStart(address_to_check));
EXPECT_FALSE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
address_to_check = address + large_size - 1;
EXPECT_FALSE(table.IsReservationStart(address_to_check));
EXPECT_FALSE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
address_to_check =
partition_alloc::internal::base::bits::AlignDown(address, kSuperPageSize);
EXPECT_TRUE(table.IsReservationStart(address_to_check));
EXPECT_FALSE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_TRUE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
allocator.root()->Free(ptr);
address_to_check =
partition_alloc::internal::base::bits::AlignDown(address, kSuperPageSize);
EXPECT_FALSE(
IsManagedByNormalBucketsForTesting(address_to_check, allocator.root()));
EXPECT_FALSE(
IsManagedByDirectMapForTesting(address_to_check, allocator.root()));
EXPECT_FALSE(IsManagedByNormalBucketsOrDirectMapForTesting(address_to_check,
allocator.root()));
#if PA_BUILDFLAG(DCHECKS_ARE_ON) && \
(!defined(OFFICIAL_BUILD) || PA_BUILDFLAG(IS_DEBUG))
EXPECT_DEATH_IF_SUPPORTED(table.IsReservationStart(address_to_check), "");
#endif
}
TEST_P(PartitionAllocTest, CrossPartitionRootRealloc) {
size_t test_size = MaxRegularSlotSpanSize() - ExtraAllocSize(allocator);
void* ptr = allocator.root()->Alloc<AllocFlags::kReturnNull>(test_size);
EXPECT_TRUE(ptr);
allocator.root()->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans |
PurgeFlags::kDiscardUnusedSystemPages);
std::unique_ptr<PartitionRoot> new_root = CreateCustomTestRoot(
GetCommonPartitionOptions(),
PartitionTestOptions{.set_bucket_distribution = true});
void* ptr2 = new_root->Realloc<AllocFlags::kReturnNull>(ptr, test_size + 1024,
nullptr);
EXPECT_TRUE(ptr2);
PA_EXPECT_PTR_NE(ptr, ptr2);
}
TEST_P(PartitionAllocTest, FastPathOrReturnNull) {
size_t allocation_size = 64;
EXPECT_FALSE(allocator.root()->Alloc<AllocFlags::kFastPathOrReturnNull>(
allocation_size, ""));
void* ptr = allocator.root()->Alloc(allocation_size);
ASSERT_TRUE(ptr);
void* ptr2 = allocator.root()->Alloc<AllocFlags::kFastPathOrReturnNull>(
allocation_size, "");
EXPECT_TRUE(ptr2);
EXPECT_FALSE(allocator.root()->Alloc<AllocFlags::kFastPathOrReturnNull>(
2 * allocation_size, ""));
size_t allocated_size = 2 * allocation_size;
std::vector<void*> ptrs;
while (void* new_ptr =
allocator.root()->Alloc<AllocFlags::kFastPathOrReturnNull>(
allocation_size, "")) {
ptrs.push_back(new_ptr);
allocated_size += allocation_size;
}
EXPECT_LE(allocated_size,
PartitionPageSize() * kMaxPartitionPagesPerRegularSlotSpan);
for (void* ptr_to_free : ptrs) {
allocator.root()->Free<FreeFlags::kNoHooks>(ptr_to_free);
}
allocator.root()->Free<FreeFlags::kNoHooks>(ptr);
allocator.root()->Free<FreeFlags::kNoHooks>(ptr2);
}
#if PA_USE_DEATH_TESTS()
#if !defined(OFFICIAL_BUILD) || PA_BUILDFLAG(IS_DEBUG)
TEST_P(PartitionAllocDeathTest, CheckTriggered) {
PA_EXPECT_DCHECK_DEATH_WITH(PA_CHECK(5 == 7), "Check failed.*5 == 7");
EXPECT_DEATH(PA_CHECK(5 == 7), "Check failed.*5 == 7");
}
#endif #endif
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_USE_DEATH_TESTS() && \
!PA_BUILDFLAG(IS_CASTOS)
namespace {
PA_NOINLINE void FreeForTest(void* data) {
free(data);
}
class ThreadDelegateForPreforkHandler
: public base::PlatformThreadForTesting::Delegate {
public:
ThreadDelegateForPreforkHandler(std::atomic<bool>& please_stop,
std::atomic<int>& started_threads,
const int alloc_size)
: please_stop_(please_stop),
started_threads_(started_threads),
alloc_size_(alloc_size) {}
void ThreadMain() override {
started_threads_++;
while (!please_stop_.load(std::memory_order_relaxed)) {
void* ptr = malloc(alloc_size_);
FreeForTest(ptr);
}
}
private:
std::atomic<bool>& please_stop_;
std::atomic<int>& started_threads_;
const int alloc_size_;
};
}
TEST_P(PartitionAllocTest, DISABLED_PreforkHandler) {
std::atomic<bool> please_stop;
std::atomic<int> started_threads{0};
constexpr size_t kAllocSize = ThreadCache::kLargeSizeThreshold + 1;
ThreadDelegateForPreforkHandler delegate(please_stop, started_threads,
kAllocSize);
constexpr int kThreads = 4;
base::PlatformThreadHandle thread_handles[kThreads];
for (auto& thread_handle : thread_handles) {
base::PlatformThreadForTesting::Create(0, &delegate, &thread_handle);
}
while (started_threads != kThreads) {
}
EXPECT_EXIT(
{
void* ptr = malloc(kAllocSize);
FreeForTest(ptr);
exit(1);
},
::testing::ExitedWithCode(1), "");
please_stop.store(true);
for (auto& thread_handle : thread_handles) {
base::PlatformThreadForTesting::Join(thread_handle);
}
}
#endif
TEST_P(PartitionAllocTest, GetIndex) {
BucketDistribution distribution = allocator.root()->GetBucketDistribution();
for (size_t size = 0; size <= BucketIndexLookup::kMaxBucketSize; size++) {
uint16_t index = PartitionRoot::SizeToBucketIndex(size, distribution);
ASSERT_LT(index, BucketIndexLookup::kNumBuckets);
size_t bucket_size = BucketIndexLookup::GetBucketSize(index);
ASSERT_LE(size, bucket_size);
ASSERT_EQ(bucket_size % kAlignment, 0u);
size_t minimum_required_size = size == 0 ? 1 : size;
minimum_required_size =
base::bits::AlignUp(minimum_required_size, kAlignment);
double waste_rate =
1 - static_cast<double>(minimum_required_size) / bucket_size;
ASSERT_LE(waste_rate, 1.0 / 4);
if (distribution == PartitionRoot::BucketDistribution::kDenser) {
ASSERT_LE(waste_rate, 1. / 8);
}
}
ASSERT_EQ(PartitionRoot::SizeToBucketIndex(
BucketIndexLookup::kMaxBucketSize + 1, distribution),
BucketIndexLookup::kNumBuckets);
ASSERT_EQ(PartitionRoot::SizeToBucketIndex(std::numeric_limits<size_t>::max(),
distribution),
BucketIndexLookup::kNumBuckets);
for (size_t size = BucketIndexLookup::kMinBucketSize;
size <= BucketIndexLookup::kMaxBucketSize; size <<= 1) {
uint16_t index = PartitionRoot::SizeToBucketIndex(size, distribution);
ASSERT_EQ(BucketIndexLookup::GetBucketSize(index), size);
}
}
TEST_P(PartitionAllocTest, MallocFunctionAnnotations) {
struct TestStruct {
uint64_t a = 0;
uint64_t b = 0;
};
void* buffer = Alloc(sizeof(TestStruct));
auto* x = new (buffer) TestStruct();
EXPECT_EQ(x->a, 0u);
Free(buffer);
}
TEST_P(PartitionAllocTest, ConfigurablePool) {
EXPECT_FALSE(IsConfigurablePoolAvailable());
#if PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)
const size_t max_pool_size = PartitionAddressSpace::ConfigurablePoolMaxSize();
const size_t min_pool_size = PartitionAddressSpace::ConfigurablePoolMinSize();
for (size_t pool_size = max_pool_size; pool_size >= min_pool_size;
pool_size /= 2) {
PA_DCHECK(base::bits::HasSingleBit(pool_size));
EXPECT_FALSE(IsConfigurablePoolAvailable());
uintptr_t pool_base =
AllocPages(pool_size, pool_size,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible),
PageTag::kPartitionAlloc);
EXPECT_NE(0u, pool_base);
PartitionAddressSpace::InitConfigurablePool(pool_base, pool_size);
EXPECT_TRUE(IsConfigurablePoolAvailable());
PartitionOptions opts = GetCommonPartitionOptions();
opts.use_configurable_pool = PartitionOptions::kAllowed;
std::unique_ptr<PartitionRoot> root = CreateCustomTestRoot(
opts, PartitionTestOptions{.uncap_empty_slot_span_memory = true,
.set_bucket_distribution = true});
const size_t count = 250;
std::vector<void*> allocations(count, nullptr);
for (size_t i = 0; i < count; ++i) {
const size_t size =
PA_UNSAFE_TODO(kTestSizes[base::RandGenerator(kTestSizesCount)]);
allocations[i] = root->Alloc(size);
EXPECT_NE(nullptr, allocations[i]);
uintptr_t allocation_base = reinterpret_cast<uintptr_t>(allocations[i]);
EXPECT_EQ(allocation_base, UntagPtr(allocations[i]));
EXPECT_TRUE(allocation_base >= pool_base &&
allocation_base < pool_base + pool_size);
}
PartitionRoot::DeleteForTesting(root.release());
PartitionAddressSpace::UninitConfigurablePoolForTesting();
FreePages(pool_base, pool_size);
}
#endif }
TEST_P(PartitionAllocTest, EmptySlotSpanSizeIsCapped) {
std::unique_ptr<PartitionRoot> root = CreateCustomTestRoot(
GetCommonPartitionOptions(),
PartitionTestOptions{.set_bucket_distribution = true});
std::vector<void*> allocated_memory;
const size_t size = SystemPageSize();
const size_t count = 400;
for (size_t i = 0; i < count; i++) {
void* ptr = root->Alloc(size);
allocated_memory.push_back(ptr);
}
ASSERT_GE(
root->total_size_of_committed_pages_.load(std::memory_order_relaxed),
size * count);
std::vector<void*> single_slot_allocated_memory;
constexpr size_t single_slot_count = kDefaultEmptySlotSpanRingSize - 1;
const size_t single_slot_size = MaxRegularSlotSpanSize() + 1;
ASSERT_LT(MaxRegularSlotSpanSize() * 2,
((count * size) >> root->max_empty_slot_spans_dirty_bytes_shift_));
for (size_t i = 0; i < single_slot_count; i++) {
void* ptr = root->Alloc(single_slot_size);
single_slot_allocated_memory.push_back(ptr);
}
for (void* ptr : single_slot_allocated_memory) {
root->Free(ptr);
}
EXPECT_GT(PA_TS_UNCHECKED_READ(root->empty_slot_spans_dirty_bytes_), 0u);
EXPECT_LT(PA_TS_UNCHECKED_READ(root->empty_slot_spans_dirty_bytes_),
single_slot_count * single_slot_size);
root->PurgeMemory(PurgeFlags::kDecommitEmptySlotSpans);
EXPECT_EQ(PA_TS_UNCHECKED_READ(root->empty_slot_spans_dirty_bytes_), 0u);
for (void* ptr : allocated_memory) {
root->Free(ptr);
}
}
TEST_P(PartitionAllocTest, IncreaseEmptySlotSpanRingSize) {
std::unique_ptr<PartitionRoot> root = CreateCustomTestRoot(
GetCommonPartitionOptions(),
PartitionTestOptions{.uncap_empty_slot_span_memory = true,
.set_bucket_distribution = true});
std::vector<void*> single_slot_allocated_memory;
constexpr size_t single_slot_count = kDefaultEmptySlotSpanRingSize + 10;
const size_t single_slot_size = MaxRegularSlotSpanSize() + 1;
const size_t bucket_size =
PA_UNSAFE_TODO(root->buckets_[SizeToIndex(single_slot_size)]).slot_size;
for (size_t i = 0; i < single_slot_count; i++) {
void* ptr = root->Alloc(single_slot_size);
single_slot_allocated_memory.push_back(ptr);
}
for (void* ptr : single_slot_allocated_memory) {
root->Free(ptr);
}
single_slot_allocated_memory.clear();
EXPECT_EQ(PA_TS_UNCHECKED_READ(root->empty_slot_spans_dirty_bytes_),
kDefaultEmptySlotSpanRingSize * bucket_size);
constexpr size_t kLargeEmptySlotSpanRingSize = SlotSpanRingMaxSize::kMedium;
constexpr int kDirtyBytesShift = 0;
root->AdjustSlotSpanRing(kLargeEmptySlotSpanRingSize, kDirtyBytesShift);
constexpr size_t single_slot_large_count = kDefaultEmptySlotSpanRingSize + 10;
for (int x = 0; x < 2; ++x) {
for (size_t i = 0; i < single_slot_large_count; i++) {
void* ptr = root->Alloc(single_slot_size);
single_slot_allocated_memory.push_back(ptr);
}
for (void* ptr : single_slot_allocated_memory) {
root->Free(ptr);
}
single_slot_allocated_memory.clear();
}
EXPECT_EQ(PA_TS_UNCHECKED_READ(root->empty_slot_spans_dirty_bytes_),
single_slot_large_count * bucket_size);
#if !PA_BUILDFLAG(USE_LARGE_EMPTY_SLOT_SPAN_RING)
constexpr size_t single_slot_too_many_count = kMaxEmptySlotSpanRingSize + 10;
for (size_t i = 0; i < single_slot_too_many_count; i++) {
void* ptr = root->Alloc(single_slot_size);
single_slot_allocated_memory.push_back(ptr);
}
for (void* ptr : single_slot_allocated_memory) {
root->Free(ptr);
}
single_slot_allocated_memory.clear();
EXPECT_EQ(PA_TS_UNCHECKED_READ(root->empty_slot_spans_dirty_bytes_),
kMaxEmptySlotSpanRingSize * bucket_size);
#endif
}
#if PA_BUILDFLAG(ENABLE_SYSTEM_FREE_FALLBACK) && \
PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
extern "C" {
void* __real_malloc(size_t);
}
TEST_P(PartitionAllocTest, HandleMixedAllocations) {
void* ptr = __real_malloc(12);
free(ptr);
}
#endif
TEST_P(PartitionAllocTest, SortFreelist) {
const size_t count = 100;
const size_t allocation_size = 1;
void* first_ptr = allocator.root()->Alloc(allocation_size);
std::vector<void*> allocations;
for (size_t i = 0; i < count; ++i) {
allocations.push_back(allocator.root()->Alloc(allocation_size));
}
std::random_device rd;
std::mt19937 generator(rd());
std::shuffle(allocations.begin(), allocations.end(), generator);
for (void* ptr : allocations) {
allocator.root()->Free(ptr);
}
allocations.clear();
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
size_t bucket_index =
SizeToIndex(allocation_size + ExtraAllocSize(allocator));
auto& bucket = PA_UNSAFE_TODO(allocator.root()->buckets_[bucket_index]);
EXPECT_TRUE(bucket.active_slot_spans_head->freelist_is_sorted());
allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
EXPECT_TRUE(bucket.active_slot_spans_head->freelist_is_sorted());
for (size_t i = 0; i < count; ++i) {
allocations.push_back(allocator.root()->Alloc(allocation_size));
EXPECT_TRUE(bucket.active_slot_spans_head->freelist_is_sorted());
}
for (size_t i = 1; i < allocations.size(); i++) {
EXPECT_LT(UntagPtr(allocations[i - 1]), UntagPtr(allocations[i]));
}
for (void* ptr : allocations) {
allocator.root()->Free(ptr);
auto* slot_span = SlotSpan::FromSlotStart(SlotStart::Unchecked(ptr).Untag(),
allocator.root());
EXPECT_FALSE(slot_span->freelist_is_sorted());
}
allocator.root()->Free(first_ptr);
}
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_BUILDFLAG(IS_LINUX) && \
PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)
TEST_P(PartitionAllocTest, CrashOnUnknownPointer) {
int not_a_heap_object = 42;
EXPECT_DEATH(allocator.root()->Free(¬_a_heap_object), "");
}
#endif
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_BUILDFLAG(IS_MAC)
class ScopedOpenCLNoOpKernel {
public:
ScopedOpenCLNoOpKernel()
: context_(nullptr),
program_(nullptr),
kernel_(nullptr),
success_(false) {}
ScopedOpenCLNoOpKernel(const ScopedOpenCLNoOpKernel&) = delete;
ScopedOpenCLNoOpKernel& operator=(const ScopedOpenCLNoOpKernel&) = delete;
~ScopedOpenCLNoOpKernel() {
if (kernel_) {
cl_int rv = clReleaseKernel(kernel_);
EXPECT_EQ(rv, CL_SUCCESS) << "clReleaseKernel";
}
if (program_) {
cl_int rv = clReleaseProgram(program_);
EXPECT_EQ(rv, CL_SUCCESS) << "clReleaseProgram";
}
if (context_) {
cl_int rv = clReleaseContext(context_);
EXPECT_EQ(rv, CL_SUCCESS) << "clReleaseContext";
}
}
void SetUp() {
cl_platform_id platform_id;
cl_int rv = clGetPlatformIDs(1, &platform_id, nullptr);
ASSERT_EQ(rv, CL_SUCCESS) << "clGetPlatformIDs";
cl_device_id device_id;
rv =
clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_CPU, 1, &device_id, nullptr);
#if PA_BUILDFLAG(PA_ARCH_CPU_ARM64)
if (rv == CL_INVALID_VALUE) {
return;
}
#endif ASSERT_EQ(rv, CL_SUCCESS) << "clGetDeviceIDs";
context_ = clCreateContext(nullptr, 1, &device_id, nullptr, nullptr, &rv);
ASSERT_EQ(rv, CL_SUCCESS) << "clCreateContext";
const char* sources[] = {
"__kernel void NoOp(void) {barrier(CLK_LOCAL_MEM_FENCE);}",
};
const size_t source_lengths[] = {
strlen(sources[0]),
};
static_assert(std::size(sources) == std::size(source_lengths),
"arrays must be parallel");
program_ = clCreateProgramWithSource(context_, std::size(sources), sources,
source_lengths, &rv);
ASSERT_EQ(rv, CL_SUCCESS) << "clCreateProgramWithSource";
rv = clBuildProgram(program_, 1, &device_id, "-cl-opt-disable", nullptr,
nullptr);
ASSERT_EQ(rv, CL_SUCCESS) << "clBuildProgram";
kernel_ = clCreateKernel(program_, "NoOp", &rv);
ASSERT_EQ(rv, CL_SUCCESS) << "clCreateKernel";
success_ = true;
}
bool success() const { return success_; }
private:
cl_context context_;
cl_program program_;
cl_kernel kernel_;
bool success_;
};
TEST_P(PartitionAllocTest, OpenCL) {
ScopedOpenCLNoOpKernel kernel;
kernel.SetUp();
#if !PA_BUILDFLAG(PA_ARCH_CPU_ARM64)
ASSERT_TRUE(kernel.success());
#endif
}
#endif
TEST_P(PartitionAllocTest, SmallSlotSpanWaste) {
for (PartitionRoot::Bucket& bucket : allocator.root()->buckets_) {
const size_t slot_size = bucket.slot_size;
size_t small_system_page_count =
partition_alloc::internal::ComputeSystemPagesPerSlotSpan(
bucket.slot_size, true);
size_t small_waste =
(small_system_page_count * SystemPageSize()) % slot_size;
EXPECT_LT(small_waste, .05 * SystemPageSize());
if (slot_size <= MaxRegularSlotSpanSize()) {
EXPECT_LE(small_system_page_count, MaxSystemPagesPerRegularSlotSpan());
}
}
}
TEST_P(PartitionAllocTest, SortActiveSlotSpans) {
auto run_test = [](size_t count) {
PartitionBucket bucket;
bucket.Init(16);
bucket.active_slot_spans_head = nullptr;
std::vector<SlotSpan> slot_spans;
slot_spans.reserve(count);
for (size_t i = 0; i < count; i++) {
slot_spans.emplace_back(&bucket);
auto& slot_span = slot_spans.back();
slot_span.num_unprovisioned_slots =
partition_alloc::internal::base::RandGenerator(
bucket.get_slots_per_span() / 2);
slot_span.num_allocated_slots =
partition_alloc::internal::base::RandGenerator(
bucket.get_slots_per_span() - slot_span.num_unprovisioned_slots);
slot_span.next_slot_span = bucket.active_slot_spans_head;
bucket.active_slot_spans_head = &slot_span;
}
bucket.SortActiveSlotSpans();
std::set<const SlotSpan*> seen_slot_spans;
std::vector<const SlotSpan*> sorted_slot_spans;
for (auto* slot_span = bucket.active_slot_spans_head; slot_span;
slot_span = slot_span->next_slot_span) {
sorted_slot_spans.push_back(slot_span);
seen_slot_spans.insert(slot_span);
}
EXPECT_EQ(seen_slot_spans.size(), sorted_slot_spans.size());
EXPECT_EQ(seen_slot_spans.size(), slot_spans.size());
size_t sorted_spans_count =
std::min(PartitionBucket::kMaxSlotSpansToSort, count);
EXPECT_TRUE(std::is_sorted(sorted_slot_spans.begin(),
sorted_slot_spans.begin() + sorted_spans_count,
partition_alloc::internal::CompareSlotSpans));
auto has_empty_freelist = [](const SlotSpan* a) {
return a->GetFreelistLength() == 0;
};
auto it = std::find_if(sorted_slot_spans.begin(),
sorted_slot_spans.begin() + sorted_spans_count,
has_empty_freelist);
if (it != sorted_slot_spans.end()) {
EXPECT_TRUE(std::all_of(it,
sorted_slot_spans.begin() + sorted_spans_count,
has_empty_freelist));
}
};
run_test(PartitionBucket::kMaxSlotSpansToSort / 2);
run_test(PartitionBucket::kMaxSlotSpansToSort * 2);
run_test(0);
run_test(1);
}
#if PA_BUILDFLAG(USE_LARGE_EMPTY_SLOT_SPAN_RING)
TEST_P(PartitionAllocTest, GlobalEmptySlotSpanRingIndexResets) {
constexpr int16_t kLargeRingSize = SlotSpanRingMaxSize::kLarge;
constexpr int kLargeDirtyBytesShift = 2;
allocator.root()->AdjustSlotSpanRing(kLargeRingSize, kLargeDirtyBytesShift);
allocator.root()->SetGlobalEmptySlotSpanRingIndexForTesting(
internal::kMaxEmptySlotSpanRingSize - 1);
constexpr int16_t kSmallRingSize = SlotSpanRingMaxSize::kMedium;
constexpr int kSmallirtyBytesShift = 3;
allocator.root()->AdjustSlotSpanRing(kSmallRingSize, kSmallirtyBytesShift);
void* ptr = allocator.root()->Alloc(kTestAllocSize, type_name);
allocator.root()->Free(ptr);
ClearEmptySlotSpanCache();
EXPECT_EQ(0u, PA_TS_UNCHECKED_READ(
allocator.root()->empty_slot_spans_dirty_bytes_));
}
#endif
TEST_P(PartitionAllocTest, FastReclaim) {
static base::TimeTicks now = base::TimeTicks();
allocator.root()->now_maybe_overridden_for_testing_ = [] {
now += PartitionRoot::kMaxPurgeDuration / 10;
return now;
};
PurgeState purge_state;
constexpr int kFlags = PurgeFlags::kDecommitEmptySlotSpans |
PurgeFlags::kDiscardUnusedSystemPages;
allocator.root()->PurgeMemory(kFlags, purge_state);
ASSERT_GT(now, base::TimeTicks());
EXPECT_EQ(purge_state.next_bucket_index, 0u);
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
unsigned int next_bucket = purge_state.next_bucket_index;
EXPECT_NE(next_bucket, 0u);
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
EXPECT_GT(purge_state.next_bucket_index, next_bucket);
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
EXPECT_NE(purge_state.next_bucket_index, 0u);
while (purge_state.next_bucket_index != 0) {
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
}
allocator.root()->now_maybe_overridden_for_testing_ = base::TimeTicks::Now;
}
TEST_P(PartitionAllocTest, FastReclaimEventuallyLooksAtAllBuckets) {
static base::TimeTicks now = base::TimeTicks();
allocator.root()->now_maybe_overridden_for_testing_ = [] {
now += PartitionRoot::kMaxPurgeDuration / 10;
return now;
};
PurgeState purge_state;
constexpr int kFlags = PurgeFlags::kDecommitEmptySlotSpans |
PurgeFlags::kDiscardUnusedSystemPages;
allocator.root()->PurgeMemory(kFlags, purge_state);
ASSERT_GT(now, base::TimeTicks());
EXPECT_EQ(purge_state.next_bucket_index, 0u);
EXPECT_EQ(purge_state.generation, 1u);
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
unsigned int next_bucket = purge_state.next_bucket_index;
EXPECT_NE(next_bucket, 0u);
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
EXPECT_GT(purge_state.next_bucket_index, next_bucket);
EXPECT_EQ(purge_state.generation, 1u);
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
EXPECT_NE(purge_state.next_bucket_index, 0u);
while (purge_state.generation != 0) {
allocator.root()->PurgeMemory(kFlags | PurgeFlags::kLimitDuration,
purge_state);
}
EXPECT_EQ(purge_state.next_bucket_index, 0u);
allocator.root()->now_maybe_overridden_for_testing_ = base::TimeTicks::Now;
}
TEST_P(PartitionAllocTest, SwitchBucketDistributionBeforeAlloc) {
PartitionRoot* root = allocator.root();
root->SwitchToDenserBucketDistribution();
constexpr size_t n = (1 << 12) * 15 / 8;
EXPECT_NE(BucketIndexLookup::GetIndexForDenserBuckets(n),
BucketIndexLookup::GetIndexForNeutralBuckets(n));
void* ptr = root->Alloc(n);
root->ResetBucketDistributionForTesting();
root->Free(ptr);
}
TEST_P(PartitionAllocTest, SwitchBucketDistributionAfterAlloc) {
constexpr size_t n = (1 << 12) * 15 / 8;
EXPECT_NE(BucketIndexLookup::GetIndexForDenserBuckets(n),
BucketIndexLookup::GetIndexForNeutralBuckets(n));
PartitionRoot* root = allocator.root();
void* ptr = root->Alloc(n);
root->SwitchToDenserBucketDistribution();
void* ptr2 = root->Alloc(n);
root->Free(ptr2);
root->Free(ptr);
}
TEST_P(PartitionAllocTest, MultipleThreadCachePerThread) {
constexpr size_t index1 = kNumPartitions;
constexpr size_t index2 = kNumPartitions + 1;
static_assert(index1 < internal::kMaxThreadCacheIndex);
static_assert(index2 < internal::kMaxThreadCacheIndex);
ASSERT_FALSE(ThreadCache::IsValid(ThreadCache::Get(index1)));
ASSERT_FALSE(ThreadCache::IsValid(ThreadCache::Get(index2)));
auto root1 = CreateCustomTestRootWithThreadCache(GetCommonPartitionOptions(),
{}, index1);
auto root2 = CreateCustomTestRootWithThreadCache(GetCommonPartitionOptions(),
{}, index2);
auto* ptr1 = root1->Alloc(kTestAllocSize);
auto* ptr2 = root2->Alloc(kTestAllocSize);
root1->Free(ptr1);
root2->Free(ptr2);
ThreadCache* tcache1 = ThreadCache::Get(index1);
ThreadCache* tcache2 = ThreadCache::Get(index2);
EXPECT_NE(tcache1, tcache2);
size_t bucket_index =
SizeToIndex(kTestAllocSize + kExtraAllocSizeWithoutMetadata);
size_t pos1, pos2;
EXPECT_TRUE(tcache1->IsInFreelist(
internal::SlotStart::Unchecked(ptr1).Untag(), bucket_index, pos1));
EXPECT_EQ(pos1, 0u);
EXPECT_TRUE(tcache2->IsInFreelist(
internal::SlotStart::Unchecked(ptr2).Untag(), bucket_index, pos2));
EXPECT_EQ(pos2, 0u);
}
}
#endif