v8/test/unittests/heap/cppgc/persistent-family-unittest.cc
Omer Katz fe5f67e9b5 cppgc: Add checks and locks to (Weak)CrossThreadPersistents
This CL adds missing locks to the PersistentRegions for
(Weak)CrossThreadPersistents.
To make sure no locks are missed in the future, this CL also splits
PersistentRegion and introduces CrossThreadPersistentRegion that checks
whether a lock is taken whenever it is accessed.

Bug: chromium:1056170
Change-Id: Iaaef4a28af0f02bcb896706e9abf1ee5ad2ee1e1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2737299
Commit-Queue: Omer Katz <omerkatz@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73264}
2021-03-08 14:01:13 +00:00

933 lines
33 KiB
C++

// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "include/cppgc/allocation.h"
#include "include/cppgc/cross-thread-persistent.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/internal/pointer-policies.h"
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/source-location.h"
#include "include/cppgc/type-traits.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/liveness-broker.h"
#include "src/heap/cppgc/visitor.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
struct GCed : GarbageCollected<GCed> {
static size_t trace_call_count;
virtual void Trace(cppgc::Visitor*) const { ++trace_call_count; }
};
size_t GCed::trace_call_count = 0;
struct DerivedGCed : GCed {
void Trace(cppgc::Visitor* v) const override { GCed::Trace(v); }
};
template <template <typename> class PersistentType>
struct PersistentRegionTrait;
template <>
struct PersistentRegionTrait<Persistent> {
static PersistentRegion& Get(cppgc::Heap* heap) {
return internal::Heap::From(heap)->GetStrongPersistentRegion();
}
};
template <>
struct PersistentRegionTrait<WeakPersistent> {
static PersistentRegion& Get(cppgc::Heap* heap) {
return internal::Heap::From(heap)->GetWeakPersistentRegion();
}
};
template <>
struct PersistentRegionTrait<subtle::CrossThreadPersistent> {
static CrossThreadPersistentRegion& Get(cppgc::Heap* heap) {
return internal::Heap::From(heap)->GetStrongCrossThreadPersistentRegion();
}
};
template <>
struct PersistentRegionTrait<subtle::WeakCrossThreadPersistent> {
static CrossThreadPersistentRegion& Get(cppgc::Heap* heap) {
return internal::Heap::From(heap)->GetWeakCrossThreadPersistentRegion();
}
};
template <template <typename> class PersistentType>
auto& GetRegion(cppgc::Heap* heap) {
return PersistentRegionTrait<PersistentType>::Get(heap);
}
template <typename T>
using LocalizedPersistent =
internal::BasicPersistent<T, internal::StrongPersistentPolicy,
internal::KeepLocationPolicy,
internal::DefaultCheckingPolicy>;
template <typename T>
using LocalizedCrossThreadPersistent = internal::BasicCrossThreadPersistent<
T, internal::StrongCrossThreadPersistentPolicy,
internal::KeepLocationPolicy, internal::DefaultCheckingPolicy>;
class RootVisitor final : public VisitorBase {
public:
RootVisitor() = default;
const auto& WeakCallbacks() const { return weak_callbacks_; }
void ProcessWeakCallbacks() {
const auto info = LivenessBrokerFactory::Create();
for (const auto& cb : weak_callbacks_) {
cb.first(info, cb.second);
}
weak_callbacks_.clear();
}
protected:
void VisitRoot(const void* t, TraceDescriptor desc,
const SourceLocation&) final {
desc.callback(this, desc.base_object_payload);
}
void VisitWeakRoot(const void*, TraceDescriptor, WeakCallback callback,
const void* object, const SourceLocation&) final {
weak_callbacks_.emplace_back(callback, object);
}
private:
std::vector<std::pair<WeakCallback, const void*>> weak_callbacks_;
};
class PersistentTest : public testing::TestSupportingAllocationOnly {};
} // namespace
template <template <typename> class PersistentType>
void NullStateCtor(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> empty;
EXPECT_EQ(nullptr, empty.Get());
EXPECT_EQ(nullptr, empty.Release());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
{
PersistentType<GCed> empty = nullptr;
EXPECT_EQ(nullptr, empty.Get());
EXPECT_EQ(nullptr, empty.Release());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
{
PersistentType<GCed> empty = kSentinelPointer;
EXPECT_EQ(kSentinelPointer, empty);
EXPECT_EQ(kSentinelPointer, empty.Release());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
{
// Runtime null must not allocated associated node.
PersistentType<GCed> empty = static_cast<GCed*>(nullptr);
EXPECT_EQ(nullptr, empty.Get());
EXPECT_EQ(nullptr, empty.Release());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, NullStateCtor) {
auto* heap = GetHeap();
NullStateCtor<Persistent>(heap);
NullStateCtor<WeakPersistent>(heap);
NullStateCtor<subtle::CrossThreadPersistent>(heap);
NullStateCtor<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void RawCtor(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
{
PersistentType<GCed> p = gced;
EXPECT_EQ(gced, p.Get());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p = *gced;
EXPECT_EQ(gced, p.Get());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<const GCed> p = gced;
EXPECT_EQ(gced, p.Get());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, RawCtor) {
auto* heap = GetHeap();
RawCtor<Persistent>(heap);
RawCtor<WeakPersistent>(heap);
RawCtor<subtle::CrossThreadPersistent>(heap);
RawCtor<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void CopyCtor(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
PersistentType<GCed> p2 = p1;
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p1;
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
PersistentType<GCed> p2 = p1;
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(nullptr, p1.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<DerivedGCed> p1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
PersistentType<GCed> p2 = p1;
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
static constexpr size_t kSlots = 512u;
const PersistentType<GCed> prototype =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
std::vector<PersistentType<GCed>> vector;
vector.reserve(kSlots);
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
for (size_t i = 0; i < kSlots; ++i) {
vector.emplace_back(prototype);
EXPECT_EQ(i + 2, GetRegion<PersistentType>(heap).NodesInUse());
}
vector.clear();
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, CopyCtor) {
auto* heap = GetHeap();
CopyCtor<Persistent>(heap);
CopyCtor<WeakPersistent>(heap);
CopyCtor<subtle::CrossThreadPersistent>(heap);
CopyCtor<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void MoveCtor(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p1 = gced;
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
PersistentType<GCed> p2 = std::move(p1);
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(gced, p2.Get());
// Moved-from-object is in the valid specified (nullptr) state.
EXPECT_EQ(nullptr, p1.Get());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<DerivedGCed> p1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
// Move ctor is not heterogeneous - fall back to copy ctor.
PersistentType<GCed> p2 = std::move(p1);
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p1;
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
PersistentType<GCed> p2 = std::move(p1);
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(nullptr, p1.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, MoveCtor) {
auto* heap = GetHeap();
MoveCtor<Persistent>(heap);
MoveCtor<WeakPersistent>(heap);
MoveCtor<subtle::CrossThreadPersistent>(heap);
MoveCtor<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType,
template <typename> class MemberType>
void MemberCtor(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
MemberType<GCed> m =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p = m;
EXPECT_EQ(m.Get(), p.Get());
EXPECT_EQ(m, p);
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, MemberCtor) {
auto* heap = GetHeap();
MemberCtor<Persistent, Member>(heap);
MemberCtor<Persistent, WeakMember>(heap);
MemberCtor<Persistent, UntracedMember>(heap);
MemberCtor<WeakPersistent, Member>(heap);
MemberCtor<WeakPersistent, WeakMember>(heap);
MemberCtor<WeakPersistent, UntracedMember>(heap);
MemberCtor<subtle::CrossThreadPersistent, Member>(heap);
MemberCtor<subtle::CrossThreadPersistent, WeakMember>(heap);
MemberCtor<subtle::CrossThreadPersistent, UntracedMember>(heap);
MemberCtor<subtle::WeakCrossThreadPersistent, Member>(heap);
MemberCtor<subtle::WeakCrossThreadPersistent, WeakMember>(heap);
MemberCtor<subtle::WeakCrossThreadPersistent, UntracedMember>(heap);
}
template <template <typename> class PersistentType>
void NullStateAssignment(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p = nullptr;
EXPECT_EQ(nullptr, p.Get());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
{
PersistentType<GCed> p =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p = kSentinelPointer;
EXPECT_EQ(kSentinelPointer, p);
EXPECT_EQ(kSentinelPointer, p.Get());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
{
PersistentType<GCed> p =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p = static_cast<GCed*>(0);
EXPECT_EQ(nullptr, p.Get());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
}
TEST_F(PersistentTest, NullStateAssignemnt) {
auto* heap = GetHeap();
NullStateAssignment<Persistent>(heap);
NullStateAssignment<WeakPersistent>(heap);
NullStateAssignment<subtle::CrossThreadPersistent>(heap);
NullStateAssignment<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void RawAssignment(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
{
PersistentType<GCed> p;
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
p = gced;
EXPECT_EQ(gced, p.Get());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p;
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
p = *gced;
EXPECT_EQ(gced, p.Get());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, RawAssignment) {
auto* heap = GetHeap();
RawAssignment<Persistent>(heap);
RawAssignment<WeakPersistent>(heap);
RawAssignment<subtle::CrossThreadPersistent>(heap);
RawAssignment<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void CopyAssignment(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p2;
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p2 = p1;
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p2 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
p2 = p1;
// The old node from p2 must be dropped.
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<DerivedGCed> p1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
PersistentType<GCed> p2;
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p2 = p1;
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
static constexpr size_t kSlots = 512u;
const PersistentType<GCed> prototype =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
std::vector<PersistentType<GCed>> vector(kSlots);
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
size_t i = 0;
for (auto& p : vector) {
p = prototype;
EXPECT_EQ(i + 2, GetRegion<PersistentType>(heap).NodesInUse());
++i;
}
vector.clear();
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, CopyAssignment) {
auto* heap = GetHeap();
CopyAssignment<Persistent>(heap);
CopyAssignment<WeakPersistent>(heap);
CopyAssignment<subtle::CrossThreadPersistent>(heap);
CopyAssignment<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void MoveAssignment(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p1 = gced;
PersistentType<GCed> p2;
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p2 = std::move(p1);
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(gced, p2.Get());
// Moved-from-object is in the valid specified (nullptr) state.
EXPECT_EQ(nullptr, p1.Get());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<GCed> p1;
PersistentType<GCed> p2 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
p2 = std::move(p1);
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
// Moved-from-object is in the valid specified (nullptr) state.
EXPECT_EQ(nullptr, p2.Get());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p1 = gced;
PersistentType<GCed> p2 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
p2 = std::move(p1);
// The old node from p2 must be dropped.
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(gced, p2.Get());
// Moved-from-object is in the valid specified (nullptr) state.
EXPECT_EQ(nullptr, p1.Get());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
PersistentType<DerivedGCed> p1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
PersistentType<GCed> p2;
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
// Move ctor is not heterogeneous - fall back to copy assignment.
p2 = std::move(p1);
EXPECT_EQ(2u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_EQ(p1.Get(), p2.Get());
EXPECT_EQ(p1, p2);
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, MoveAssignment) {
auto* heap = GetHeap();
MoveAssignment<Persistent>(heap);
MoveAssignment<WeakPersistent>(heap);
MoveAssignment<subtle::CrossThreadPersistent>(heap);
MoveAssignment<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType,
template <typename> class MemberType>
void MemberAssignment(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
{
MemberType<GCed> m =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p;
p = m;
EXPECT_EQ(m.Get(), p.Get());
EXPECT_EQ(m, p);
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, MemberAssignment) {
auto* heap = GetHeap();
MemberAssignment<Persistent, Member>(heap);
MemberAssignment<Persistent, WeakMember>(heap);
MemberAssignment<Persistent, UntracedMember>(heap);
MemberAssignment<WeakPersistent, Member>(heap);
MemberAssignment<WeakPersistent, WeakMember>(heap);
MemberAssignment<WeakPersistent, UntracedMember>(heap);
MemberAssignment<subtle::CrossThreadPersistent, Member>(heap);
MemberAssignment<subtle::CrossThreadPersistent, WeakMember>(heap);
MemberAssignment<subtle::CrossThreadPersistent, UntracedMember>(heap);
MemberAssignment<subtle::WeakCrossThreadPersistent, Member>(heap);
MemberAssignment<subtle::WeakCrossThreadPersistent, WeakMember>(heap);
MemberAssignment<subtle::WeakCrossThreadPersistent, UntracedMember>(heap);
}
template <template <typename> class PersistentType>
void ClearTest(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
PersistentType<GCed> p =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_NE(nullptr, p.Get());
p.Clear();
EXPECT_EQ(nullptr, p.Get());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, Clear) {
auto* heap = GetHeap();
ClearTest<Persistent>(heap);
ClearTest<WeakPersistent>(heap);
ClearTest<subtle::CrossThreadPersistent>(heap);
ClearTest<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType>
void ReleaseTest(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType<GCed> p = gced;
EXPECT_EQ(1u, GetRegion<PersistentType>(heap).NodesInUse());
EXPECT_NE(nullptr, p.Get());
GCed* raw = p.Release();
EXPECT_EQ(gced, raw);
EXPECT_EQ(nullptr, p.Get());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
TEST_F(PersistentTest, Release) {
auto* heap = GetHeap();
ReleaseTest<Persistent>(heap);
ReleaseTest<WeakPersistent>(heap);
ReleaseTest<subtle::CrossThreadPersistent>(heap);
ReleaseTest<subtle::WeakCrossThreadPersistent>(heap);
}
template <template <typename> class PersistentType1,
template <typename> class PersistentType2>
void HeterogeneousConversion(cppgc::Heap* heap) {
EXPECT_EQ(0u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(0u, GetRegion<PersistentType2>(heap).NodesInUse());
{
PersistentType1<GCed> persistent1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType2<GCed> persistent2 = persistent1;
EXPECT_EQ(persistent1.Get(), persistent2.Get());
EXPECT_EQ(1u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(1u, GetRegion<PersistentType2>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(0u, GetRegion<PersistentType2>(heap).NodesInUse());
{
PersistentType1<DerivedGCed> persistent1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
PersistentType2<GCed> persistent2 = persistent1;
EXPECT_EQ(persistent1.Get(), persistent2.Get());
EXPECT_EQ(1u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(1u, GetRegion<PersistentType2>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(0u, GetRegion<PersistentType2>(heap).NodesInUse());
{
PersistentType1<GCed> persistent1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType2<GCed> persistent2;
persistent2 = persistent1;
EXPECT_EQ(persistent1.Get(), persistent2.Get());
EXPECT_EQ(1u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(1u, GetRegion<PersistentType2>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(0u, GetRegion<PersistentType2>(heap).NodesInUse());
{
PersistentType1<DerivedGCed> persistent1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
PersistentType2<GCed> persistent2;
persistent2 = persistent1;
EXPECT_EQ(persistent1.Get(), persistent2.Get());
EXPECT_EQ(1u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(1u, GetRegion<PersistentType2>(heap).NodesInUse());
}
EXPECT_EQ(0u, GetRegion<PersistentType1>(heap).NodesInUse());
EXPECT_EQ(0u, GetRegion<PersistentType2>(heap).NodesInUse());
}
TEST_F(PersistentTest, HeterogeneousConversion) {
auto* heap = GetHeap();
HeterogeneousConversion<Persistent, WeakPersistent>(heap);
HeterogeneousConversion<WeakPersistent, Persistent>(heap);
}
namespace {
class Parent : public GarbageCollected<Parent> {
public:
virtual void Trace(Visitor*) const {}
void ParentFoo() { /* Dummy method to trigger vtable check on UBSan. */
}
};
class Child : public Parent {
public:
void ChildFoo() { /* Dummy method to trigger vtable check on UBSan. */
}
};
template <template <typename> class PersistentType>
void ImplicitUpcast(cppgc::Heap* heap) {
PersistentType<Child> child;
PersistentType<Parent> parent = child;
}
template <template <typename> class PersistentType>
void ExplicitDowncast(cppgc::Heap* heap) {
PersistentType<Parent> parent{
MakeGarbageCollected<Child>(heap->GetAllocationHandle())};
PersistentType<Child> child = parent.template To<Child>();
child->ChildFoo();
}
} // namespace
TEST_F(PersistentTest, ImplicitUpcast) {
auto* heap = GetHeap();
ImplicitUpcast<Persistent>(heap);
ImplicitUpcast<WeakPersistent>(heap);
ImplicitUpcast<subtle::CrossThreadPersistent>(heap);
ImplicitUpcast<subtle::WeakCrossThreadPersistent>(heap);
}
TEST_F(PersistentTest, ExplicitDowncast) {
auto* heap = GetHeap();
ExplicitDowncast<Persistent>(heap);
ExplicitDowncast<WeakPersistent>(heap);
ExplicitDowncast<subtle::CrossThreadPersistent>(heap);
ExplicitDowncast<subtle::WeakCrossThreadPersistent>(heap);
}
TEST_F(PersistentTest, TraceStrong) {
auto* heap = GetHeap();
static constexpr size_t kItems = 512;
std::vector<Persistent<GCed>> vec(kItems);
for (auto& p : vec) {
p = MakeGarbageCollected<GCed>(GetAllocationHandle());
}
{
GCed::trace_call_count = 0;
RootVisitor v;
GetRegion<Persistent>(heap).Trace(&v);
EXPECT_EQ(kItems, GCed::trace_call_count);
EXPECT_EQ(kItems, GetRegion<Persistent>(heap).NodesInUse());
}
{
GCed::trace_call_count = 0;
vec[0].Clear();
vec[kItems / 2].Clear();
vec[kItems / 4].Clear();
vec[kItems - 1].Clear();
RootVisitor v;
GetRegion<Persistent>(heap).Trace(&v);
EXPECT_EQ(kItems - 4, GCed::trace_call_count);
EXPECT_EQ(kItems - 4, GetRegion<Persistent>(heap).NodesInUse());
}
{
GCed::trace_call_count = 0;
vec.clear();
RootVisitor v;
GetRegion<Persistent>(heap).Trace(&v);
EXPECT_EQ(0u, GCed::trace_call_count);
EXPECT_EQ(0u, GetRegion<Persistent>(heap).NodesInUse());
}
}
TEST_F(PersistentTest, TraceWeak) {
auto* heap = GetHeap();
static constexpr size_t kItems = 512;
std::vector<WeakPersistent<GCed>> vec(kItems);
for (auto& p : vec) {
p = MakeGarbageCollected<GCed>(GetAllocationHandle());
}
GCed::trace_call_count = 0;
RootVisitor v;
GetRegion<WeakPersistent>(heap).Trace(&v);
const auto& callbacks = v.WeakCallbacks();
EXPECT_EQ(kItems, callbacks.size());
EXPECT_EQ(kItems, GetRegion<WeakPersistent>(heap).NodesInUse());
v.ProcessWeakCallbacks();
for (const auto& p : vec) {
EXPECT_EQ(nullptr, p.Get());
}
EXPECT_EQ(0u, GetRegion<WeakPersistent>(heap).NodesInUse());
}
TEST_F(PersistentTest, ClearOnHeapDestruction) {
Persistent<GCed> persistent;
WeakPersistent<GCed> weak_persistent;
// Create another heap that can be destroyed during the test.
auto heap = Heap::Create(GetPlatformHandle());
persistent = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
weak_persistent = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
const Persistent<GCed> persistent_sentinel(kSentinelPointer);
const WeakPersistent<GCed> weak_persistent_sentinel(kSentinelPointer);
const subtle::CrossThreadPersistent<GCed> cross_thread_persistent_sentinel(
kSentinelPointer);
const subtle::WeakCrossThreadPersistent<GCed>
cross_thread_weak_persistent_sentinel(kSentinelPointer);
heap.reset();
EXPECT_EQ(nullptr, persistent);
EXPECT_EQ(nullptr, weak_persistent);
// Sentinel values survive as they do not represent actual heap objects.
EXPECT_EQ(kSentinelPointer, persistent_sentinel);
EXPECT_EQ(kSentinelPointer, weak_persistent_sentinel);
}
#if CPPGC_SUPPORTS_SOURCE_LOCATION
TEST_F(PersistentTest, LocalizedPersistent) {
GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
{
const auto expected_loc = SourceLocation::Current();
LocalizedPersistent<GCed> p = gced;
const auto actual_loc = p.Location();
EXPECT_STREQ(expected_loc.Function(), actual_loc.Function());
EXPECT_STREQ(expected_loc.FileName(), actual_loc.FileName());
EXPECT_EQ(expected_loc.Line() + 1, actual_loc.Line());
}
{
const auto expected_loc = SourceLocation::Current();
LocalizedCrossThreadPersistent<GCed> p = gced;
const auto actual_loc = p.Location();
EXPECT_STREQ(expected_loc.Function(), actual_loc.Function());
EXPECT_STREQ(expected_loc.FileName(), actual_loc.FileName());
EXPECT_EQ(expected_loc.Line() + 1, actual_loc.Line());
}
{
// Copy ctor doesn't copy source location.
LocalizedPersistent<GCed> p1 = gced;
LocalizedPersistent<GCed> p2 = p1;
EXPECT_STREQ(p1.Location().Function(), p2.Location().Function());
EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName());
EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line());
}
{
// Copy ctor doesn't copy source location.
LocalizedCrossThreadPersistent<GCed> p1 = gced;
LocalizedCrossThreadPersistent<GCed> p2 = p1;
EXPECT_STREQ(p1.Location().Function(), p2.Location().Function());
EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName());
EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line());
}
{
// Copy assignment doesn't copy source location.
LocalizedPersistent<GCed> p1 = gced;
LocalizedPersistent<GCed> p2;
p2 = p1;
EXPECT_STREQ(p1.Location().Function(), p2.Location().Function());
EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName());
EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line());
}
{
// Copy assignment doesn't copy source location.
LocalizedCrossThreadPersistent<GCed> p1 = gced;
LocalizedCrossThreadPersistent<GCed> p2;
p2 = p1;
EXPECT_STREQ(p1.Location().Function(), p2.Location().Function());
EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName());
EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line());
}
{
// Clearing doesn't clear source location.
LocalizedPersistent<GCed> p1 = gced;
LocalizedPersistent<GCed> p2 = gced;
p2.Clear();
EXPECT_STREQ(p1.Location().Function(), p2.Location().Function());
EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName());
EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line());
}
{
// Clearing doesn't clear source location.
LocalizedCrossThreadPersistent<GCed> p1 = gced;
LocalizedCrossThreadPersistent<GCed> p2 = gced;
p2.Clear();
EXPECT_STREQ(p1.Location().Function(), p2.Location().Function());
EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName());
EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line());
}
{
LocalizedPersistent<GCed> p1 = gced;
const auto expected_loc = p1.Location();
LocalizedPersistent<GCed> p2 = std::move(p1);
EXPECT_STREQ(expected_loc.Function(), p2.Location().Function());
EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName());
EXPECT_EQ(expected_loc.Line(), p2.Location().Line());
}
{
LocalizedCrossThreadPersistent<GCed> p1 = gced;
const auto expected_loc = p1.Location();
LocalizedCrossThreadPersistent<GCed> p2 = std::move(p1);
EXPECT_STREQ(expected_loc.Function(), p2.Location().Function());
EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName());
EXPECT_EQ(expected_loc.Line(), p2.Location().Line());
}
{
LocalizedPersistent<GCed> p1 = gced;
const auto expected_loc = p1.Location();
LocalizedPersistent<GCed> p2;
p2 = std::move(p1);
EXPECT_STREQ(expected_loc.Function(), p2.Location().Function());
EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName());
EXPECT_EQ(expected_loc.Line(), p2.Location().Line());
}
{
LocalizedCrossThreadPersistent<GCed> p1 = gced;
const auto expected_loc = p1.Location();
LocalizedCrossThreadPersistent<GCed> p2;
p2 = std::move(p1);
EXPECT_STREQ(expected_loc.Function(), p2.Location().Function());
EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName());
EXPECT_EQ(expected_loc.Line(), p2.Location().Line());
}
}
#endif
namespace {
class ExpectingLocationVisitor final : public VisitorBase {
public:
explicit ExpectingLocationVisitor(const SourceLocation& expected_location)
: expected_loc_(expected_location) {}
protected:
void VisitRoot(const void* t, TraceDescriptor desc,
const SourceLocation& loc) final {
EXPECT_STREQ(expected_loc_.Function(), loc.Function());
EXPECT_STREQ(expected_loc_.FileName(), loc.FileName());
EXPECT_EQ(expected_loc_.Line(), loc.Line());
}
private:
const SourceLocation& expected_loc_;
};
} // namespace
TEST_F(PersistentTest, PersistentTraceLocation) {
GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
{
#if CPPGC_SUPPORTS_SOURCE_LOCATION
// Baseline for creating expected location which has a different line
// number.
const auto loc = SourceLocation::Current();
const auto expected_loc =
SourceLocation::Current(loc.Function(), loc.FileName(), loc.Line() + 6);
#else // !CCPPGC_SUPPORTS_SOURCE_LOCATION
const SourceLocation expected_loc;
#endif // !CCPPGC_SUPPORTS_SOURCE_LOCATION
LocalizedPersistent<GCed> p = gced;
ExpectingLocationVisitor visitor(expected_loc);
visitor.TraceRootForTesting(p, p.Location());
}
}
namespace {
class IncompleteType;
} // namespace
TEST_F(PersistentTest, EmptyPersistentConstructDestructWithoutCompleteType) {
// Test ensures that empty constructor and destructor compile without having
// a complete type available.
Persistent<IncompleteType> p1;
WeakPersistent<IncompleteType> p2;
subtle::CrossThreadPersistent<IncompleteType> p3;
subtle::WeakCrossThreadPersistent<IncompleteType> p4;
}
TEST_F(PersistentTest, Lock) {
subtle::WeakCrossThreadPersistent<GCed> weak;
auto strong = weak.Lock();
}
} // namespace internal
} // namespace cppgc