v8/test/unittests/heap/cppgc/persistent-family-unittest.cc
Michael Lippautz 3902ffbba4 cppgc: Persistent: Check thread usage on slow path
Checks whether a Persistent is used from the creation thread on slow
path allocations. In practice, these currently happen every 256
Persistent allocations. This is a best effort check that may help to
flush out issues that are missed with DCHECK builds.

Bug: chromium:1276570
Change-Id: Ia868ca436341b1b5ef427d5b3ec04926c1394e41
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3318658
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Anton Bikineev <bikineev@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78276}
2021-12-07 14:32:47 +00:00

1085 lines
38 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/persistent-node.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/base/logging.h"
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/globals.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::DefaultPersistentCheckingPolicy>;
template <typename T>
using LocalizedCrossThreadPersistent = internal::BasicCrossThreadPersistent<
T, internal::StrongCrossThreadPersistentPolicy,
internal::KeepLocationPolicy, internal::DisabledCheckingPolicy>;
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::TestWithHeap {};
class PersistentDeathTest : public testing::TestWithHeap {};
} // 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*>(nullptr);
EXPECT_EQ(nullptr, p.Get());
EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse());
}
}
TEST_F(PersistentTest, NullStateAssignment) {
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);
}
namespace {
template <template <typename> class PersistentType1,
template <typename> class PersistentType2>
void EqualityTest(cppgc::Heap* heap) {
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType1<GCed> persistent1 = gced;
PersistentType2<GCed> persistent2 = gced;
EXPECT_TRUE(persistent1 == persistent2);
EXPECT_FALSE(persistent1 != persistent2);
persistent2 = persistent1;
EXPECT_TRUE(persistent1 == persistent2);
EXPECT_FALSE(persistent1 != persistent2);
}
{
PersistentType1<GCed> persistent1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
PersistentType2<GCed> persistent2 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_TRUE(persistent1 != persistent2);
EXPECT_FALSE(persistent1 == persistent2);
}
}
} // namespace
TEST_F(PersistentTest, EqualityTest) {
cppgc::Heap* heap = GetHeap();
EqualityTest<Persistent, Persistent>(heap);
EqualityTest<Persistent, WeakPersistent>(heap);
EqualityTest<Persistent, subtle::CrossThreadPersistent>(heap);
EqualityTest<Persistent, subtle::WeakCrossThreadPersistent>(heap);
EqualityTest<WeakPersistent, Persistent>(heap);
EqualityTest<WeakPersistent, WeakPersistent>(heap);
EqualityTest<WeakPersistent, subtle::CrossThreadPersistent>(heap);
EqualityTest<WeakPersistent, subtle::WeakCrossThreadPersistent>(heap);
EqualityTest<subtle::CrossThreadPersistent, Persistent>(heap);
EqualityTest<subtle::CrossThreadPersistent, WeakPersistent>(heap);
EqualityTest<subtle::CrossThreadPersistent, subtle::CrossThreadPersistent>(
heap);
EqualityTest<subtle::CrossThreadPersistent,
subtle::WeakCrossThreadPersistent>(heap);
EqualityTest<subtle::WeakCrossThreadPersistent, Persistent>(heap);
EqualityTest<subtle::WeakCrossThreadPersistent, WeakPersistent>(heap);
EqualityTest<subtle::WeakCrossThreadPersistent,
subtle::CrossThreadPersistent>(heap);
EqualityTest<subtle::WeakCrossThreadPersistent,
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 {
class TraceCounter final : public GarbageCollected<TraceCounter> {
public:
void Trace(cppgc::Visitor* visitor) const {
trace_calls_++;
}
size_t trace_calls() const { return trace_calls_; }
private:
mutable size_t trace_calls_ = 0;
};
class DestructionCounter final : public GarbageCollected<DestructionCounter> {
public:
static size_t destructor_calls_;
~DestructionCounter() { destructor_calls_++; }
void Trace(cppgc::Visitor*) const {}
};
size_t DestructionCounter::destructor_calls_;
} // namespace
TEST_F(PersistentTest, PersistentRetainsObject) {
Persistent<TraceCounter> trace_counter =
MakeGarbageCollected<TraceCounter>(GetAllocationHandle());
WeakPersistent<TraceCounter> weak_trace_counter(trace_counter.Get());
EXPECT_EQ(0u, trace_counter->trace_calls());
PreciseGC();
size_t saved_trace_count = trace_counter->trace_calls();
EXPECT_LT(0u, saved_trace_count);
PreciseGC();
EXPECT_LT(saved_trace_count, trace_counter->trace_calls());
EXPECT_TRUE(weak_trace_counter);
}
TEST_F(PersistentTest, WeakPersistentDoesNotRetainObject) {
WeakPersistent<TraceCounter> weak_trace_counter =
MakeGarbageCollected<TraceCounter>(GetAllocationHandle());
PreciseGC();
EXPECT_FALSE(weak_trace_counter);
}
TEST_F(PersistentTest, ObjectReclaimedAfterClearedPersistent) {
WeakPersistent<DestructionCounter> weak_finalized;
{
DestructionCounter::destructor_calls_ = 0;
Persistent<DestructionCounter> finalized =
MakeGarbageCollected<DestructionCounter>(GetAllocationHandle());
weak_finalized = finalized.Get();
EXPECT_EQ(0u, DestructionCounter::destructor_calls_);
PreciseGC();
EXPECT_EQ(0u, DestructionCounter::destructor_calls_);
USE(finalized);
EXPECT_TRUE(weak_finalized);
}
PreciseGC();
EXPECT_EQ(1u, DestructionCounter::destructor_calls_);
EXPECT_FALSE(weak_finalized);
}
namespace {
class PersistentAccessOnBackgroundThread : public v8::base::Thread {
public:
explicit PersistentAccessOnBackgroundThread(GCed* raw_gced)
: v8::base::Thread(v8::base::Thread::Options(
"PersistentAccessOnBackgroundThread", 2 * kMB)),
raw_gced_(raw_gced) {}
void Run() override {
EXPECT_DEATH_IF_SUPPORTED(
Persistent<GCed> gced(static_cast<GCed*>(raw_gced_)), "");
}
private:
void* raw_gced_;
};
} // namespace
TEST_F(PersistentDeathTest, CheckCreationThread) {
#ifdef DEBUG
// In DEBUG mode, every Persistent creation should check whether the handle
// is created on the right thread. In release mode, this check is only
// performed on slow path allocations.
Persistent<GCed> first_persistent_triggers_slow_path(
MakeGarbageCollected<GCed>(GetAllocationHandle()));
#endif // DEBUG
PersistentAccessOnBackgroundThread thread(
MakeGarbageCollected<GCed>(GetAllocationHandle()));
CHECK(thread.StartSynchronously());
thread.Join();
}
} // namespace internal
} // namespace cppgc