node/deps/v8/test/unittests/heap/cppgc-js/embedder-roots-handler-unittest.cc
Michaël Zasso 9d7cd9b864
deps: update V8 to 12.8.374.13
PR-URL: https://github.com/nodejs/node/pull/54077
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
2024-08-16 16:03:01 +02:00

210 lines
8.0 KiB
C++

// 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:
START_ALLOW_USE_DEPRECATED()
explicit ClearingEmbedderRootsHandler(v8::Isolate* isolate)
: EmbedderRootsHandler(EmbedderRootsHandler::RootHandling::
kDontQueryEmbedderForAnyReference),
isolate_(isolate) {}
END_ALLOW_USE_DEPRECATED()
bool IsRoot(const v8::TracedReference<v8::Value>& handle) final {
// Every handle that's droppable will not be considered as root and thus
// dropped.
return false;
}
void ResetRoot(const v8::TracedReference<v8::Value>& handle) final {
CHECK(!IsRoot(handle));
// Convention for test: Objects that are optimized have use a back pointer
// in the wrappable field.
BasicTracedReference<v8::Value>* original_handle =
reinterpret_cast<BasicTracedReference<v8::Value>*>(
v8::Object::Unwrap<CppHeapPointerTag::kDefaultTag>(
isolate_, handle.As<v8::Object>()));
original_handle->Reset();
}
private:
v8::Isolate* const isolate_;
};
void ConstructNonDroppableJSObject(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::TracedReference<v8::Object>* handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(v8::Object::New(isolate));
EXPECT_FALSE(object.IsEmpty());
*handle = v8::TracedReference<v8::Object>(isolate, object);
EXPECT_FALSE(handle->IsEmpty());
}
void ConstructNonDroppableJSApiObject(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::TracedReference<v8::Object>* handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(context, nullptr);
EXPECT_FALSE(object.IsEmpty());
*handle = v8::TracedReference<v8::Object>(isolate, object);
EXPECT_FALSE(handle->IsEmpty());
}
void ConstructDroppableJSApiObject(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::TracedReference<v8::Object>* handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(context, handle);
EXPECT_FALSE(object.IsEmpty());
*handle = v8::TracedReference<v8::Object>(
isolate, object, typename v8::TracedReference<v8::Object>::IsDroppable{});
EXPECT_FALSE(handle->IsEmpty());
}
} // namespace
namespace {
enum class SurvivalMode { kSurvives, kDies };
template <typename ModifierFunction, typename ConstructTracedReferenceFunction,
typename GCFunction>
void TracedReferenceTest(v8::Isolate* isolate,
ConstructTracedReferenceFunction construct_function,
ModifierFunction modifier_function,
GCFunction gc_function, SurvivalMode survives) {
auto i_isolate = reinterpret_cast<i::Isolate*>(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();
auto gc_invisible_handle =
std::make_unique<v8::TracedReference<v8::Object>>();
construct_function(isolate, isolate->GetCurrentContext(),
gc_invisible_handle.get());
ASSERT_TRUE(IsNewObjectInCorrectGeneration(isolate, *gc_invisible_handle));
modifier_function(*gc_invisible_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<v8::Object>&) {}, [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<v8::Object> strong_global;
TracedReferenceTest(
v8_isolate(), ConstructNonDroppableJSObject,
[this, &strong_global](const TracedReference<v8::Object>& handle) {
v8::HandleScope scope(v8_isolate());
strong_global =
v8::Global<v8::Object>(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<v8::Object>&) {}, [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<v8::Object>&) {}, [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<v8::Object>& handle) {}, [this]() { InvokeMinorGC(); },
SurvivalMode::kDies);
}
} // namespace v8::internal