// 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 "include/cppgc/allocation.h" #include "include/cppgc/explicit-management.h" #include "include/cppgc/garbage-collected.h" #include "include/cppgc/heap-consistency.h" #include "include/cppgc/internal/api-constants.h" #include "include/cppgc/persistent.h" #include "include/cppgc/testing.h" #include "include/libplatform/libplatform.h" #include "include/v8-context.h" #include "include/v8-cppgc.h" #include "include/v8-local-handle.h" #include "include/v8-object.h" #include "include/v8-traced-handle.h" #include "src/api/api-inl.h" #include "src/common/globals.h" #include "src/heap/cppgc-js/cpp-heap.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/sweeper.h" #include "src/objects/objects-inl.h" #include "test/unittests/heap/cppgc-js/unified-heap-utils.h" #include "test/unittests/heap/heap-utils.h" namespace v8::internal { namespace { class Wrappable final : public cppgc::GarbageCollected { public: static size_t destructor_callcount; ~Wrappable() { destructor_callcount++; } void Trace(cppgc::Visitor* visitor) const { visitor->Trace(wrapper_); } void SetWrapper(v8::Isolate* isolate, v8::Local wrapper) { wrapper_.Reset(isolate, wrapper); } TracedReference& wrapper() { return wrapper_; } private: TracedReference wrapper_; }; size_t Wrappable::destructor_callcount = 0; using UnifiedHeapDetachedTest = TestWithHeapInternals; } // namespace TEST_F(UnifiedHeapTest, OnlyGC) { CollectGarbageWithEmbedderStack(); } TEST_F(UnifiedHeapTest, FindingV8ToCppReference) { v8::HandleScope scope(v8_isolate()); uint16_t wrappable_type = WrapperHelper::kTracedEmbedderId; auto* wrappable_object = cppgc::MakeGarbageCollected(allocation_handle()); v8::Local api_object = WrapperHelper::CreateWrapper( v8_isolate()->GetCurrentContext(), &wrappable_type, wrappable_object); Wrappable::destructor_callcount = 0; EXPECT_FALSE(api_object.IsEmpty()); EXPECT_EQ(0u, Wrappable::destructor_callcount); CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); EXPECT_EQ(0u, Wrappable::destructor_callcount); WrapperHelper::ResetWrappableConnection(api_object); CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); EXPECT_EQ(1u, Wrappable::destructor_callcount); } TEST_F(UnifiedHeapTest, WriteBarrierV8ToCppReference) { if (!v8_flags.incremental_marking) return; v8::HandleScope scope(v8_isolate()); void* wrappable = cppgc::MakeGarbageCollected(allocation_handle()); v8::Local api_object = WrapperHelper::CreateWrapper( v8_isolate()->GetCurrentContext(), nullptr, nullptr); Wrappable::destructor_callcount = 0; WrapperHelper::ResetWrappableConnection(api_object); SimulateIncrementalMarking(); uint16_t type_info = WrapperHelper::kTracedEmbedderId; WrapperHelper::SetWrappableConnection(api_object, &type_info, wrappable); CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); EXPECT_EQ(0u, Wrappable::destructor_callcount); } #if DEBUG namespace { class Unreferenced : public cppgc::GarbageCollected { public: void Trace(cppgc::Visitor*) const {} }; } // namespace TEST_F(UnifiedHeapTest, FreeUnreferencedDuringNoGcScope) { v8::HandleScope handle_scope(v8_isolate()); auto* unreferenced = cppgc::MakeGarbageCollected( allocation_handle(), cppgc::AdditionalBytes(cppgc::internal::api_constants::kMB)); // Force safepoint to force flushing of cached allocated/freed sizes in cppgc. cpp_heap().stats_collector()->NotifySafePointForTesting(); { cppgc::subtle::NoGarbageCollectionScope no_gc_scope(cpp_heap()); cppgc::subtle::FreeUnreferencedObject(cpp_heap(), *unreferenced); // Force safepoint to make sure allocated size decrease due to freeing // unreferenced object is reported to CppHeap. Due to // NoGarbageCollectionScope, CppHeap will cache the reported decrease and // won't report it further. cpp_heap().stats_collector()->NotifySafePointForTesting(); } // Running a GC resets the allocated size counters to the current marked bytes // counter. CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); // If CppHeap didn't clear it's cached values when the counters were reset, // the next safepoint will try to decrease the cached value from the last // marked bytes (which is smaller than the cached value) and crash. cppgc::MakeGarbageCollected(allocation_handle()); cpp_heap().stats_collector()->NotifySafePointForTesting(); } #endif // DEBUG TEST_F(UnifiedHeapTest, TracedReferenceRetainsFromStack) { v8::HandleScope handle_scope(v8_isolate()); TracedReference holder; { v8::HandleScope inner_handle_scope(v8_isolate()); auto local = v8::Object::New(v8_isolate()); EXPECT_TRUE(local->IsObject()); holder.Reset(v8_isolate(), local); } CollectGarbageWithEmbedderStack(cppgc::Heap::SweepingType::kAtomic); auto local = holder.Get(v8_isolate()); EXPECT_TRUE(local->IsObject()); } TEST_F(UnifiedHeapDetachedTest, AllocationBeforeConfigureHeap) { auto heap = v8::CppHeap::Create( V8::GetCurrentPlatform(), CppHeapCreateParams{{}, WrapperHelper::DefaultWrapperDescriptor()}); auto* object = cppgc::MakeGarbageCollected(heap->GetAllocationHandle()); cppgc::WeakPersistent weak_holder{object}; auto& js_heap = *isolate()->heap(); js_heap.AttachCppHeap(heap.get()); auto& cpp_heap = *CppHeap::From(isolate()->heap()->cpp_heap()); { CollectGarbage(OLD_SPACE); cpp_heap.AsBase().sweeper().FinishIfRunning(); EXPECT_TRUE(weak_holder); } USE(object); { EmbedderStackStateScope stack_scope( &js_heap, EmbedderStackStateScope::kExplicitInvocation, StackState::kNoHeapPointers); CollectGarbage(OLD_SPACE); cpp_heap.AsBase().sweeper().FinishIfRunning(); EXPECT_FALSE(weak_holder); } } TEST_F(UnifiedHeapDetachedTest, StandAloneCppGC) { // Test ensures that stand-alone C++ GC are possible when using CppHeap. This // works even in the presence of wrappables using TracedReference as long // as the reference is empty. auto heap = v8::CppHeap::Create( V8::GetCurrentPlatform(), CppHeapCreateParams{{}, WrapperHelper::DefaultWrapperDescriptor()}); auto* object = cppgc::MakeGarbageCollected(heap->GetAllocationHandle()); cppgc::WeakPersistent weak_holder{object}; heap->EnableDetachedGarbageCollectionsForTesting(); { heap->CollectGarbageForTesting( cppgc::EmbedderStackState::kMayContainHeapPointers); EXPECT_TRUE(weak_holder); } USE(object); { heap->CollectGarbageForTesting(cppgc::EmbedderStackState::kNoHeapPointers); EXPECT_FALSE(weak_holder); } } TEST_F(UnifiedHeapDetachedTest, StandaloneTestingHeap) { // Perform garbage collection through the StandaloneTestingHeap API. auto cpp_heap = v8::CppHeap::Create( V8::GetCurrentPlatform(), CppHeapCreateParams{{}, WrapperHelper::DefaultWrapperDescriptor()}); cpp_heap->EnableDetachedGarbageCollectionsForTesting(); cppgc::testing::StandaloneTestingHeap heap(cpp_heap->GetHeapHandle()); heap.StartGarbageCollection(); heap.PerformMarkingStep(cppgc::EmbedderStackState::kNoHeapPointers); heap.FinalizeGarbageCollection(cppgc::EmbedderStackState::kNoHeapPointers); } } // namespace v8::internal namespace cppgc { class CustomSpaceForTest : public CustomSpace { public: static constexpr size_t kSpaceIndex = 0; }; constexpr size_t CustomSpaceForTest::kSpaceIndex; } // namespace cppgc namespace v8::internal { namespace { class StatisticsReceiver final : public CustomSpaceStatisticsReceiver { public: static size_t num_calls_; StatisticsReceiver(cppgc::CustomSpaceIndex space_index, size_t bytes) : expected_space_index_(space_index), expected_bytes_(bytes) {} void AllocatedBytes(cppgc::CustomSpaceIndex space_index, size_t bytes) final { EXPECT_EQ(expected_space_index_.value, space_index.value); EXPECT_EQ(expected_bytes_, bytes); ++num_calls_; } private: const cppgc::CustomSpaceIndex expected_space_index_; const size_t expected_bytes_; }; size_t StatisticsReceiver::num_calls_ = 0u; class GCed final : public cppgc::GarbageCollected { public: ~GCed() { // Force a finalizer to guarantee sweeping can't finish without the main // thread. USE(data_); } static size_t GetAllocatedSize() { return sizeof(GCed) + sizeof(cppgc::internal::HeapObjectHeader); } void Trace(cppgc::Visitor*) const {} private: char data_[KB]; }; } // namespace } // namespace v8::internal namespace cppgc { template <> struct SpaceTrait { using Space = CustomSpaceForTest; }; } // namespace cppgc namespace v8::internal { namespace { class UnifiedHeapWithCustomSpaceTest : public UnifiedHeapTest { public: static std::vector> GetCustomSpaces() { std::vector> custom_spaces; custom_spaces.emplace_back(std::make_unique()); return custom_spaces; } UnifiedHeapWithCustomSpaceTest() : UnifiedHeapTest(GetCustomSpaces()) {} }; } // namespace TEST_F(UnifiedHeapWithCustomSpaceTest, CollectCustomSpaceStatisticsAtLastGC) { // TPH does not support kIncrementalAndConcurrent yet. if (v8_flags.enable_third_party_heap) return; StatisticsReceiver::num_calls_ = 0; // Initial state. cpp_heap().CollectCustomSpaceStatisticsAtLastGC( {cppgc::CustomSpaceForTest::kSpaceIndex}, std::make_unique( cppgc::CustomSpaceForTest::kSpaceIndex, 0u)); EXPECT_EQ(1u, StatisticsReceiver::num_calls_); // State unpdated only after GC. cppgc::Persistent live_obj = cppgc::MakeGarbageCollected(allocation_handle()); cppgc::MakeGarbageCollected(allocation_handle()); cpp_heap().CollectCustomSpaceStatisticsAtLastGC( {cppgc::CustomSpaceForTest::kSpaceIndex}, std::make_unique( cppgc::CustomSpaceForTest::kSpaceIndex, 0u)); EXPECT_EQ(2u, StatisticsReceiver::num_calls_); // Check state after GC. CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); cpp_heap().CollectCustomSpaceStatisticsAtLastGC( {cppgc::CustomSpaceForTest::kSpaceIndex}, std::make_unique( cppgc::CustomSpaceForTest::kSpaceIndex, GCed::GetAllocatedSize())); EXPECT_EQ(3u, StatisticsReceiver::num_calls_); // State callback delayed during sweeping. cppgc::Persistent another_live_obj = cppgc::MakeGarbageCollected(allocation_handle()); while (v8::platform::PumpMessageLoop( V8::GetCurrentPlatform(), v8_isolate(), v8::platform::MessageLoopBehavior::kDoNotWait)) { // Empty the message loop to avoid finalizing garbage collections through // unrelated tasks. } CollectGarbageWithoutEmbedderStack( cppgc::Heap::SweepingType::kIncrementalAndConcurrent); DCHECK(cpp_heap().sweeper().IsSweepingInProgress()); cpp_heap().CollectCustomSpaceStatisticsAtLastGC( {cppgc::CustomSpaceForTest::kSpaceIndex}, std::make_unique( cppgc::CustomSpaceForTest::kSpaceIndex, 2 * GCed::GetAllocatedSize())); while (v8::platform::PumpMessageLoop( V8::GetCurrentPlatform(), v8_isolate(), v8::platform::MessageLoopBehavior::kWaitForWork)) { if (3 < StatisticsReceiver::num_calls_) { EXPECT_FALSE(cpp_heap().sweeper().IsSweepingInProgress()); break; } } EXPECT_EQ(4u, StatisticsReceiver::num_calls_); } namespace { class InConstructionObjectReferringToGlobalHandle final : public cppgc::GarbageCollected< InConstructionObjectReferringToGlobalHandle> { public: InConstructionObjectReferringToGlobalHandle(Heap* heap, v8::Local wrapper) : wrapper_(reinterpret_cast(heap->isolate()), wrapper) { heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); } void Trace(cppgc::Visitor* visitor) const { visitor->Trace(wrapper_); } TracedReference& GetWrapper() { return wrapper_; } private: TracedReference wrapper_; }; } // namespace TEST_F(UnifiedHeapTest, InConstructionObjectReferringToGlobalHandle) { v8::HandleScope handle_scope(v8_isolate()); { v8::HandleScope inner_handle_scope(v8_isolate()); auto local = v8::Object::New(v8_isolate()); auto* cpp_obj = cppgc::MakeGarbageCollected< InConstructionObjectReferringToGlobalHandle>( allocation_handle(), reinterpret_cast(v8_isolate())->heap(), local); CHECK_NE(kGlobalHandleZapValue, ValueHelper::ValueAsAddress( ValueHelper::HandleAsValue(cpp_obj->GetWrapper()))); } } namespace { class ResetReferenceInDestructorObject final : public cppgc::GarbageCollected { public: ResetReferenceInDestructorObject(Heap* heap, v8::Local wrapper) : wrapper_(reinterpret_cast(heap->isolate()), wrapper) {} ~ResetReferenceInDestructorObject() { wrapper_.Reset(); } void Trace(cppgc::Visitor* visitor) const { visitor->Trace(wrapper_); } private: TracedReference wrapper_; }; } // namespace TEST_F(UnifiedHeapTest, ResetReferenceInDestructor) { v8::HandleScope handle_scope(v8_isolate()); { v8::HandleScope inner_handle_scope(v8_isolate()); auto local = v8::Object::New(v8_isolate()); cppgc::MakeGarbageCollected( allocation_handle(), reinterpret_cast(v8_isolate())->heap(), local); } CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); } TEST_F(UnifiedHeapTest, OnStackReferencesAreTemporary) { ManualGCScope manual_gc(i_isolate()); v8::Global observer; { v8::TracedReference stack_ref; v8::HandleScope scope(v8_isolate()); v8::Local api_object = WrapperHelper::CreateWrapper( v8_isolate()->GetCurrentContext(), nullptr, nullptr); stack_ref.Reset(v8_isolate(), api_object); observer.Reset(v8_isolate(), api_object); observer.SetWeak(); } EXPECT_FALSE(observer.IsEmpty()); { // Conservative scanning may find stale pointers to on-stack handles. // Disable scanning, assuming the slots are overwritten. DisableConservativeStackScanningScopeForTesting no_stack_scanning( reinterpret_cast(v8_isolate())->heap()); CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); } EXPECT_TRUE(observer.IsEmpty()); } TEST_F(UnifiedHeapTest, TracedReferenceOnStack) { ManualGCScope manual_gc(i_isolate()); v8::Global observer; v8::TracedReference stack_ref; { v8::HandleScope scope(v8_isolate()); v8::Local object = WrapperHelper::CreateWrapper( v8_isolate()->GetCurrentContext(), nullptr, nullptr); stack_ref.Reset(v8_isolate(), object); observer.Reset(v8_isolate(), object); observer.SetWeak(); } EXPECT_FALSE(observer.IsEmpty()); FullGC(); EXPECT_FALSE(observer.IsEmpty()); } namespace { enum class Operation { kCopy, kMove, }; template V8_NOINLINE void PerformOperation(Operation op, T* target, T* source) { switch (op) { case Operation::kMove: *target = std::move(*source); break; case Operation::kCopy: *target = *source; source->Reset(); break; } } enum class TargetHandling { kNonInitialized, kInitializedYoungGen, kInitializedOldGen }; class GCedWithHeapRef final : public cppgc::GarbageCollected { public: v8::TracedReference heap_handle; void Trace(cppgc::Visitor* visitor) const { visitor->Trace(heap_handle); } }; V8_NOINLINE void StackToHeapTest(v8::Isolate* v8_isolate, Operation op, TargetHandling target_handling) { v8::Global observer; v8::TracedReference stack_handle; v8::CppHeap* cpp_heap = v8_isolate->GetCppHeap(); cppgc::Persistent cpp_heap_obj = cppgc::MakeGarbageCollected( cpp_heap->GetAllocationHandle()); if (target_handling != TargetHandling::kNonInitialized) { v8::HandleScope scope(v8_isolate); v8::Local to_object = WrapperHelper::CreateWrapper( v8_isolate->GetCurrentContext(), nullptr, nullptr); EXPECT_TRUE( IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object))); if (!v8_flags.single_generation && target_handling == TargetHandling::kInitializedOldGen) { FullGC(v8_isolate); EXPECT_FALSE( i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object))); } cpp_heap_obj->heap_handle.Reset(v8_isolate, to_object); } { v8::HandleScope scope(v8_isolate); v8::Local object = WrapperHelper::CreateWrapper( v8_isolate->GetCurrentContext(), nullptr, nullptr); stack_handle.Reset(v8_isolate, object); observer.Reset(v8_isolate, object); observer.SetWeak(); } EXPECT_FALSE(observer.IsEmpty()); FullGC(v8_isolate); EXPECT_FALSE(observer.IsEmpty()); PerformOperation(op, &cpp_heap_obj->heap_handle, &stack_handle); FullGC(v8_isolate); EXPECT_FALSE(observer.IsEmpty()); cpp_heap_obj.Clear(); { // Conservative scanning may find stale pointers to on-stack handles. // Disable scanning, assuming the slots are overwritten. DisableConservativeStackScanningScopeForTesting no_stack_scanning( reinterpret_cast(v8_isolate)->heap()); FullGC(v8_isolate); } ASSERT_TRUE(observer.IsEmpty()); } V8_NOINLINE void HeapToStackTest(v8::Isolate* v8_isolate, Operation op, TargetHandling target_handling) { v8::Global observer; v8::TracedReference stack_handle; v8::CppHeap* cpp_heap = v8_isolate->GetCppHeap(); cppgc::Persistent cpp_heap_obj = cppgc::MakeGarbageCollected( cpp_heap->GetAllocationHandle()); if (target_handling != TargetHandling::kNonInitialized) { v8::HandleScope scope(v8_isolate); v8::Local to_object = WrapperHelper::CreateWrapper( v8_isolate->GetCurrentContext(), nullptr, nullptr); EXPECT_TRUE( IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object))); if (!v8_flags.single_generation && target_handling == TargetHandling::kInitializedOldGen) { FullGC(v8_isolate); EXPECT_FALSE( i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object))); } stack_handle.Reset(v8_isolate, to_object); } { v8::HandleScope scope(v8_isolate); v8::Local object = WrapperHelper::CreateWrapper( v8_isolate->GetCurrentContext(), nullptr, nullptr); cpp_heap_obj->heap_handle.Reset(v8_isolate, object); observer.Reset(v8_isolate, object); observer.SetWeak(); } EXPECT_FALSE(observer.IsEmpty()); FullGC(v8_isolate); EXPECT_FALSE(observer.IsEmpty()); PerformOperation(op, &stack_handle, &cpp_heap_obj->heap_handle); FullGC(v8_isolate); EXPECT_FALSE(observer.IsEmpty()); stack_handle.Reset(); { // Conservative scanning may find stale pointers to on-stack handles. // Disable scanning, assuming the slots are overwritten. DisableConservativeStackScanningScopeForTesting no_stack_scanning( reinterpret_cast(v8_isolate)->heap()); FullGC(v8_isolate); } EXPECT_TRUE(observer.IsEmpty()); } V8_NOINLINE void StackToStackTest(v8::Isolate* v8_isolate, Operation op, TargetHandling target_handling) { v8::Global observer; v8::TracedReference stack_handle1; v8::TracedReference stack_handle2; if (target_handling != TargetHandling::kNonInitialized) { v8::HandleScope scope(v8_isolate); v8::Local to_object = WrapperHelper::CreateWrapper( v8_isolate->GetCurrentContext(), nullptr, nullptr); EXPECT_TRUE( IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object))); if (!v8_flags.single_generation && target_handling == TargetHandling::kInitializedOldGen) { FullGC(v8_isolate); EXPECT_FALSE( i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object))); } stack_handle2.Reset(v8_isolate, to_object); } { v8::HandleScope scope(v8_isolate); v8::Local object = WrapperHelper::CreateWrapper( v8_isolate->GetCurrentContext(), nullptr, nullptr); stack_handle1.Reset(v8_isolate, object); observer.Reset(v8_isolate, object); observer.SetWeak(); } EXPECT_FALSE(observer.IsEmpty()); FullGC(v8_isolate); EXPECT_FALSE(observer.IsEmpty()); PerformOperation(op, &stack_handle2, &stack_handle1); FullGC(v8_isolate); EXPECT_FALSE(observer.IsEmpty()); stack_handle2.Reset(); { // Conservative scanning may find stale pointers to on-stack handles. // Disable scanning, assuming the slots are overwritten. DisableConservativeStackScanningScopeForTesting no_stack_scanning( reinterpret_cast(v8_isolate)->heap()); FullGC(v8_isolate); } EXPECT_TRUE(observer.IsEmpty()); } } // namespace TEST_F(UnifiedHeapTest, TracedReferenceMove) { ManualGCScope manual_gc(i_isolate()); StackToHeapTest(v8_isolate(), Operation::kMove, TargetHandling::kNonInitialized); StackToHeapTest(v8_isolate(), Operation::kMove, TargetHandling::kInitializedYoungGen); StackToHeapTest(v8_isolate(), Operation::kMove, TargetHandling::kInitializedOldGen); HeapToStackTest(v8_isolate(), Operation::kMove, TargetHandling::kNonInitialized); HeapToStackTest(v8_isolate(), Operation::kMove, TargetHandling::kInitializedYoungGen); HeapToStackTest(v8_isolate(), Operation::kMove, TargetHandling::kInitializedOldGen); StackToStackTest(v8_isolate(), Operation::kMove, TargetHandling::kNonInitialized); StackToStackTest(v8_isolate(), Operation::kMove, TargetHandling::kInitializedYoungGen); StackToStackTest(v8_isolate(), Operation::kMove, TargetHandling::kInitializedOldGen); } TEST_F(UnifiedHeapTest, TracedReferenceCopy) { ManualGCScope manual_gc(i_isolate()); StackToHeapTest(v8_isolate(), Operation::kCopy, TargetHandling::kNonInitialized); StackToHeapTest(v8_isolate(), Operation::kCopy, TargetHandling::kInitializedYoungGen); StackToHeapTest(v8_isolate(), Operation::kCopy, TargetHandling::kInitializedOldGen); HeapToStackTest(v8_isolate(), Operation::kCopy, TargetHandling::kNonInitialized); HeapToStackTest(v8_isolate(), Operation::kCopy, TargetHandling::kInitializedYoungGen); HeapToStackTest(v8_isolate(), Operation::kCopy, TargetHandling::kInitializedOldGen); StackToStackTest(v8_isolate(), Operation::kCopy, TargetHandling::kNonInitialized); StackToStackTest(v8_isolate(), Operation::kCopy, TargetHandling::kInitializedYoungGen); StackToStackTest(v8_isolate(), Operation::kCopy, TargetHandling::kInitializedOldGen); } TEST_F(UnifiedHeapTest, TracingInEphemerons) { // Tests that wrappers that are part of ephemerons are traced. ManualGCScope manual_gc(i_isolate()); v8::HandleScope scope(v8_isolate()); v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); uint16_t wrappable_type = WrapperHelper::kTracedEmbedderId; Wrappable::destructor_callcount = 0; v8::Local key = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); Handle weak_map = i_isolate()->factory()->NewJSWeakMap(); { v8::HandleScope inner_scope(v8_isolate()); // C++ object that should be traced through ephemeron value. auto* wrappable_object = cppgc::MakeGarbageCollected(allocation_handle()); v8::Local value = WrapperHelper::CreateWrapper( v8_isolate()->GetCurrentContext(), &wrappable_type, wrappable_object); EXPECT_FALSE(value.IsEmpty()); Handle js_key = handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate()); Handle js_value = v8::Utils::OpenHandle(*value); int32_t hash = js_key->GetOrCreateHash(i_isolate()).value(); JSWeakCollection::Set(weak_map, js_key, js_value, hash); } CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); EXPECT_EQ(Wrappable::destructor_callcount, 0u); } TEST_F(UnifiedHeapTest, TracedReferenceHandlesDoNotLeak) { // TracedReference handles are not cleared by the destructor of the embedder // object. To avoid leaks we need to mark these handles during GC. // This test checks that unmarked handles do not leak. ManualGCScope manual_gc(i_isolate()); v8::HandleScope scope(v8_isolate()); v8::TracedReference ref; ref.Reset(v8_isolate(), v8::Undefined(v8_isolate())); auto* traced_handles = i_isolate()->traced_handles(); const size_t initial_count = traced_handles->used_node_count(); CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic); const size_t final_count = traced_handles->used_node_count(); EXPECT_EQ(initial_count, final_count + 1); } } // namespace v8::internal