// 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 "include/cppgc/garbage-collected.h" #include "include/cppgc/allocation.h" #include "include/cppgc/type-traits.h" #include "src/base/platform/mutex.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 GCed : public GarbageCollected { public: void Trace(Visitor*) const {} }; class NotGCed {}; class Mixin : public GarbageCollectedMixin {}; class GCedWithMixin : public GarbageCollected, public Mixin {}; class OtherMixin : public GarbageCollectedMixin {}; class MergedMixins : public Mixin, public OtherMixin { public: void Trace(cppgc::Visitor* visitor) const override { Mixin::Trace(visitor); OtherMixin::Trace(visitor); } }; class GCWithMergedMixins : public GCed, public MergedMixins { public: void Trace(cppgc::Visitor* visitor) const override { MergedMixins::Trace(visitor); } }; class GarbageCollectedTestWithHeap : public testing::TestSupportingAllocationOnly {}; } // namespace TEST(GarbageCollectedTest, GarbageCollectedTrait) { static_assert(!IsGarbageCollectedTypeV); static_assert(!IsGarbageCollectedTypeV); static_assert(IsGarbageCollectedTypeV); static_assert(!IsGarbageCollectedTypeV); static_assert(IsGarbageCollectedTypeV); static_assert(!IsGarbageCollectedTypeV); static_assert(IsGarbageCollectedTypeV); } TEST(GarbageCollectedTest, GarbageCollectedMixinTrait) { static_assert(!IsGarbageCollectedMixinTypeV); static_assert(!IsGarbageCollectedMixinTypeV); static_assert(!IsGarbageCollectedMixinTypeV); static_assert(IsGarbageCollectedMixinTypeV); static_assert(!IsGarbageCollectedMixinTypeV); static_assert(IsGarbageCollectedMixinTypeV); static_assert(!IsGarbageCollectedMixinTypeV); } TEST(GarbageCollectedTest, GarbageCollectedOrMixinTrait) { static_assert(!IsGarbageCollectedOrMixinTypeV); static_assert(IsGarbageCollectedOrMixinTypeV); static_assert(!IsGarbageCollectedOrMixinTypeV); static_assert(IsGarbageCollectedOrMixinTypeV); static_assert(IsGarbageCollectedOrMixinTypeV); static_assert(IsGarbageCollectedOrMixinTypeV); static_assert(IsGarbageCollectedOrMixinTypeV); } TEST(GarbageCollectedTest, GarbageCollectedWithMixinTrait) { static_assert(!IsGarbageCollectedWithMixinTypeV); static_assert(!IsGarbageCollectedWithMixinTypeV); static_assert(!IsGarbageCollectedWithMixinTypeV); static_assert(!IsGarbageCollectedWithMixinTypeV); static_assert(IsGarbageCollectedWithMixinTypeV); static_assert(!IsGarbageCollectedWithMixinTypeV); static_assert(IsGarbageCollectedWithMixinTypeV); } namespace { class ForwardDeclaredType; } // namespace TEST(GarbageCollectedTest, CompleteTypeTrait) { static_assert(IsCompleteV); static_assert(!IsCompleteV); } TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCurrentAddress) { GCed* gced = MakeGarbageCollected(GetAllocationHandle()); GCedWithMixin* gced_with_mixin = MakeGarbageCollected(GetAllocationHandle()); const void* base_object_payload = TraceTrait::GetTraceDescriptor( static_cast(gced_with_mixin)) .base_object_payload; EXPECT_EQ(gced_with_mixin, base_object_payload); EXPECT_NE(gced, base_object_payload); } namespace { class GCedWithPostConstructionCallback final : public GCed { public: static size_t cb_callcount; GCedWithPostConstructionCallback() { cb_callcount = 0; } }; size_t GCedWithPostConstructionCallback::cb_callcount; class MixinWithPostConstructionCallback { public: static size_t cb_callcount; MixinWithPostConstructionCallback() { cb_callcount = 0; } using MarkerForMixinWithPostConstructionCallback = int; }; size_t MixinWithPostConstructionCallback::cb_callcount; class GCedWithMixinWithPostConstructionCallback final : public GCed, public MixinWithPostConstructionCallback {}; } // namespace } // namespace internal template <> struct PostConstructionCallbackTrait< internal::GCedWithPostConstructionCallback> { static void Call(internal::GCedWithPostConstructionCallback* object) { EXPECT_FALSE( internal::HeapObjectHeader::FromObject(object).IsInConstruction()); internal::GCedWithPostConstructionCallback::cb_callcount++; } }; template struct PostConstructionCallbackTrait< T, std::void_t> { // The parameter could just be T*. static void Call( internal::GCedWithMixinWithPostConstructionCallback* object) { EXPECT_FALSE( internal::HeapObjectHeader::FromObject(object).IsInConstruction()); internal::GCedWithMixinWithPostConstructionCallback::cb_callcount++; } }; namespace internal { TEST_F(GarbageCollectedTestWithHeap, PostConstructionCallback) { EXPECT_EQ(0u, GCedWithPostConstructionCallback::cb_callcount); MakeGarbageCollected(GetAllocationHandle()); EXPECT_EQ(1u, GCedWithPostConstructionCallback::cb_callcount); } TEST_F(GarbageCollectedTestWithHeap, PostConstructionCallbackForMixin) { EXPECT_EQ(0u, MixinWithPostConstructionCallback::cb_callcount); MakeGarbageCollected( GetAllocationHandle()); EXPECT_EQ(1u, MixinWithPostConstructionCallback::cb_callcount); } namespace { int GetDummyValue() { static v8::base::Mutex mutex; static int ret = 43; // Global lock access to avoid reordering. v8::base::MutexGuard guard(&mutex); return ret; } class CheckObjectInConstructionBeforeInitializerList final : public GarbageCollected { public: CheckObjectInConstructionBeforeInitializerList() : in_construction_before_initializer_list_( HeapObjectHeader::FromObject(this).IsInConstruction()), unused_int_(GetDummyValue()) { EXPECT_TRUE(in_construction_before_initializer_list_); EXPECT_TRUE(HeapObjectHeader::FromObject(this).IsInConstruction()); } void Trace(Visitor*) const {} private: bool in_construction_before_initializer_list_; int unused_int_; }; class CheckMixinInConstructionBeforeInitializerList : public GarbageCollectedMixin { public: explicit CheckMixinInConstructionBeforeInitializerList(void* payload_start) : in_construction_before_initializer_list_( HeapObjectHeader::FromObject(payload_start).IsInConstruction()), unused_int_(GetDummyValue()) { EXPECT_TRUE(in_construction_before_initializer_list_); EXPECT_TRUE(HeapObjectHeader::FromObject(payload_start).IsInConstruction()); } void Trace(Visitor*) const override {} private: bool in_construction_before_initializer_list_; int unused_int_; }; class UnmanagedMixinForcingVTable { protected: virtual void ForceVTable() {} }; class CheckGCedWithMixinInConstructionBeforeInitializerList : public GarbageCollected< CheckGCedWithMixinInConstructionBeforeInitializerList>, public UnmanagedMixinForcingVTable, public CheckMixinInConstructionBeforeInitializerList { public: CheckGCedWithMixinInConstructionBeforeInitializerList() : CheckMixinInConstructionBeforeInitializerList(this) { // Ensure that compiler indeed generated an inner object. CHECK_NE( this, static_cast( static_cast(this))); } }; } // namespace TEST_F(GarbageCollectedTestWithHeap, GarbageCollectedInConstructionDuringCtor) { MakeGarbageCollected( GetAllocationHandle()); } TEST_F(GarbageCollectedTestWithHeap, GarbageCollectedMixinInConstructionDuringCtor) { MakeGarbageCollected( GetAllocationHandle()); } namespace { struct MixinA : GarbageCollectedMixin {}; struct MixinB : GarbageCollectedMixin {}; struct GCed1 : GarbageCollected, MixinA, MixinB {}; struct GCed2 : MixinA, MixinB {}; static_assert( sizeof(GCed1) == sizeof(GCed2), "Check that empty base optimization always works for GarbageCollected"); } // namespace } // namespace internal } // namespace cppgc