blob: ea081d535c894d475cbbb8159ac07f90fc12e051 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "partition_alloc/starscan/state_bitmap.h"
#include <cstdint>
#include "partition_alloc/page_allocator.h"
#include "partition_alloc/partition_alloc_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace partition_alloc::internal {
namespace {
using TestBitmap = StateBitmap<kSuperPageSize, kSuperPageSize, kAlignment>;
class PageWithBitmap final {
public:
PageWithBitmap()
: base_(AllocPages(kSuperPageSize,
kSuperPageAlignment,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kReadWrite),
PageTag::kPartitionAlloc)),
bitmap_(new(reinterpret_cast<void*>(base_)) TestBitmap) {}
PageWithBitmap(const PageWithBitmap&) = delete;
PageWithBitmap& operator=(const PageWithBitmap&) = delete;
~PageWithBitmap() { FreePages(base_, kSuperPageSize); }
TestBitmap& bitmap() const { return *bitmap_; }
void* base() const { return reinterpret_cast<void*>(base_); }
size_t size() const { return kSuperPageSize; }
uintptr_t base_;
TestBitmap* bitmap_;
};
class PartitionAllocStateBitmapTest : public ::testing::Test {
protected:
TestBitmap& bitmap() const { return page.bitmap(); }
void AllocateObject(size_t object_position) {
page.bitmap().Allocate(ObjectAddress(object_position));
}
void FreeObject(size_t object_position) {
page.bitmap().Free(ObjectAddress(object_position));
}
bool QuarantineObject(size_t object_position, size_t epoch) {
return page.bitmap().Quarantine(ObjectAddress(object_position), epoch);
}
bool MarkQuarantinedObject(size_t object_position, size_t epoch) {
return page.bitmap().MarkQuarantinedAsReachable(
ObjectAddress(object_position), epoch);
}
bool IsAllocated(size_t object_position) const {
return page.bitmap().IsAllocated(ObjectAddress(object_position));
}
bool IsQuarantined(size_t object_position) const {
return page.bitmap().IsQuarantined(ObjectAddress(object_position));
}
bool IsFreed(size_t object_position) const {
return page.bitmap().IsFreed(ObjectAddress(object_position));
}
void AssertAllocated(size_t object_position) const {
EXPECT_TRUE(IsAllocated(object_position));
EXPECT_FALSE(IsQuarantined(object_position));
EXPECT_FALSE(IsFreed(object_position));
}
void AssertFreed(size_t object_position) const {
EXPECT_FALSE(IsAllocated(object_position));
EXPECT_FALSE(IsQuarantined(object_position));
EXPECT_TRUE(IsFreed(object_position));
}
void AssertQuarantined(size_t object_position) const {
EXPECT_FALSE(IsAllocated(object_position));
EXPECT_TRUE(IsQuarantined(object_position));
EXPECT_FALSE(IsFreed(object_position));
}
size_t CountAllocated() const {
size_t count = 0;
bitmap().IterateAllocated([&count](uintptr_t) { count++; });
return count;
}
size_t CountQuarantined() const {
size_t count = 0;
bitmap().IterateQuarantined([&count](uintptr_t) { count++; });
return count;
}
bool IsQuarantineEmpty() const { return !CountQuarantined(); }
uintptr_t ObjectAddress(size_t pos) const {
return reinterpret_cast<uintptr_t>(page.base()) + sizeof(TestBitmap) +
pos * kAlignment;
}
static constexpr uintptr_t LastIndex() {
return TestBitmap::kMaxEntries - (sizeof(TestBitmap) / kAlignment) - 1;
}
static constexpr uintptr_t MiddleIndex() { return LastIndex() / 2; }
private:
PageWithBitmap page;
};
constexpr size_t kTestEpoch = 0;
} // namespace
TEST_F(PartitionAllocStateBitmapTest, MoreThanZeroEntriesPossible) {
const size_t max_entries = TestBitmap::kMaxEntries;
EXPECT_LT(0u, max_entries);
}
TEST_F(PartitionAllocStateBitmapTest, InitialQuarantineEmpty) {
EXPECT_TRUE(IsQuarantineEmpty());
}
TEST_F(PartitionAllocStateBitmapTest, QuarantineImpliesNonEmpty) {
AllocateObject(0);
EXPECT_TRUE(IsQuarantineEmpty());
QuarantineObject(0, kTestEpoch);
EXPECT_FALSE(IsQuarantineEmpty());
}
TEST_F(PartitionAllocStateBitmapTest, RepetitiveQuarantine) {
AllocateObject(MiddleIndex());
EXPECT_TRUE(QuarantineObject(MiddleIndex(), kTestEpoch));
EXPECT_FALSE(QuarantineObject(MiddleIndex(), kTestEpoch));
}
TEST_F(PartitionAllocStateBitmapTest, CountAllocated) {
AllocateObject(0);
EXPECT_EQ(1u, CountAllocated());
QuarantineObject(0, kTestEpoch);
EXPECT_EQ(0u, CountAllocated());
}
TEST_F(PartitionAllocStateBitmapTest, StateTransititions) {
for (auto i : {uintptr_t{0}, uintptr_t{1}, LastIndex() - 1, LastIndex()}) {
AssertFreed(i);
AllocateObject(i);
AssertAllocated(i);
QuarantineObject(i, kTestEpoch);
AssertQuarantined(i);
MarkQuarantinedObject(i, kTestEpoch);
AssertQuarantined(i);
FreeObject(i);
AssertFreed(i);
}
}
TEST_F(PartitionAllocStateBitmapTest, MultipleMarks) {
AllocateObject(0);
QuarantineObject(0, kTestEpoch);
EXPECT_TRUE(MarkQuarantinedObject(0, kTestEpoch));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch));
EXPECT_TRUE(MarkQuarantinedObject(0, kTestEpoch + 1));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch + 1));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch + 1));
EXPECT_TRUE(MarkQuarantinedObject(0, kTestEpoch + 2));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch + 2));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch + 2));
}
TEST_F(PartitionAllocStateBitmapTest, MultipleMarksAdjacent) {
AllocateObject(0);
QuarantineObject(0, kTestEpoch);
AllocateObject(1);
QuarantineObject(1, kTestEpoch);
AllocateObject(2);
QuarantineObject(2, kTestEpoch);
EXPECT_TRUE(MarkQuarantinedObject(0, kTestEpoch));
EXPECT_TRUE(MarkQuarantinedObject(1, kTestEpoch));
EXPECT_TRUE(MarkQuarantinedObject(2, kTestEpoch));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch));
EXPECT_FALSE(MarkQuarantinedObject(1, kTestEpoch));
EXPECT_FALSE(MarkQuarantinedObject(2, kTestEpoch));
EXPECT_TRUE(MarkQuarantinedObject(0, kTestEpoch + 1));
EXPECT_TRUE(MarkQuarantinedObject(1, kTestEpoch + 1));
EXPECT_TRUE(MarkQuarantinedObject(2, kTestEpoch + 1));
EXPECT_FALSE(MarkQuarantinedObject(0, kTestEpoch + 1));
EXPECT_FALSE(MarkQuarantinedObject(1, kTestEpoch + 1));
EXPECT_FALSE(MarkQuarantinedObject(2, kTestEpoch + 1));
}
TEST_F(PartitionAllocStateBitmapTest, QuarantineFreeMultipleObjects) {
static constexpr size_t kCount = 256;
for (size_t i = 0; i < kCount; ++i) {
AllocateObject(i);
}
EXPECT_EQ(kCount, CountAllocated());
EXPECT_EQ(0u, CountQuarantined());
for (size_t i = 0; i < kCount; ++i) {
QuarantineObject(i, kTestEpoch);
}
EXPECT_EQ(0u, CountAllocated());
EXPECT_EQ(kCount, CountQuarantined());
for (size_t i = 0; i < kCount; ++i) {
FreeObject(i);
EXPECT_EQ(kCount - i - 1, CountQuarantined());
}
EXPECT_TRUE(IsQuarantineEmpty());
}
TEST_F(PartitionAllocStateBitmapTest, AdjacentQuarantinedObjectsAtBegin) {
AllocateObject(0);
QuarantineObject(0, kTestEpoch);
AllocateObject(1);
QuarantineObject(1, kTestEpoch);
EXPECT_FALSE(IsQuarantined(2));
{
size_t count = 0;
this->bitmap().IterateQuarantined([&count, this](uintptr_t current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(0), current);
} else if (count == 1) {
EXPECT_EQ(ObjectAddress(1), current);
}
count++;
});
EXPECT_EQ(2u, count);
}
// Now mark only the first object.
{
MarkQuarantinedObject(0, kTestEpoch);
size_t count = 0;
this->bitmap().IterateUnmarkedQuarantined(
kTestEpoch, [&count, this](uintptr_t current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(1), current);
}
count++;
});
EXPECT_EQ(1u, count);
}
}
TEST_F(PartitionAllocStateBitmapTest, AdjacentQuarantinedObjectsAtMiddle) {
AllocateObject(MiddleIndex());
QuarantineObject(MiddleIndex(), kTestEpoch);
AllocateObject(MiddleIndex() + 1);
QuarantineObject(MiddleIndex() + 1, kTestEpoch);
{
size_t count = 0;
this->bitmap().IterateQuarantined([&count, this](uintptr_t current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(MiddleIndex()), current);
} else if (count == 1) {
EXPECT_EQ(ObjectAddress(MiddleIndex() + 1), current);
}
count++;
});
EXPECT_EQ(2u, count);
}
// Now mark only the first object.
{
MarkQuarantinedObject(MiddleIndex(), kTestEpoch);
size_t count = 0;
this->bitmap().IterateUnmarkedQuarantined(
kTestEpoch, [&count, this](uintptr_t current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(MiddleIndex() + 1), current);
}
count++;
});
EXPECT_EQ(1u, count);
}
}
TEST_F(PartitionAllocStateBitmapTest, AdjacentQuarantinedObjectsAtEnd) {
AllocateObject(LastIndex());
QuarantineObject(LastIndex(), kTestEpoch);
AllocateObject(LastIndex() - 1);
QuarantineObject(LastIndex() - 1, kTestEpoch);
EXPECT_FALSE(IsQuarantined(LastIndex() - 2));
{
size_t count = 0;
this->bitmap().IterateQuarantined([&count, this](uintptr_t current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(LastIndex() - 1), current);
} else if (count == 1) {
EXPECT_EQ(ObjectAddress(LastIndex()), current);
}
count++;
});
EXPECT_EQ(2u, count);
}
// Now mark only the first object.
{
MarkQuarantinedObject(LastIndex(), kTestEpoch);
size_t count = 0;
this->bitmap().IterateUnmarkedQuarantined(
kTestEpoch, [&count, this](uintptr_t current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(LastIndex() - 1), current);
}
count++;
});
EXPECT_EQ(1u, count);
}
}
} // namespace partition_alloc::internal