v8/test/unittests/heap/cppgc/marking-verifier-unittest.cc
Michael Lippautz 4b437beee9 cppgc: Add support for young generation only marking verification
Marking verification should only process young generation objects when
verifying the marking state of a young generation garbage collection.

Bug: v8:12324
Change-Id: I01db261437ec5c42ddb6c79c44e31b5fe0e536d7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3231343
Reviewed-by: Omer Katz <omerkatz@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77518}
2021-10-25 11:16:28 +00:00

273 lines
9.2 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 "src/heap/cppgc/marking-verifier.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/prefinalizer.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class MarkingVerifierTest : public testing::TestWithHeap {
public:
using StackState = Heap::Config::StackState;
V8_NOINLINE void VerifyMarking(HeapBase& heap, StackState stack_state,
size_t expected_marked_bytes) {
Heap::From(GetHeap())->object_allocator().ResetLinearAllocationBuffers();
MarkingVerifier verifier(heap, Heap::Config::CollectionType::kMajor);
verifier.Run(stack_state, v8::base::Stack::GetCurrentStackPosition(),
expected_marked_bytes);
}
};
class GCed : public GarbageCollected<GCed> {
public:
void SetChild(GCed* child) { child_ = child; }
void SetWeakChild(GCed* child) { weak_child_ = child; }
GCed* child() const { return child_.Get(); }
GCed* weak_child() const { return weak_child_.Get(); }
void Trace(cppgc::Visitor* visitor) const {
visitor->Trace(child_);
visitor->Trace(weak_child_);
}
private:
Member<GCed> child_;
WeakMember<GCed> weak_child_;
};
template <typename T>
V8_NOINLINE T access(volatile const T& t) {
return t;
}
} // namespace
// Following tests should not crash.
TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedOnStackReference) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
ASSERT_TRUE(header.TryMarkAtomic());
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers, header.AllocatedSize());
access(object);
}
TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent.Get());
ASSERT_TRUE(parent_header.TryMarkAtomic());
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
auto& child_header = HeapObjectHeader::FromObject(parent->child());
ASSERT_TRUE(child_header.TryMarkAtomic());
VerifyMarking(Heap::From(GetHeap())->AsBase(), StackState::kNoHeapPointers,
parent_header.AllocatedSize() + child_header.AllocatedSize());
}
TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedWeakMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent.Get());
ASSERT_TRUE(parent_header.TryMarkAtomic());
parent->SetWeakChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
auto& child_header = HeapObjectHeader::FromObject(parent->weak_child());
ASSERT_TRUE(child_header.TryMarkAtomic());
VerifyMarking(Heap::From(GetHeap())->AsBase(), StackState::kNoHeapPointers,
parent_header.AllocatedSize() + child_header.AllocatedSize());
}
namespace {
class GCedWithCallback : public GarbageCollected<GCedWithCallback> {
public:
template <typename Callback>
explicit GCedWithCallback(Callback callback) {
callback(this);
}
void Trace(cppgc::Visitor* visitor) const {}
};
} // namespace
TEST_F(MarkingVerifierTest, DoesNotDieOnInConstructionOnObject) {
MakeGarbageCollected<GCedWithCallback>(
GetAllocationHandle(), [this](GCedWithCallback* obj) {
auto& header = HeapObjectHeader::FromObject(obj);
CHECK(header.TryMarkAtomic());
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers,
header.AllocatedSize());
});
}
namespace {
class GCedWithCallbackAndChild final
: public GarbageCollected<GCedWithCallbackAndChild> {
public:
template <typename Callback>
GCedWithCallbackAndChild(GCed* gced, Callback callback) : child_(gced) {
callback(this);
}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); }
private:
Member<GCed> child_;
};
template <typename T>
struct Holder : public GarbageCollected<Holder<T>> {
public:
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(object); }
Member<T> object = nullptr;
};
} // namespace
TEST_F(MarkingVerifierTest, DoesntDieOnInConstructionObjectWithWriteBarrier) {
// Regression test: https://crbug.com/v8/10989.
// GCedWithCallbackAndChild is marked by write barrier and then discarded by
// FlushNotFullyConstructedObjects because it is already marked.
Persistent<Holder<GCedWithCallbackAndChild>> persistent =
MakeGarbageCollected<Holder<GCedWithCallbackAndChild>>(
GetAllocationHandle());
GarbageCollector::Config config =
GarbageCollector::Config::PreciseIncrementalConfig();
Heap::From(GetHeap())->StartIncrementalGarbageCollection(config);
MakeGarbageCollected<GCedWithCallbackAndChild>(
GetAllocationHandle(), MakeGarbageCollected<GCed>(GetAllocationHandle()),
[&persistent](GCedWithCallbackAndChild* obj) {
persistent->object = obj;
});
GetMarkerRef()->IncrementalMarkingStepForTesting(
GarbageCollector::Config::StackState::kNoHeapPointers);
Heap::From(GetHeap())->FinalizeIncrementalGarbageCollectionIfRunning(config);
}
// Death tests.
namespace {
class MarkingVerifierDeathTest : public MarkingVerifierTest {
protected:
template <template <typename T> class Reference>
void TestResurrectingPreFinalizer();
};
} // namespace
TEST_F(MarkingVerifierDeathTest, DieOnUnmarkedOnStackReference) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
USE(header);
EXPECT_DEATH_IF_SUPPORTED(VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers,
header.AllocatedSize()),
"");
access(object);
}
TEST_F(MarkingVerifierDeathTest, DieOnUnmarkedMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent);
ASSERT_TRUE(parent_header.TryMarkAtomic());
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kNoHeapPointers, parent_header.AllocatedSize()),
"");
}
TEST_F(MarkingVerifierDeathTest, DieOnUnmarkedWeakMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent);
ASSERT_TRUE(parent_header.TryMarkAtomic());
parent->SetWeakChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kNoHeapPointers, parent_header.AllocatedSize()),
"");
}
#ifdef CPPGC_VERIFY_HEAP
TEST_F(MarkingVerifierDeathTest, DieOnUnexpectedLiveByteCount) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
ASSERT_TRUE(header.TryMarkAtomic());
EXPECT_DEATH_IF_SUPPORTED(VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers,
header.AllocatedSize() - 1),
"");
}
#endif // CPPGC_VERIFY_HEAP
namespace {
template <template <typename T> class Reference>
class ResurrectingPreFinalizer
: public GarbageCollected<ResurrectingPreFinalizer<Reference>> {
CPPGC_USING_PRE_FINALIZER(ResurrectingPreFinalizer<Reference>, Dispose);
public:
class Storage : public GarbageCollected<Storage> {
public:
void Trace(Visitor* visitor) const { visitor->Trace(ref); }
Reference<GCed> ref;
};
ResurrectingPreFinalizer(Storage* storage, GCed* object_that_dies)
: storage_(storage), object_that_dies_(object_that_dies) {}
void Trace(Visitor* visitor) const {
visitor->Trace(storage_);
visitor->Trace(object_that_dies_);
}
private:
void Dispose() { storage_->ref = object_that_dies_; }
Member<Storage> storage_;
Member<GCed> object_that_dies_;
};
} // namespace
template <template <typename T> class Reference>
void MarkingVerifierDeathTest::TestResurrectingPreFinalizer() {
Persistent<typename ResurrectingPreFinalizer<Reference>::Storage> storage(
MakeGarbageCollected<
typename ResurrectingPreFinalizer<Reference>::Storage>(
GetAllocationHandle()));
MakeGarbageCollected<ResurrectingPreFinalizer<Reference>>(
GetAllocationHandle(), storage.Get(),
MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
}
#if CPPGC_VERIFY_HEAP
TEST_F(MarkingVerifierDeathTest, DiesOnResurrectedMember) {
TestResurrectingPreFinalizer<Member>();
}
TEST_F(MarkingVerifierDeathTest, DiesOnResurrectedWeakMember) {
TestResurrectingPreFinalizer<WeakMember>();
}
#endif // CPPGC_VERIFY_HEAP
} // namespace internal
} // namespace cppgc