// 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: V8_NOINLINE void VerifyMarking(HeapBase& heap, StackState stack_state, size_t expected_marked_bytes) { Heap::From(GetHeap())->object_allocator().ResetLinearAllocationBuffers(); Heap::From(GetHeap())->stack()->SetMarkerToCurrentStackPosition(); MarkingVerifier verifier(heap, CollectionType::kMajor); verifier.Run(stack_state, expected_marked_bytes); } }; class GCed : public GarbageCollected { 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 child_; WeakMember weak_child_; }; template V8_NOINLINE T access(volatile const T& t) { return t; } } // namespace // Following tests should not crash. TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedOnStackReference) { GCed* object = MakeGarbageCollected(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 parent = MakeGarbageCollected(GetAllocationHandle()); auto& parent_header = HeapObjectHeader::FromObject(parent.Get()); ASSERT_TRUE(parent_header.TryMarkAtomic()); parent->SetChild(MakeGarbageCollected(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 parent = MakeGarbageCollected(GetAllocationHandle()); auto& parent_header = HeapObjectHeader::FromObject(parent.Get()); ASSERT_TRUE(parent_header.TryMarkAtomic()); parent->SetWeakChild(MakeGarbageCollected(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 { public: template explicit GCedWithCallback(Callback callback) { callback(this); } void Trace(cppgc::Visitor* visitor) const {} }; } // namespace TEST_F(MarkingVerifierTest, DoesNotDieOnInConstructionOnObject) { MakeGarbageCollected( 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 { public: template GCedWithCallbackAndChild(GCed* gced, Callback callback) : child_(gced) { callback(this); } void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } private: Member child_; }; template struct Holder : public GarbageCollected> { public: void Trace(cppgc::Visitor* visitor) const { visitor->Trace(object); } Member 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> persistent = MakeGarbageCollected>( GetAllocationHandle()); GCConfig config = GCConfig::PreciseIncrementalConfig(); Heap::From(GetHeap())->StartIncrementalGarbageCollection(config); MakeGarbageCollected( GetAllocationHandle(), MakeGarbageCollected(GetAllocationHandle()), [&persistent](GCedWithCallbackAndChild* obj) { persistent->object = obj; }); GetMarkerRef()->IncrementalMarkingStepForTesting(StackState::kNoHeapPointers); Heap::From(GetHeap())->FinalizeIncrementalGarbageCollectionIfRunning(config); } // Death tests. namespace { class MarkingVerifierDeathTest : public MarkingVerifierTest { protected: template