// Copyright 2022 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/v8-embedder-heap.h" #include "include/v8-traced-handle.h" #include "src/handles/handles.h" #include "src/handles/traced-handles.h" #include "test/unittests/heap/cppgc-js/unified-heap-utils.h" #include "test/unittests/heap/heap-utils.h" namespace v8::internal { namespace { using EmbedderRootsHandlerTest = TestWithHeapInternalsAndContext; class V8_NODISCARD TemporaryEmbedderRootsHandleScope final { public: TemporaryEmbedderRootsHandleScope(v8::Isolate* isolate, v8::EmbedderRootsHandler* handler) : isolate_(isolate) { isolate_->SetEmbedderRootsHandler(handler); } ~TemporaryEmbedderRootsHandleScope() { isolate_->SetEmbedderRootsHandler(nullptr); } private: v8::Isolate* const isolate_; }; // EmbedderRootsHandler that can optimize Scavenger handling when used with // TracedReference. class ClearingEmbedderRootsHandler final : public v8::EmbedderRootsHandler { public: explicit ClearingEmbedderRootsHandler(v8::Isolate* isolate) : EmbedderRootsHandler(), isolate_(isolate) {} void ResetRoot(const v8::TracedReference& handle) final { // Convention for test: Objects that are optimized have use a back pointer // in the wrappable field. BasicTracedReference* original_handle = reinterpret_cast*>( v8::Object::Unwrap( isolate_, handle.As())); original_handle->Reset(); } private: v8::Isolate* const isolate_; }; void ConstructNonDroppableJSObject(v8::Isolate* isolate, v8::Local context, v8::TracedReference* handle) { v8::HandleScope scope(isolate); v8::Local object(v8::Object::New(isolate)); EXPECT_FALSE(object.IsEmpty()); *handle = v8::TracedReference(isolate, object); EXPECT_FALSE(handle->IsEmpty()); } void ConstructNonDroppableJSApiObject(v8::Isolate* isolate, v8::Local context, v8::TracedReference* handle) { v8::HandleScope scope(isolate); v8::Local object = WrapperHelper::CreateWrapper(context, nullptr); EXPECT_FALSE(object.IsEmpty()); *handle = v8::TracedReference(isolate, object); EXPECT_FALSE(handle->IsEmpty()); } void ConstructDroppableJSApiObject(v8::Isolate* isolate, v8::Local context, v8::TracedReference* handle) { v8::HandleScope scope(isolate); v8::Local object = WrapperHelper::CreateWrapper(context, handle); EXPECT_FALSE(object.IsEmpty()); *handle = v8::TracedReference( isolate, object, typename v8::TracedReference::IsDroppable{}); EXPECT_FALSE(handle->IsEmpty()); } } // namespace namespace { enum class SurvivalMode { kSurvives, kDies }; template void TracedReferenceTest(v8::Isolate* isolate, ConstructTracedReferenceFunction construct_function, ModifierFunction modifier_function, GCFunction gc_function, SurvivalMode survives) { auto i_isolate = reinterpret_cast(isolate); ManualGCScope manual_gc_scope(i_isolate); DisableConservativeStackScanningScopeForTesting no_stack_scanning( i_isolate->heap()); v8::HandleScope scope(isolate); auto* traced_handles = i_isolate->traced_handles(); const size_t initial_count = traced_handles->used_node_count(); // Store v8::TracedReference on the stack here on purpose. On Android storing // it on the heap is problematic. This is because the native memory allocator // on Android sets the top-byte of allocations for verification. However, in // same tests we store the address of the v8::TracedReference in the // CppHeapPointerTable to simulate a cppgc wrapper object. The table expectes // the hightest 16-bit to be 0 for all entries. v8::TracedReference handle; construct_function(isolate, isolate->GetCurrentContext(), &handle); ASSERT_TRUE(IsNewObjectInCorrectGeneration(isolate, handle)); modifier_function(handle); const size_t after_modification_count = traced_handles->used_node_count(); gc_function(); // Cannot check the handle as it is not explicitly cleared by the GC. Instead // check the handles count. CHECK_IMPLIES(survives == SurvivalMode::kSurvives, after_modification_count == traced_handles->used_node_count()); CHECK_IMPLIES(survives == SurvivalMode::kDies, initial_count == traced_handles->used_node_count()); } } // namespace TEST_F(EmbedderRootsHandlerTest, FullGC_UnreachableTracedReferenceToNonDroppableDies) { if (v8_flags.stress_incremental_marking) GTEST_SKIP() << "When stressing incremental marking, a write barrier may " "keep the object alive."; ClearingEmbedderRootsHandler handler(v8_isolate()); TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler); TracedReferenceTest( v8_isolate(), ConstructNonDroppableJSObject, [](const TracedReference&) {}, [this]() { InvokeMajorGC(); }, SurvivalMode::kDies); } TEST_F(EmbedderRootsHandlerTest, FullGC_UnreachableTracedReferenceToNonDroppableDies2) { ManualGCScope manual_gcs(i_isolate()); ClearingEmbedderRootsHandler handler(v8_isolate()); TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler); // The TracedReference itself will die as it's not found by the full GC. The // pointee will be kept alive through other means. v8::Global strong_global; TracedReferenceTest( v8_isolate(), ConstructNonDroppableJSObject, [this, &strong_global](const TracedReference& handle) { v8::HandleScope scope(v8_isolate()); strong_global = v8::Global(v8_isolate(), handle.Get(v8_isolate())); }, [this, &strong_global]() { InvokeMajorGC(); strong_global.Reset(); }, SurvivalMode::kDies); } TEST_F(EmbedderRootsHandlerTest, YoungGC_UnreachableTracedReferenceToNonDroppableSurvives) { if (v8_flags.single_generation) GTEST_SKIP(); ManualGCScope manual_gc(i_isolate()); ClearingEmbedderRootsHandler handler(v8_isolate()); TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler); TracedReferenceTest( v8_isolate(), ConstructNonDroppableJSObject, [](const TracedReference&) {}, [this]() { InvokeMinorGC(); }, SurvivalMode::kSurvives); } TEST_F(EmbedderRootsHandlerTest, YoungGC_UnreachableTracedReferenceToNonDroppableAPIObjectSurvives) { if (v8_flags.single_generation) GTEST_SKIP(); ManualGCScope manual_gc(i_isolate()); ClearingEmbedderRootsHandler handler(v8_isolate()); TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler); TracedReferenceTest( v8_isolate(), ConstructNonDroppableJSApiObject, [](const TracedReference&) {}, [this]() { InvokeMinorGC(); }, SurvivalMode::kSurvives); } TEST_F(EmbedderRootsHandlerTest, YoungGC_UnreachableTracedReferenceToDroppableDies) { if (v8_flags.single_generation || !v8_flags.reclaim_unmodified_wrappers) GTEST_SKIP(); ManualGCScope manual_gc(i_isolate()); ClearingEmbedderRootsHandler handler(v8_isolate()); TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler); TracedReferenceTest( v8_isolate(), ConstructDroppableJSApiObject, [](TracedReference& handle) {}, [this]() { InvokeMinorGC(); }, SurvivalMode::kDies); } } // namespace v8::internal