// Copyright 2017 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 "include/v8.h" #include "src/api/api-inl.h" #include "src/heap/heap-inl.h" #include "src/heap/safepoint.h" #include "src/objects/module.h" #include "src/objects/objects-inl.h" #include "src/objects/script.h" #include "src/objects/shared-function-info.h" #include "test/cctest/cctest.h" #include "test/cctest/heap/heap-utils.h" namespace v8 { namespace internal { namespace heap { namespace { v8::Local ConstructTraceableJSApiObject( v8::Local context, void* first_field, void* second_field) { v8::EscapableHandleScope scope(context->GetIsolate()); v8::Local function_t = v8::FunctionTemplate::New(context->GetIsolate()); v8::Local instance_t = function_t->InstanceTemplate(); instance_t->SetInternalFieldCount(2); v8::Local function = function_t->GetFunction(context).ToLocalChecked(); v8::Local instance = function->NewInstance(context).ToLocalChecked(); instance->SetAlignedPointerInInternalField(0, first_field); instance->SetAlignedPointerInInternalField(1, second_field); CHECK(!instance.IsEmpty()); i::Handle js_obj = v8::Utils::OpenHandle(*instance); CHECK_EQ(i::JS_API_OBJECT_TYPE, js_obj->map().instance_type()); return scope.Escape(instance); } enum class TracePrologueBehavior { kNoop, kCallV8WriteBarrier }; class TestEmbedderHeapTracer final : public v8::EmbedderHeapTracer { public: TestEmbedderHeapTracer() = default; TestEmbedderHeapTracer(TracePrologueBehavior prologue_behavior, v8::Global array) : prologue_behavior_(prologue_behavior), array_(std::move(array)) {} void RegisterV8References( const std::vector>& embedder_fields) final { registered_from_v8_.insert(registered_from_v8_.end(), embedder_fields.begin(), embedder_fields.end()); } void AddReferenceForTracing(v8::TracedGlobal* global) { to_register_with_v8_.push_back(global); } void AddReferenceForTracing(v8::TracedReference* ref) { to_register_with_v8_references_.push_back(ref); } bool AdvanceTracing(double deadline_in_ms) final { for (auto global : to_register_with_v8_) { RegisterEmbedderReference(global->As()); } to_register_with_v8_.clear(); for (auto ref : to_register_with_v8_references_) { RegisterEmbedderReference(ref->As()); } to_register_with_v8_references_.clear(); return true; } bool IsTracingDone() final { return to_register_with_v8_.empty(); } void TracePrologue(EmbedderHeapTracer::TraceFlags) final { if (prologue_behavior_ == TracePrologueBehavior::kCallV8WriteBarrier) { auto local = array_.Get(isolate()); local->Set(local->CreationContext(), 0, v8::Object::New(isolate())) .Check(); } } void TraceEpilogue(TraceSummary*) final {} void EnterFinalPause(EmbedderStackState) final {} bool IsRegisteredFromV8(void* first_field) const { for (auto pair : registered_from_v8_) { if (pair.first == first_field) return true; } return false; } void ConsiderTracedGlobalAsRoot(bool value) { consider_traced_global_as_root_ = value; } bool IsRootForNonTracingGC(const v8::TracedGlobal& handle) final { return consider_traced_global_as_root_; } private: std::vector> registered_from_v8_; std::vector*> to_register_with_v8_; std::vector*> to_register_with_v8_references_; bool consider_traced_global_as_root_ = true; TracePrologueBehavior prologue_behavior_ = TracePrologueBehavior::kNoop; v8::Global array_; }; } // namespace TEST(V8RegisteringEmbedderReference) { // Tests that wrappers are properly registered with the embedder heap // tracer. ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); void* first_field = reinterpret_cast(0x2); v8::Local api_object = ConstructTraceableJSApiObject(context, first_field, nullptr); CHECK(!api_object.IsEmpty()); CcTest::CollectGarbage(i::OLD_SPACE); CHECK(tracer.IsRegisteredFromV8(first_field)); } TEST(EmbedderRegisteringV8Reference) { // Tests that references that are registered by the embedder heap tracer are // considered live by V8. ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); v8::TracedGlobal g; { v8::HandleScope inner_scope(isolate); v8::Local o = v8::Local::New(isolate, v8::Object::New(isolate)); g.Reset(isolate, o); } tracer.AddReferenceForTracing(&g); CcTest::CollectGarbage(i::OLD_SPACE); CHECK(!g.IsEmpty()); } namespace { void ResurrectingFinalizer( const v8::WeakCallbackInfo>& data) { data.GetParameter()->ClearWeak(); } } // namespace TEST(TracingInRevivedSubgraph) { // Tests that wrappers are traced when they are contained with in a subgraph // that is revived by a finalizer. ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); v8::Global g; void* first_field = reinterpret_cast(0x4); { v8::HandleScope inner_scope(isolate); v8::Local api_object = ConstructTraceableJSApiObject(context, first_field, nullptr); CHECK(!api_object.IsEmpty()); v8::Local o = v8::Local::New(isolate, v8::Object::New(isolate)); o->Set(context, v8_str("link"), api_object).FromJust(); g.Reset(isolate, o); g.SetWeak(&g, ResurrectingFinalizer, v8::WeakCallbackType::kFinalizer); } CcTest::CollectGarbage(i::OLD_SPACE); CHECK(tracer.IsRegisteredFromV8(first_field)); } TEST(TracingInEphemerons) { // Tests that wrappers that are part of ephemerons are traced. ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); v8::Local key = v8::Local::New(isolate, v8::Object::New(isolate)); void* first_field = reinterpret_cast(0x8); i::Isolate* i_isolate = reinterpret_cast(isolate); Handle weak_map = i_isolate->factory()->NewJSWeakMap(); { v8::HandleScope inner_scope(isolate); v8::Local api_object = ConstructTraceableJSApiObject(context, first_field, nullptr); CHECK(!api_object.IsEmpty()); Handle js_key = handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate); Handle js_api_object = v8::Utils::OpenHandle(*api_object); int32_t hash = js_key->GetOrCreateHash(i_isolate).value(); JSWeakCollection::Set(weak_map, js_key, js_api_object, hash); } CcTest::CollectGarbage(i::OLD_SPACE); CHECK(tracer.IsRegisteredFromV8(first_field)); } TEST(FinalizeTracingIsNoopWhenNotMarking) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); // Finalize a potentially running garbage collection. i_isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); CHECK(i_isolate->heap()->incremental_marking()->IsStopped()); int gc_counter = i_isolate->heap()->gc_count(); tracer.FinalizeTracing(); CHECK(i_isolate->heap()->incremental_marking()->IsStopped()); CHECK_EQ(gc_counter, i_isolate->heap()->gc_count()); } TEST(FinalizeTracingWhenMarking) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); // Finalize a potentially running garbage collection. i_isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); if (i_isolate->heap()->mark_compact_collector()->sweeping_in_progress()) { i_isolate->heap()->mark_compact_collector()->EnsureSweepingCompleted(); } CHECK(i_isolate->heap()->incremental_marking()->IsStopped()); i::IncrementalMarking* marking = i_isolate->heap()->incremental_marking(); { SafepointScope scope(i_isolate->heap()); marking->Start(i::GarbageCollectionReason::kTesting); } // Sweeping is not runing so we should immediately start marking. CHECK(marking->IsMarking()); tracer.FinalizeTracing(); CHECK(marking->IsStopped()); } TEST(GarbageCollectionForTesting) { ManualGCScope manual_gc; i::FLAG_expose_gc = true; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); int saved_gc_counter = i_isolate->heap()->gc_count(); tracer.GarbageCollectionForTesting( EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers); CHECK_GT(i_isolate->heap()->gc_count(), saved_gc_counter); } namespace { void ConstructJSObject(v8::Isolate* isolate, v8::Local context, v8::TracedGlobal* global) { v8::HandleScope scope(isolate); v8::Local object(v8::Object::New(isolate)); CHECK(!object.IsEmpty()); *global = v8::TracedGlobal(isolate, object); CHECK(!global->IsEmpty()); } template void ConstructJSApiObject(v8::Isolate* isolate, v8::Local context, T* global) { v8::HandleScope scope(isolate); v8::Local object( ConstructTraceableJSApiObject(context, nullptr, nullptr)); CHECK(!object.IsEmpty()); *global = T(isolate, object); CHECK(!global->IsEmpty()); } enum class SurvivalMode { kSurvives, kDies }; template void TracedGlobalTest(v8::Isolate* isolate, ConstructTracedGlobalFunction construct_function, ModifierFunction modifier_function, void (*gc_function)(), SurvivalMode survives) { v8::HandleScope scope(isolate); v8::Local context = v8::Context::New(isolate); v8::Context::Scope context_scope(context); auto global = std::make_unique>(); construct_function(isolate, context, global.get()); CHECK(InCorrectGeneration(isolate, *global)); modifier_function(*global); gc_function(); CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !global->IsEmpty()); CHECK_IMPLIES(survives == SurvivalMode::kDies, global->IsEmpty()); } } // namespace TEST(TracedGlobalReset) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::TracedGlobal traced; ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced); CHECK(!traced.IsEmpty()); traced.Reset(); CHECK(traced.IsEmpty()); } TEST(TracedGlobalInStdVector) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); std::vector> vec; { v8::HandleScope scope(isolate); vec.emplace_back(isolate, v8::Object::New(isolate)); } CHECK(!vec[0].IsEmpty()); InvokeMarkSweep(); CHECK(vec[0].IsEmpty()); } TEST(TracedGlobalCopyWithDestructor) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); auto global1 = std::make_unique>(); { v8::HandleScope scope(isolate); global1->Reset(isolate, v8::Object::New(isolate)); } auto global2 = std::make_unique>(*global1); auto global3 = std::make_unique>(); *global3 = *global2; CHECK_EQ(initial_count + 3, global_handles->handles_count()); CHECK(!global1->IsEmpty()); CHECK_EQ(*global1, *global2); CHECK_EQ(*global2, *global3); { v8::HandleScope scope(isolate); auto tmp = v8::Local::New(isolate, *global3); CHECK(!tmp.IsEmpty()); InvokeMarkSweep(); } CHECK_EQ(initial_count + 3, global_handles->handles_count()); CHECK(!global1->IsEmpty()); CHECK_EQ(*global1, *global2); CHECK_EQ(*global2, *global3); InvokeMarkSweep(); CHECK_EQ(initial_count, global_handles->handles_count()); CHECK(global1->IsEmpty()); CHECK_EQ(*global1, *global2); CHECK_EQ(*global2, *global3); } TEST(TracedGlobalCopyNoDestructor) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); auto global1 = std::make_unique>(); { v8::HandleScope scope(isolate); global1->Reset(isolate, v8::Object::New(isolate)); } auto global2 = std::make_unique>(*global1); auto global3 = std::make_unique>(); *global3 = *global2; CHECK_EQ(initial_count + 3, global_handles->handles_count()); CHECK(!global1->IsEmpty()); CHECK_EQ(*global1, *global2); CHECK_EQ(*global2, *global3); { v8::HandleScope scope(isolate); auto tmp = v8::Local::New(isolate, *global3); CHECK(!tmp.IsEmpty()); InvokeMarkSweep(); } CHECK_EQ(initial_count + 3, global_handles->handles_count()); CHECK(!global1->IsEmpty()); CHECK_EQ(*global1, *global2); CHECK_EQ(*global2, *global3); InvokeMarkSweep(); CHECK_EQ(initial_count, global_handles->handles_count()); } TEST(TracedGlobalInStdUnorderedMap) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); std::unordered_map> map; { v8::HandleScope scope(isolate); map.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(isolate, v8::Object::New(isolate))); } CHECK(!map[1].IsEmpty()); InvokeMarkSweep(); CHECK(map[1].IsEmpty()); } TEST(TracedGlobalToUnmodifiedJSObjectDiesOnMarkSweep) { CcTest::InitializeVM(); TracedGlobalTest( CcTest::isolate(), ConstructJSObject, [](const TracedGlobal& global) {}, [] { InvokeMarkSweep(); }, SurvivalMode::kDies); } TEST(TracedGlobalToUnmodifiedJSObjectSurvivesMarkSweepWhenHeldAliveOtherwise) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::Global strong_global; TracedGlobalTest( CcTest::isolate(), ConstructJSObject, [isolate, &strong_global](const TracedGlobal& global) { v8::HandleScope scope(isolate); strong_global = v8::Global(isolate, global.Get(isolate)); }, []() { InvokeMarkSweep(); }, SurvivalMode::kSurvives); } TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavenge) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); TracedGlobalTest( CcTest::isolate(), ConstructJSObject, [](const TracedGlobal& global) {}, []() { InvokeScavenge(); }, SurvivalMode::kSurvives); } TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavengeWhenExcludedFromRoots) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); tracer.ConsiderTracedGlobalAsRoot(false); TracedGlobalTest( CcTest::isolate(), ConstructJSObject, [](const TracedGlobal& global) {}, []() { InvokeScavenge(); }, SurvivalMode::kSurvives); } TEST(TracedGlobalToUnmodifiedJSApiObjectSurvivesScavengePerDefault) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); tracer.ConsiderTracedGlobalAsRoot(true); TracedGlobalTest( CcTest::isolate(), ConstructJSApiObject>, [](const TracedGlobal& global) {}, []() { InvokeScavenge(); }, SurvivalMode::kSurvives); } TEST(TracedGlobalToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); tracer.ConsiderTracedGlobalAsRoot(false); TracedGlobalTest( CcTest::isolate(), ConstructJSApiObject>, [](const TracedGlobal& global) {}, []() { InvokeScavenge(); }, SurvivalMode::kDies); } TEST(TracedGlobalWrapperClassId) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); v8::TracedGlobal traced; ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced); CHECK_EQ(0, traced.WrapperClassId()); traced.SetWrapperClassId(17); CHECK_EQ(17, traced.WrapperClassId()); } TEST(TracedReferenceHandlesMarking) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); auto live = std::make_unique>(); auto dead = std::make_unique>(); live->Reset(isolate, v8::Undefined(isolate)); dead->Reset(isolate, v8::Undefined(isolate)); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); { TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); tracer.AddReferenceForTracing(live.get()); const size_t initial_count = global_handles->handles_count(); InvokeMarkSweep(); const size_t final_count = global_handles->handles_count(); // Handles are black allocated, so the first GC does not collect them. CHECK_EQ(initial_count, final_count); } { TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); tracer.AddReferenceForTracing(live.get()); const size_t initial_count = global_handles->handles_count(); InvokeMarkSweep(); const size_t final_count = global_handles->handles_count(); CHECK_EQ(initial_count, final_count + 1); } } TEST(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; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); auto ref = std::make_unique>(); ref->Reset(isolate, v8::Undefined(isolate)); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); // We need two GCs because handles are black allocated. InvokeMarkSweep(); InvokeMarkSweep(); const size_t final_count = global_handles->handles_count(); CHECK_EQ(initial_count, final_count + 1); } TEST(TracedGlobalHandlesAreRetained) { // TracedGlobal handles are cleared by the destructor of the embedder object. ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::TracedGlobal global; global.Reset(isolate, v8::Undefined(isolate)); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); // We need two GCs because handles are black allocated. InvokeMarkSweep(); InvokeMarkSweep(); const size_t final_count = global_handles->handles_count(); CHECK_EQ(initial_count, final_count); } namespace { class TracedGlobalVisitor final : public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor { public: ~TracedGlobalVisitor() override = default; void VisitTracedGlobalHandle(const TracedGlobal& value) final { if (value.WrapperClassId() == 57) { count_++; } } size_t count() const { return count_; } private: size_t count_ = 0; }; } // namespace TEST(TracedGlobalIteration) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); auto traced = std::make_unique>(); ConstructJSObject(isolate, isolate->GetCurrentContext(), traced.get()); CHECK(!traced->IsEmpty()); traced->SetWrapperClassId(57); TracedGlobalVisitor visitor; { v8::HandleScope scope(isolate); tracer.IterateTracedGlobalHandles(&visitor); } CHECK_EQ(1, visitor.count()); } namespace { void FinalizationCallback(const WeakCallbackInfo& data) { v8::TracedGlobal* traced = reinterpret_cast*>(data.GetParameter()); CHECK_EQ(reinterpret_cast(0x4), data.GetInternalField(0)); CHECK_EQ(reinterpret_cast(0x8), data.GetInternalField(1)); traced->Reset(); } } // namespace TEST(TracedGlobalSetFinalizationCallbackScavenge) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); TestEmbedderHeapTracer tracer; tracer.ConsiderTracedGlobalAsRoot(false); heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); auto traced = std::make_unique>(); ConstructJSApiObject(isolate, isolate->GetCurrentContext(), traced.get()); CHECK(!traced->IsEmpty()); { v8::HandleScope scope(isolate); auto local = traced->Get(isolate); local->SetAlignedPointerInInternalField(0, reinterpret_cast(0x4)); local->SetAlignedPointerInInternalField(1, reinterpret_cast(0x8)); } traced->SetFinalizationCallback(traced.get(), FinalizationCallback); heap::InvokeScavenge(); CHECK(traced->IsEmpty()); } TEST(TracedGlobalSetFinalizationCallbackMarkSweep) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); auto traced = std::make_unique>(); ConstructJSApiObject(isolate, isolate->GetCurrentContext(), traced.get()); CHECK(!traced->IsEmpty()); { v8::HandleScope scope(isolate); auto local = traced->Get(isolate); local->SetAlignedPointerInInternalField(0, reinterpret_cast(0x4)); local->SetAlignedPointerInInternalField(1, reinterpret_cast(0x8)); } traced->SetFinalizationCallback(traced.get(), FinalizationCallback); heap::InvokeMarkSweep(); CHECK(traced->IsEmpty()); } TEST(TracePrologueCallingIntoV8WriteBarrier) { // Regression test: https://crbug.com/940003 ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Global global; { v8::HandleScope scope(isolate); auto local = v8::Array::New(isolate, 10); global.Reset(isolate, local); } TestEmbedderHeapTracer tracer(TracePrologueBehavior::kCallV8WriteBarrier, std::move(global)); heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); SimulateIncrementalMarking(CcTest::i_isolate()->heap()); // Finish GC to avoid removing the tracer while GC is running which may end up // in an infinite loop because of unprocessed objects. heap::InvokeMarkSweep(); } TEST(TracedGlobalWithDestructor) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); auto* traced = new v8::TracedGlobal(); { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); CHECK(traced->IsEmpty()); *traced = v8::TracedGlobal(isolate, object); CHECK(!traced->IsEmpty()); CHECK_EQ(initial_count + 1, global_handles->handles_count()); } delete traced; CHECK_EQ(initial_count, global_handles->handles_count()); // GC should not need to clear the handle. heap::InvokeMarkSweep(); CHECK_EQ(initial_count, global_handles->handles_count()); } TEST(TracedGlobalNoDestructor) { ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); char* memory = new char[sizeof(v8::TracedReference)]; auto* traced = new (memory) v8::TracedReference(); { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); CHECK(traced->IsEmpty()); *traced = v8::TracedReference(isolate, object); CHECK(!traced->IsEmpty()); CHECK_EQ(initial_count + 1, global_handles->handles_count()); } traced->~TracedReference(); CHECK_EQ(initial_count + 1, global_handles->handles_count()); // GC should clear the handle. heap::InvokeMarkSweep(); CHECK_EQ(initial_count, global_handles->handles_count()); delete[] memory; } namespace { class EmptyEmbedderHeapTracer : public v8::EmbedderHeapTracer { public: void RegisterV8References( const std::vector>& embedder_fields) final {} bool AdvanceTracing(double deadline_in_ms) final { return true; } bool IsTracingDone() final { return true; } void TracePrologue(EmbedderHeapTracer::TraceFlags) final {} void TraceEpilogue(TraceSummary*) final {} void EnterFinalPause(EmbedderStackState) final {} }; // EmbedderHeapTracer that can optimize Scavenger handling when used with // TraceGlobal handles that have destructors. class EmbedderHeapTracerDestructorNonTracingClearing final : public EmptyEmbedderHeapTracer { public: explicit EmbedderHeapTracerDestructorNonTracingClearing( uint16_t class_id_to_optimize) : class_id_to_optimize_(class_id_to_optimize) {} bool IsRootForNonTracingGC(const v8::TracedGlobal& handle) final { return handle.WrapperClassId() != class_id_to_optimize_; } private: uint16_t class_id_to_optimize_; }; // EmbedderHeapTracer that can optimize Scavenger handling when used with // TraceGlobal handles without destructors. class EmbedderHeapTracerNoDestructorNonTracingClearing final : public EmptyEmbedderHeapTracer { public: explicit EmbedderHeapTracerNoDestructorNonTracingClearing( uint16_t class_id_to_optimize) : class_id_to_optimize_(class_id_to_optimize) {} bool IsRootForNonTracingGC( const v8::TracedReference& handle) final { return handle.WrapperClassId() != class_id_to_optimize_; } void ResetHandleInNonTracingGC( const v8::TracedReference& handle) final { if (handle.WrapperClassId() != class_id_to_optimize_) return; // Convention (for test): Objects that are optimized have their first field // set as a back pointer. BasicTracedReference* original_handle = reinterpret_cast*>( v8::Object::GetAlignedPointerFromInternalField( handle.As(), 0)); original_handle->Reset(); } private: uint16_t class_id_to_optimize_; }; template void SetupOptimizedAndNonOptimizedHandle(v8::Isolate* isolate, uint16_t optimized_class_id, T* optimized_handle, T* non_optimized_handle) { v8::HandleScope scope(isolate); v8::Local optimized_object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), optimized_handle, nullptr)); CHECK(optimized_handle->IsEmpty()); *optimized_handle = T(isolate, optimized_object); CHECK(!optimized_handle->IsEmpty()); optimized_handle->SetWrapperClassId(optimized_class_id); v8::Local non_optimized_object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); CHECK(non_optimized_handle->IsEmpty()); *non_optimized_handle = T(isolate, non_optimized_object); CHECK(!non_optimized_handle->IsEmpty()); } } // namespace TEST(TracedGlobalDestructorReclaimedOnScavenge) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); constexpr uint16_t kClassIdToOptimize = 17; EmbedderHeapTracerDestructorNonTracingClearing tracer(kClassIdToOptimize); heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); auto* optimized_handle = new v8::TracedGlobal(); auto* non_optimized_handle = new v8::TracedGlobal(); SetupOptimizedAndNonOptimizedHandle(isolate, kClassIdToOptimize, optimized_handle, non_optimized_handle); CHECK_EQ(initial_count + 2, global_handles->handles_count()); heap::InvokeScavenge(); CHECK_EQ(initial_count + 1, global_handles->handles_count()); CHECK(optimized_handle->IsEmpty()); delete optimized_handle; CHECK(!non_optimized_handle->IsEmpty()); delete non_optimized_handle; CHECK_EQ(initial_count, global_handles->handles_count()); } TEST(TracedGlobalNoDestructorReclaimedOnScavenge) { if (FLAG_single_generation) return; ManualGCScope manual_gc; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); constexpr uint16_t kClassIdToOptimize = 23; EmbedderHeapTracerNoDestructorNonTracingClearing tracer(kClassIdToOptimize); heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer); i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles(); const size_t initial_count = global_handles->handles_count(); auto* optimized_handle = new v8::TracedReference(); auto* non_optimized_handle = new v8::TracedReference(); SetupOptimizedAndNonOptimizedHandle(isolate, kClassIdToOptimize, optimized_handle, non_optimized_handle); CHECK_EQ(initial_count + 2, global_handles->handles_count()); heap::InvokeScavenge(); CHECK_EQ(initial_count + 1, global_handles->handles_count()); CHECK(optimized_handle->IsEmpty()); delete optimized_handle; CHECK(!non_optimized_handle->IsEmpty()); non_optimized_handle->Reset(); delete non_optimized_handle; CHECK_EQ(initial_count, global_handles->handles_count()); } namespace { template V8_NOINLINE void OnStackTest(TestEmbedderHeapTracer* tracer) { v8::Isolate* isolate = CcTest::isolate(); v8::Global observer; T stack_ref; { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); stack_ref.Reset(isolate, object); observer.Reset(isolate, object); observer.SetWeak(); } CHECK(!observer.IsEmpty()); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); } V8_NOINLINE void CreateTracedReferenceInDeepStack( v8::Isolate* isolate, v8::Global* observer) { v8::TracedReference stack_ref; v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); stack_ref.Reset(isolate, object); observer->Reset(isolate, object); observer->SetWeak(); } V8_NOINLINE void TracedReferenceNotifyEmptyStackTest( TestEmbedderHeapTracer* tracer) { v8::Isolate* isolate = CcTest::isolate(); v8::Global observer; CreateTracedReferenceInDeepStack(isolate, &observer); CHECK(!observer.IsEmpty()); tracer->NotifyEmptyEmbedderStack(); heap::InvokeMarkSweep(); CHECK(observer.IsEmpty()); } enum class Operation { kCopy, kMove, }; template void PerformOperation(Operation op, T* lhs, T* rhs) { switch (op) { case Operation::kMove: *lhs = std::move(*rhs); break; case Operation::kCopy: *lhs = *rhs; rhs->Reset(); break; } } enum class TargetHandling { kNonInitialized, kInitializedYoungGen, kInitializedOldGen }; template V8_NOINLINE void StackToHeapTest(TestEmbedderHeapTracer* tracer, Operation op, TargetHandling target_handling) { v8::Isolate* isolate = CcTest::isolate(); v8::Global observer; T stack_handle; T* heap_handle = new T(); if (target_handling != TargetHandling::kNonInitialized) { v8::HandleScope scope(isolate); v8::Local to_object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object))); if (!FLAG_single_generation && target_handling == TargetHandling::kInitializedOldGen) { heap::InvokeScavenge(); heap::InvokeScavenge(); CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object))); } heap_handle->Reset(isolate, to_object); } { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); stack_handle.Reset(isolate, object); observer.Reset(isolate, object); observer.SetWeak(); } CHECK(!observer.IsEmpty()); tracer->AddReferenceForTracing(heap_handle); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); tracer->AddReferenceForTracing(heap_handle); PerformOperation(op, heap_handle, &stack_handle); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); heap::InvokeMarkSweep(); CHECK(observer.IsEmpty()); delete heap_handle; } template V8_NOINLINE void HeapToStackTest(TestEmbedderHeapTracer* tracer, Operation op, TargetHandling target_handling) { v8::Isolate* isolate = CcTest::isolate(); v8::Global observer; T stack_handle; T* heap_handle = new T(); if (target_handling != TargetHandling::kNonInitialized) { v8::HandleScope scope(isolate); v8::Local to_object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object))); if (!FLAG_single_generation && target_handling == TargetHandling::kInitializedOldGen) { heap::InvokeScavenge(); heap::InvokeScavenge(); CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object))); } stack_handle.Reset(isolate, to_object); } { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); heap_handle->Reset(isolate, object); observer.Reset(isolate, object); observer.SetWeak(); } CHECK(!observer.IsEmpty()); tracer->AddReferenceForTracing(heap_handle); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); PerformOperation(op, &stack_handle, heap_handle); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); stack_handle.Reset(); heap::InvokeMarkSweep(); CHECK(observer.IsEmpty()); delete heap_handle; } template V8_NOINLINE void StackToStackTest(TestEmbedderHeapTracer* tracer, Operation op, TargetHandling target_handling) { v8::Isolate* isolate = CcTest::isolate(); v8::Global observer; T stack_handle1; T stack_handle2; if (target_handling != TargetHandling::kNonInitialized) { v8::HandleScope scope(isolate); v8::Local to_object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object))); if (!FLAG_single_generation && target_handling == TargetHandling::kInitializedOldGen) { heap::InvokeScavenge(); heap::InvokeScavenge(); CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object))); } stack_handle2.Reset(isolate, to_object); } { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); stack_handle1.Reset(isolate, object); observer.Reset(isolate, object); observer.SetWeak(); } CHECK(!observer.IsEmpty()); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); PerformOperation(op, &stack_handle2, &stack_handle1); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); stack_handle2.Reset(); heap::InvokeMarkSweep(); CHECK(observer.IsEmpty()); } template V8_NOINLINE void TracedReferenceCleanedTest(TestEmbedderHeapTracer* tracer) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); const size_t before = CcTest::i_isolate()->global_handles()->NumberOfOnStackHandlesForTesting(); for (int i = 0; i < 100; i++) { T stack_handle; stack_handle.Reset(isolate, object); } CHECK_EQ(before + 1, CcTest::i_isolate() ->global_handles() ->NumberOfOnStackHandlesForTesting()); } V8_NOINLINE void TracedGlobalDestructorTest(TestEmbedderHeapTracer* tracer) { v8::Isolate* isolate = CcTest::isolate(); v8::Global observer; { v8::TracedGlobal stack_handle; { v8::HandleScope scope(isolate); v8::Local object(ConstructTraceableJSApiObject( isolate->GetCurrentContext(), nullptr, nullptr)); stack_handle.Reset(isolate, object); observer.Reset(isolate, object); observer.SetWeak(); } CHECK(!observer.IsEmpty()); heap::InvokeMarkSweep(); CHECK(!observer.IsEmpty()); } heap::InvokeMarkSweep(); CHECK(observer.IsEmpty()); } } // namespace TEST(TracedReferenceOnStack) { ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); OnStackTest>(&tracer); } TEST(TracedGlobalOnStack) { ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); OnStackTest>(&tracer); } TEST(TracedReferenceCleaned) { ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); TracedReferenceCleanedTest>(&tracer); } TEST(TracedGlobalCleaned) { ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); TracedReferenceCleanedTest>(&tracer); } TEST(TracedReferenceMove) { using ReferenceType = v8::TracedReference; ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); StackToHeapTest(&tracer, Operation::kMove, TargetHandling::kNonInitialized); StackToHeapTest(&tracer, Operation::kMove, TargetHandling::kInitializedYoungGen); StackToHeapTest(&tracer, Operation::kMove, TargetHandling::kInitializedOldGen); HeapToStackTest(&tracer, Operation::kMove, TargetHandling::kNonInitialized); HeapToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedYoungGen); HeapToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedOldGen); StackToStackTest(&tracer, Operation::kMove, TargetHandling::kNonInitialized); StackToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedYoungGen); StackToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedOldGen); } TEST(TracedReferenceCopy) { using ReferenceType = v8::TracedReference; ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); StackToHeapTest(&tracer, Operation::kCopy, TargetHandling::kNonInitialized); StackToHeapTest(&tracer, Operation::kCopy, TargetHandling::kInitializedYoungGen); StackToHeapTest(&tracer, Operation::kCopy, TargetHandling::kInitializedOldGen); HeapToStackTest(&tracer, Operation::kCopy, TargetHandling::kNonInitialized); HeapToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedYoungGen); HeapToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedOldGen); StackToStackTest(&tracer, Operation::kCopy, TargetHandling::kNonInitialized); StackToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedYoungGen); StackToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedOldGen); } TEST(TracedGlobalMove) { using ReferenceType = v8::TracedGlobal; ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); StackToHeapTest(&tracer, Operation::kMove, TargetHandling::kNonInitialized); StackToHeapTest(&tracer, Operation::kMove, TargetHandling::kInitializedYoungGen); StackToHeapTest(&tracer, Operation::kMove, TargetHandling::kInitializedOldGen); HeapToStackTest(&tracer, Operation::kMove, TargetHandling::kNonInitialized); HeapToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedYoungGen); HeapToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedOldGen); StackToStackTest(&tracer, Operation::kMove, TargetHandling::kNonInitialized); StackToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedYoungGen); StackToStackTest(&tracer, Operation::kMove, TargetHandling::kInitializedOldGen); } TEST(TracedGlobalCopy) { using ReferenceType = v8::TracedGlobal; ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); StackToHeapTest(&tracer, Operation::kCopy, TargetHandling::kNonInitialized); StackToHeapTest(&tracer, Operation::kCopy, TargetHandling::kInitializedYoungGen); StackToHeapTest(&tracer, Operation::kCopy, TargetHandling::kInitializedOldGen); HeapToStackTest(&tracer, Operation::kCopy, TargetHandling::kNonInitialized); HeapToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedYoungGen); HeapToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedOldGen); StackToStackTest(&tracer, Operation::kCopy, TargetHandling::kNonInitialized); StackToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedYoungGen); StackToStackTest(&tracer, Operation::kCopy, TargetHandling::kInitializedOldGen); } TEST(TracedGlobalDestructor) { ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); TracedGlobalDestructorTest(&tracer); } TEST(NotifyEmptyStack) { ManualGCScope manual_gc; CcTest::InitializeVM(); TestEmbedderHeapTracer tracer; heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(), &tracer); tracer.SetStackStart(&manual_gc); TracedReferenceNotifyEmptyStackTest(&tracer); } } // namespace heap } // namespace internal } // namespace v8