// 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-visitor.h" #include "include/cppgc/allocation.h" #include "include/cppgc/member.h" #include "include/cppgc/persistent.h" #include "include/cppgc/source-location.h" #include "src/heap/cppgc/globals.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/marker.h" #include "src/heap/cppgc/marking-state.h" #include "test/unittests/heap/cppgc/tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace cppgc { namespace internal { namespace { class MarkingVisitorTest : public testing::TestWithHeap { public: MarkingVisitorTest() : marker_(std::make_unique(*Heap::From(GetHeap()), GetPlatformHandle().get())) { marker_->StartMarking(); } ~MarkingVisitorTest() override { marker_->ClearAllWorklistsForTesting(); } Marker* GetMarker() { return marker_.get(); } private: std::unique_ptr marker_; }; class GCed : public GarbageCollected { public: void Trace(cppgc::Visitor*) const {} }; class Mixin : public GarbageCollectedMixin {}; class GCedWithMixin : public GarbageCollected, public Mixin { public: void Trace(cppgc::Visitor*) const override {} }; class TestMarkingVisitor : public MutatorMarkingVisitor { public: explicit TestMarkingVisitor(Marker* marker) : MutatorMarkingVisitor(marker->heap(), marker->MutatorMarkingStateForTesting()) {} ~TestMarkingVisitor() { marking_state_.Publish(); } BasicMarkingState& marking_state() { return marking_state_; } }; class TestRootMarkingVisitor : public RootMarkingVisitor { public: explicit TestRootMarkingVisitor(Marker* marker) : RootMarkingVisitor(marker->MutatorMarkingStateForTesting()) {} ~TestRootMarkingVisitor() { mutator_marking_state_.Publish(); } MutatorMarkingState& marking_state() { return mutator_marking_state_; } }; } // namespace TEST_F(MarkingVisitorTest, MarkedBytesAreInitiallyZero) { EXPECT_EQ(0u, GetMarker()->MutatorMarkingStateForTesting().marked_bytes()); } // Strong references are marked. TEST_F(MarkingVisitorTest, MarkMember) { Member object(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(object); EXPECT_TRUE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkMemberMixin) { GCedWithMixin* object( MakeGarbageCollected(GetAllocationHandle())); Member mixin(object); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(mixin); EXPECT_TRUE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkPersistent) { Persistent object(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestRootMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(object); EXPECT_TRUE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkPersistentMixin) { GCedWithMixin* object( MakeGarbageCollected(GetAllocationHandle())); Persistent mixin(object); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestRootMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(mixin); EXPECT_TRUE(header.IsMarked()); } // Weak references are not marked. TEST_F(MarkingVisitorTest, DontMarkWeakMember) { WeakMember object(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(object); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakMemberMixin) { GCedWithMixin* object( MakeGarbageCollected(GetAllocationHandle())); WeakMember mixin(object); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(mixin); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakPersistent) { WeakPersistent object( MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestRootMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(object); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakPersistentMixin) { GCedWithMixin* object( MakeGarbageCollected(GetAllocationHandle())); WeakPersistent mixin(object); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestRootMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.Trace(mixin); EXPECT_FALSE(header.IsMarked()); } // In construction objects are not marked. namespace { class GCedWithInConstructionCallback : public GarbageCollected { public: template explicit GCedWithInConstructionCallback(Callback callback) { callback(this); } void Trace(cppgc::Visitor*) const {} }; class MixinWithInConstructionCallback : public GarbageCollectedMixin { public: template explicit MixinWithInConstructionCallback(Callback callback) { callback(this); } }; class GCedWithMixinWithInConstructionCallback : public GarbageCollected, public MixinWithInConstructionCallback { public: template explicit GCedWithMixinWithInConstructionCallback(Callback callback) : MixinWithInConstructionCallback(callback) {} void Trace(cppgc::Visitor*) const override {} }; } // namespace TEST_F(MarkingVisitorTest, MarkMemberInConstruction) { TestMarkingVisitor visitor(GetMarker()); GCedWithInConstructionCallback* gced = MakeGarbageCollected( GetAllocationHandle(), [&visitor](GCedWithInConstructionCallback* obj) { Member object(obj); visitor.Trace(object); }); HeapObjectHeader& header = HeapObjectHeader::FromObject(gced); EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( &header)); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkMemberMixinInConstruction) { TestMarkingVisitor visitor(GetMarker()); GCedWithMixinWithInConstructionCallback* gced = MakeGarbageCollected( GetAllocationHandle(), [&visitor](MixinWithInConstructionCallback* obj) { Member mixin(obj); visitor.Trace(mixin); }); HeapObjectHeader& header = HeapObjectHeader::FromObject(gced); EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( &header)); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakMemberInConstruction) { TestMarkingVisitor visitor(GetMarker()); GCedWithInConstructionCallback* gced = MakeGarbageCollected( GetAllocationHandle(), [&visitor](GCedWithInConstructionCallback* obj) { WeakMember object(obj); visitor.Trace(object); }); HeapObjectHeader& header = HeapObjectHeader::FromObject(gced); EXPECT_FALSE( visitor.marking_state().not_fully_constructed_worklist().Contains( &header)); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakMemberMixinInConstruction) { TestMarkingVisitor visitor(GetMarker()); GCedWithMixinWithInConstructionCallback* gced = MakeGarbageCollected( GetAllocationHandle(), [&visitor](MixinWithInConstructionCallback* obj) { WeakMember mixin(obj); visitor.Trace(mixin); }); HeapObjectHeader& header = HeapObjectHeader::FromObject(gced); EXPECT_FALSE( visitor.marking_state().not_fully_constructed_worklist().Contains( &header)); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkPersistentInConstruction) { TestRootMarkingVisitor visitor(GetMarker()); GCedWithInConstructionCallback* gced = MakeGarbageCollected( GetAllocationHandle(), [&visitor](GCedWithInConstructionCallback* obj) { Persistent object(obj); visitor.Trace(object); }); HeapObjectHeader& header = HeapObjectHeader::FromObject(gced); EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( &header)); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkPersistentMixinInConstruction) { TestRootMarkingVisitor visitor(GetMarker()); GCedWithMixinWithInConstructionCallback* gced = MakeGarbageCollected( GetAllocationHandle(), [&visitor](MixinWithInConstructionCallback* obj) { Persistent mixin(obj); visitor.Trace(mixin); }); HeapObjectHeader& header = HeapObjectHeader::FromObject(gced); EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( &header)); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, StrongTracingMarksWeakMember) { WeakMember object(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromObject(object); TestMarkingVisitor visitor(GetMarker()); EXPECT_FALSE(header.IsMarked()); visitor.TraceStrongly(object); EXPECT_TRUE(header.IsMarked()); } namespace { struct GCedWithDestructor : GarbageCollected { ~GCedWithDestructor() { ++g_finalized; } static size_t g_finalized; void Trace(Visitor* v) const {} }; size_t GCedWithDestructor::g_finalized = 0; struct GCedWithInConstructionCallbackWithMember : GCedWithDestructor { template explicit GCedWithInConstructionCallbackWithMember(Callback callback) { callback(this); } void Trace(Visitor* v) const { GCedWithDestructor::Trace(v); v->Trace(member); } Member member; }; struct ConservativeTracerTest : public testing::TestWithHeap { ConservativeTracerTest() { GCedWithDestructor::g_finalized = 0; } }; } // namespace TEST_F(ConservativeTracerTest, TraceConservativelyInConstructionObject) { auto* volatile gced = MakeGarbageCollected( GetAllocationHandle(), [this](GCedWithInConstructionCallbackWithMember* obj) V8_NOINLINE { [](GCedWithInConstructionCallbackWithMember* obj, AllocationHandle& handle) V8_NOINLINE { obj->member = MakeGarbageCollected(handle); }(obj, GetAllocationHandle()); ConservativeGC(); }); USE(gced); ConservativeGC(); EXPECT_EQ(0u, GCedWithDestructor::g_finalized); // Call into HoH::GetGCInfoIndex to prevent the compiler to optimize away the // stack variable. EXPECT_EQ(HeapObjectHeader::FromObject(gced).GetGCInfoIndex(), GCInfoTrait::Index()); } TEST_F(ConservativeTracerTest, TraceConservativelyStack) { volatile std::array, 16u> members = [this]() V8_NOINLINE { std::array, 16u> members; for (auto& member : members) member = MakeGarbageCollected(GetAllocationHandle()); return members; }(); USE(members); ConservativeGC(); EXPECT_EQ(0u, GCedWithDestructor::g_finalized); // Call into HoH::GetGCInfoIndex to prevent the compiler to optimize away the // stack variable. auto member = const_cast&>(members)[0]; EXPECT_EQ(HeapObjectHeader::FromObject(member.Get()).GetGCInfoIndex(), GCInfoTrait::Index()); } } // namespace internal } // namespace cppgc