Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,203 @@
// 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<v8::Value>& handle) final {
// 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);
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<v8::Object> 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<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

View File

@ -0,0 +1,369 @@
// Copyright 2016 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-cppgc.h"
#include "include/v8-traced-handle.h"
#include "src/api/api-inl.h"
#include "src/handles/global-handles.h"
#include "src/heap/cppgc/visitor.h"
#include "src/heap/marking-state-inl.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using TracedReferenceTest = TestWithHeapInternals;
TEST_F(TracedReferenceTest, ResetFromLocal) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
v8::TracedReference<v8::Object> ref;
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
ASSERT_TRUE(ref.IsEmpty());
EXPECT_NE(ref, local);
ref.Reset(v8_isolate(), local);
EXPECT_FALSE(ref.IsEmpty());
EXPECT_EQ(ref, local);
}
}
TEST_F(TracedReferenceTest, ConstructFromLocal) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref(v8_isolate(), local);
EXPECT_FALSE(ref.IsEmpty());
EXPECT_EQ(ref, local);
}
}
TEST_F(TracedReferenceTest, Reset) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref(v8_isolate(), local);
EXPECT_FALSE(ref.IsEmpty());
EXPECT_EQ(ref, local);
ref.Reset();
EXPECT_TRUE(ref.IsEmpty());
EXPECT_NE(ref, local);
}
}
TEST_F(TracedReferenceTest, Copy) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref(v8_isolate(), local);
v8::TracedReference<v8::Object> ref_copy1(ref);
v8::TracedReference<v8::Object> ref_copy2 = ref;
EXPECT_EQ(ref, local);
EXPECT_EQ(ref_copy1, local);
EXPECT_EQ(ref_copy2, local);
}
}
TEST_F(TracedReferenceTest, CopyHeterogenous) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref(v8_isolate(), local);
v8::TracedReference<v8::Value> ref_copy1(ref);
v8::TracedReference<v8::Value> ref_copy2 = ref;
EXPECT_EQ(ref, local);
EXPECT_EQ(ref_copy1, local);
EXPECT_EQ(ref_copy2, local);
}
}
TEST_F(TracedReferenceTest, Move) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref(v8_isolate(), local);
v8::TracedReference<v8::Object> ref_moved1(std::move(ref));
v8::TracedReference<v8::Object> ref_moved2 = std::move(ref_moved1);
EXPECT_TRUE(ref.IsEmpty());
EXPECT_TRUE(ref_moved1.IsEmpty());
EXPECT_EQ(ref_moved2, local);
}
}
TEST_F(TracedReferenceTest, MoveHeterogenous) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref1(v8_isolate(), local);
v8::TracedReference<v8::Value> ref_moved1(std::move(ref1));
v8::TracedReference<v8::Object> ref2(v8_isolate(), local);
v8::TracedReference<v8::Object> ref_moved2 = std::move(ref2);
EXPECT_TRUE(ref1.IsEmpty());
EXPECT_EQ(ref_moved1, local);
EXPECT_TRUE(ref2.IsEmpty());
EXPECT_EQ(ref_moved2, local);
}
}
TEST_F(TracedReferenceTest, Equality) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local1 =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref1(v8_isolate(), local1);
v8::TracedReference<v8::Object> ref2(v8_isolate(), local1);
EXPECT_EQ(ref1, ref2);
EXPECT_EQ(ref2, ref1);
v8::Local<v8::Object> local2 =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref3(v8_isolate(), local2);
EXPECT_NE(ref2, ref3);
EXPECT_NE(ref3, ref2);
}
}
TEST_F(TracedReferenceTest, EqualityHeterogenous) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local1 =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref1(v8_isolate(), local1);
v8::TracedReference<v8::Value> ref2(v8_isolate(), local1);
EXPECT_EQ(ref1, ref2);
EXPECT_EQ(ref2, ref1);
v8::Local<v8::Object> local2 =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref3(v8_isolate(), local2);
EXPECT_NE(ref2, ref3);
EXPECT_NE(ref3, ref2);
}
}
namespace {
// Must be used on stack.
class JSVisitorForTesting final : public JSVisitor {
public:
explicit JSVisitorForTesting(v8::Local<v8::Object> expected_object)
: JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
expected_object_(expected_object) {}
void Visit(const TracedReferenceBase& ref) final {
EXPECT_EQ(ref, expected_object_);
visit_count_++;
}
size_t visit_count() const { return visit_count_; }
private:
v8::Local<v8::Object> expected_object_;
size_t visit_count_ = 0;
};
} // namespace
TEST_F(TracedReferenceTest, TracedReferenceTrace) {
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> js_member(v8_isolate(), local);
JSVisitorForTesting visitor(local);
// Cast to cppgc::Visitor to ensure that we dispatch through the base
// visitor and use traits.
static_cast<cppgc::Visitor&>(visitor).Trace(js_member);
EXPECT_EQ(1u, visitor.visit_count());
}
}
TEST_F(TracedReferenceTest, NoWriteBarrierOnConstruction) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
auto ref =
std::make_unique<v8::TracedReference<v8::Object>>(v8_isolate(), local);
USE(ref);
EXPECT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
TEST_F(TracedReferenceTest, WriteBarrierForOnHeapReset) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
auto ref = std::make_unique<v8::TracedReference<v8::Object>>();
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
ref->Reset(v8_isolate(), local);
EXPECT_FALSE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
TEST_F(TracedReferenceTest, WriteBarrierForOnStackReset) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
v8::TracedReference<v8::Object> ref;
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
ref.Reset(v8_isolate(), local);
EXPECT_FALSE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
TEST_F(TracedReferenceTest, WriteBarrierOnHeapCopy) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
auto ref_from =
std::make_unique<v8::TracedReference<v8::Object>>(v8_isolate(), local);
auto ref_to = std::make_unique<v8::TracedReference<v8::Object>>();
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
*ref_to = *ref_from;
EXPECT_TRUE(!ref_from->IsEmpty());
EXPECT_FALSE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
TEST_F(TracedReferenceTest, WriteBarrierForOnStackCopy) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
auto ref_from =
std::make_unique<v8::TracedReference<v8::Object>>(v8_isolate(), local);
v8::TracedReference<v8::Object> ref_to;
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
ref_to = *ref_from;
EXPECT_TRUE(!ref_from->IsEmpty());
EXPECT_FALSE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
TEST_F(TracedReferenceTest, WriteBarrierForOnHeapMove) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
auto ref_from =
std::make_unique<v8::TracedReference<v8::Object>>(v8_isolate(), local);
auto ref_to = std::make_unique<v8::TracedReference<v8::Object>>();
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
*ref_to = std::move(*ref_from);
ASSERT_TRUE(ref_from->IsEmpty());
EXPECT_FALSE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
TEST_F(TracedReferenceTest, WriteBarrierForOnStackMove) {
if (!v8_flags.incremental_marking)
GTEST_SKIP() << "Write barrier tests require incremental marking";
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope handles(v8_isolate());
v8::Local<v8::Object> local =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
auto ref_from =
std::make_unique<v8::TracedReference<v8::Object>>(v8_isolate(), local);
v8::TracedReference<v8::Object> ref_to;
SimulateIncrementalMarking();
MarkingState state(i_isolate());
ASSERT_TRUE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
ref_to = std::move(*ref_from);
ASSERT_TRUE(ref_from->IsEmpty());
EXPECT_FALSE(
state.IsUnmarked(Cast<HeapObject>(*Utils::OpenDirectHandle(*local))));
}
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,914 @@
// 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 <cstring>
#include "include/cppgc/allocation.h"
#include "include/cppgc/common.h"
#include "include/cppgc/cross-thread-persistent.h"
#include "include/cppgc/custom-space.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/member.h"
#include "include/cppgc/name-provider.h"
#include "include/cppgc/persistent.h"
#include "include/v8-cppgc.h"
#include "include/v8-profiler.h"
#include "src/api/api-inl.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/object-allocator.h"
#include "src/objects/heap-object.h"
#include "src/objects/objects-inl.h"
#include "src/profiler/heap-snapshot-generator-inl.h"
#include "src/profiler/heap-snapshot-generator.h"
#include "test/unittests/heap/cppgc-js/unified-heap-utils.h"
#include "test/unittests/heap/heap-utils.h"
namespace cppgc {
class CompactableCustomSpace : public CustomSpace<CompactableCustomSpace> {
public:
static constexpr size_t kSpaceIndex = 0;
static constexpr bool kSupportsCompaction = true;
};
} // namespace cppgc
namespace v8::internal {
struct CompactableGCed : public cppgc::GarbageCollected<CompactableGCed>,
public cppgc::NameProvider {
public:
static constexpr const char kExpectedName[] = "CompactableGCed";
void Trace(cppgc::Visitor* v) const {}
const char* GetHumanReadableName() const final { return "CompactableGCed"; }
size_t data = 0;
};
struct CompactableHolder : public cppgc::GarbageCollected<CompactableHolder> {
public:
explicit CompactableHolder(cppgc::AllocationHandle& allocation_handle) {
object = cppgc::MakeGarbageCollected<CompactableGCed>(allocation_handle);
}
void Trace(cppgc::Visitor* visitor) const {
visitor->Trace(object);
visitor->RegisterMovableReference(object.GetSlotForTesting());
}
cppgc::subtle::UncompressedMember<CompactableGCed> object = nullptr;
};
} // namespace v8::internal
namespace cppgc {
template <>
struct SpaceTrait<v8::internal::CompactableGCed> {
using Space = CompactableCustomSpace;
};
} // namespace cppgc
namespace v8 {
namespace internal {
namespace {
template <typename TMixin>
class WithUnifiedHeapSnapshot : public TMixin {
public:
const v8::HeapSnapshot* TakeHeapSnapshot(
cppgc::EmbedderStackState stack_state =
cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode snapshot_mode =
v8::HeapProfiler::HeapSnapshotMode::kExposeInternals) {
v8::HeapProfiler* heap_profiler = TMixin::v8_isolate()->GetHeapProfiler();
v8::HeapProfiler::HeapSnapshotOptions options;
options.control = nullptr;
options.global_object_name_resolver = nullptr;
options.snapshot_mode = snapshot_mode;
options.numerics_mode = v8::HeapProfiler::NumericsMode::kHideNumericValues;
options.stack_state = stack_state;
return heap_profiler->TakeHeapSnapshot(options);
}
protected:
void TestMergedWrapperNode(v8::HeapProfiler::HeapSnapshotMode snapshot_mode);
};
using UnifiedHeapSnapshotTest = WithUnifiedHeapSnapshot<UnifiedHeapTest>;
bool IsValidSnapshot(const v8::HeapSnapshot* snapshot, int depth = 3) {
const HeapSnapshot* heap_snapshot =
reinterpret_cast<const HeapSnapshot*>(snapshot);
std::unordered_set<const HeapEntry*> visited;
for (const HeapGraphEdge& edge : heap_snapshot->edges()) {
visited.insert(edge.to());
}
size_t unretained_entries_count = 0;
for (const HeapEntry& entry : heap_snapshot->entries()) {
if (visited.find(&entry) == visited.end() && entry.id() != 1) {
entry.Print("entry with no retainer", "", depth, 0);
++unretained_entries_count;
}
}
return unretained_entries_count == 0;
}
// Returns the IDs of all entries in the snapshot with the given name.
std::vector<SnapshotObjectId> GetIds(const v8::HeapSnapshot& snapshot,
std::string name) {
const HeapSnapshot& heap_snapshot =
reinterpret_cast<const HeapSnapshot&>(snapshot);
std::vector<SnapshotObjectId> result;
for (const HeapEntry& entry : heap_snapshot.entries()) {
if (entry.name() == name) {
result.push_back(entry.id());
}
}
return result;
}
bool ContainsRetainingPath(const v8::HeapSnapshot& snapshot,
const std::vector<std::string> retaining_path,
bool debug_retaining_path = false) {
const HeapSnapshot& heap_snapshot =
reinterpret_cast<const HeapSnapshot&>(snapshot);
std::vector<HeapEntry*> haystack = {heap_snapshot.root()};
for (size_t i = 0; i < retaining_path.size(); ++i) {
const std::string& needle = retaining_path[i];
std::vector<HeapEntry*> new_haystack;
for (HeapEntry* parent : haystack) {
for (int j = 0; j < parent->children_count(); j++) {
HeapEntry* child = parent->child(j)->to();
if (0 == strcmp(child->name(), needle.c_str())) {
new_haystack.push_back(child);
}
}
}
if (new_haystack.empty()) {
if (debug_retaining_path) {
fprintf(stderr,
"#\n# Could not find object with name '%s'\n#\n# Path:\n",
needle.c_str());
for (size_t j = 0; j < retaining_path.size(); ++j) {
fprintf(stderr, "# - '%s'%s\n", retaining_path[j].c_str(),
i == j ? "\t<--- not found" : "");
}
fprintf(stderr, "#\n");
}
return false;
}
std::swap(haystack, new_haystack);
}
return true;
}
class BaseWithoutName : public cppgc::GarbageCollected<BaseWithoutName> {
public:
static constexpr const char kExpectedName[] =
"v8::internal::(anonymous namespace)::BaseWithoutName";
virtual void Trace(cppgc::Visitor* v) const {
v->Trace(next);
v->Trace(next2);
}
cppgc::Member<BaseWithoutName> next;
cppgc::Member<BaseWithoutName> next2;
};
// static
constexpr const char BaseWithoutName::kExpectedName[];
class GCed final : public BaseWithoutName, public cppgc::NameProvider {
public:
static constexpr const char kExpectedName[] = "GCed";
void Trace(cppgc::Visitor* v) const final { BaseWithoutName::Trace(v); }
const char* GetHumanReadableName() const final { return "GCed"; }
};
// static
constexpr const char GCed::kExpectedName[];
static constexpr const char kExpectedCppRootsName[] = "C++ Persistent roots";
static constexpr const char kExpectedCppCrossThreadRootsName[] =
"C++ CrossThreadPersistent roots";
static constexpr const char kExpectedCppStackRootsName[] =
"C++ native stack roots";
template <typename T>
constexpr const char* GetExpectedName() {
if (std::is_base_of<cppgc::NameProvider, T>::value ||
cppgc::NameProvider::SupportsCppClassNamesAsObjectNames()) {
return T::kExpectedName;
} else {
return cppgc::NameProvider::kHiddenName;
}
}
size_t GetExtraNativeBytes(const v8::HeapSnapshot* snapshot) {
return reinterpret_cast<const HeapSnapshot*>(snapshot)->extra_native_bytes();
}
template <typename Callback>
void ForEachEntryWithName(const v8::HeapSnapshot* snapshot, const char* name,
Callback callback) {
const HeapSnapshot* heap_snapshot =
reinterpret_cast<const HeapSnapshot*>(snapshot);
for (const HeapEntry& entry : heap_snapshot->entries()) {
if (strcmp(entry.name(), name) == 0) {
callback(entry);
}
}
}
void CheckSize(const v8::HeapSnapshot* snapshot, const char* name,
size_t size) {
ForEachEntryWithName(snapshot, name, [size](const HeapEntry& entry) {
EXPECT_EQ(size, entry.self_size());
});
}
template <typename T>
size_t GetCppSize(T* object) {
return cppgc::internal::HeapObjectHeader::FromObject(object).AllocatedSize();
}
} // namespace
TEST_F(UnifiedHeapSnapshotTest, EmptySnapshot) {
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
}
TEST_F(UnifiedHeapSnapshotTest, RetainedByCppRoot) {
cppgc::Persistent<GCed> gced =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, GetExpectedName<GCed>()}));
}
TEST_F(UnifiedHeapSnapshotTest, ConsistentId) {
cppgc::Persistent<GCed> gced =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot1 = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot1));
const v8::HeapSnapshot* snapshot2 = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot2));
std::vector<SnapshotObjectId> ids1 =
GetIds(*snapshot1, GetExpectedName<GCed>());
std::vector<SnapshotObjectId> ids2 =
GetIds(*snapshot2, GetExpectedName<GCed>());
EXPECT_EQ(ids1.size(), size_t{1});
EXPECT_EQ(ids2.size(), size_t{1});
EXPECT_EQ(ids1[0], ids2[0]);
}
template <typename TMixin>
class WithCppHeapWithCustomSpace : public TMixin {
public:
static std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>
GetCustomSpaces() {
std::vector<std::unique_ptr<cppgc::CustomSpaceBase>> custom_spaces;
custom_spaces.emplace_back(
std::make_unique<cppgc::CompactableCustomSpace>());
return custom_spaces;
}
WithCppHeapWithCustomSpace() {
IsolateWrapper::set_cpp_heap_for_next_isolate(v8::CppHeap::Create(
V8::GetCurrentPlatform(), CppHeapCreateParams{GetCustomSpaces()}));
}
};
class UnifiedHeapWithCustomSpaceSnapshotTest
: public WithUnifiedHeap< //
WithContextMixin< //
WithHeapInternals< //
WithInternalIsolateMixin< //
WithIsolateScopeMixin< //
WithIsolateMixin< //
WithCppHeapWithCustomSpace< //
WithDefaultPlatformMixin< //
::testing::Test>>>>>>>> {
public:
const v8::HeapSnapshot* TakeHeapSnapshot(
cppgc::EmbedderStackState stack_state =
cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode snapshot_mode =
v8::HeapProfiler::HeapSnapshotMode::kExposeInternals) {
v8::HeapProfiler* heap_profiler = v8_isolate()->GetHeapProfiler();
v8::HeapProfiler::HeapSnapshotOptions options;
options.control = nullptr;
options.global_object_name_resolver = nullptr;
options.snapshot_mode = snapshot_mode;
options.numerics_mode = v8::HeapProfiler::NumericsMode::kHideNumericValues;
options.stack_state = stack_state;
return heap_profiler->TakeHeapSnapshot(options);
}
};
TEST_F(UnifiedHeapWithCustomSpaceSnapshotTest, ConsistentIdAfterCompaction) {
// Ensure that only things held by Persistent handles will remain after GC.
DisableConservativeStackScanningScopeForTesting no_css(isolate()->heap());
// Allocate an object that will be thrown away by the GC, so that there's
// somewhere for the compactor to move stuff to.
cppgc::Persistent<CompactableGCed> trash =
cppgc::MakeGarbageCollected<CompactableGCed>(allocation_handle());
// Create the object which we'll actually test.
cppgc::Persistent<CompactableHolder> gced =
cppgc::MakeGarbageCollected<CompactableHolder>(allocation_handle(),
allocation_handle());
// Release the persistent reference to the other object.
trash.Release();
void* original_pointer = gced->object.Get();
// This first snapshot should not trigger compaction of the cppgc heap because
// the heap is still very small.
const v8::HeapSnapshot* snapshot1 =
TakeHeapSnapshot(cppgc::EmbedderStackState::kNoHeapPointers);
EXPECT_TRUE(IsValidSnapshot(snapshot1));
EXPECT_EQ(original_pointer, gced->object.Get());
// Manually run a GC with compaction. The GCed object should move.
CppHeap::From(isolate()->heap()->cpp_heap())
->compactor()
.EnableForNextGCForTesting();
i::InvokeMajorGC(isolate(), i::GCFlag::kReduceMemoryFootprint);
EXPECT_NE(original_pointer, gced->object.Get());
// In the second heap snapshot, the moved object should still have the same
// ID.
const v8::HeapSnapshot* snapshot2 =
TakeHeapSnapshot(cppgc::EmbedderStackState::kNoHeapPointers);
EXPECT_TRUE(IsValidSnapshot(snapshot2));
std::vector<SnapshotObjectId> ids1 =
GetIds(*snapshot1, GetExpectedName<CompactableGCed>());
std::vector<SnapshotObjectId> ids2 =
GetIds(*snapshot2, GetExpectedName<CompactableGCed>());
// Depending on build config, GetIds might have returned only the ID for the
// CompactableGCed instance or it might have also returned the ID for the
// CompactableHolder.
EXPECT_TRUE(ids1.size() == 1 || ids1.size() == 2);
std::sort(ids1.begin(), ids1.end());
std::sort(ids2.begin(), ids2.end());
EXPECT_EQ(ids1, ids2);
}
TEST_F(UnifiedHeapSnapshotTest, RetainedByCppCrossThreadRoot) {
cppgc::subtle::CrossThreadPersistent<GCed> gced =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppCrossThreadRootsName, GetExpectedName<GCed>()}));
}
TEST_F(UnifiedHeapSnapshotTest, RetainedByStackRoots) {
auto* volatile gced = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot =
TakeHeapSnapshot(cppgc::EmbedderStackState::kMayContainHeapPointers);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppStackRootsName, GetExpectedName<GCed>()}));
EXPECT_STREQ(gced->GetHumanReadableName(), GetExpectedName<GCed>());
}
TEST_F(UnifiedHeapSnapshotTest, RetainingUnnamedTypeWithInternalDetails) {
cppgc::Persistent<BaseWithoutName> base_without_name =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, GetExpectedName<BaseWithoutName>()}));
CheckSize(snapshot, GetExpectedName<BaseWithoutName>(),
GetCppSize(base_without_name.Get()));
EXPECT_EQ(0u, GetExtraNativeBytes(snapshot));
}
TEST_F(UnifiedHeapSnapshotTest, RetainingUnnamedTypeWithoutInternalDetails) {
cppgc::Persistent<BaseWithoutName> base_without_name =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
const v8::HeapSnapshot* snapshot =
TakeHeapSnapshot(cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode::kRegular);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_FALSE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, cppgc::NameProvider::kHiddenName}));
EXPECT_FALSE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, GetExpectedName<BaseWithoutName>()}));
EXPECT_EQ(GetCppSize(base_without_name.Get()), GetExtraNativeBytes(snapshot));
}
TEST_F(UnifiedHeapSnapshotTest, RetainingNamedThroughUnnamed) {
cppgc::Persistent<BaseWithoutName> base_without_name =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
base_without_name->next =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot =
TakeHeapSnapshot(cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode::kRegular);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, cppgc::NameProvider::kHiddenName,
GetExpectedName<GCed>()}));
CheckSize(snapshot, cppgc::NameProvider::kHiddenName, 0);
CheckSize(snapshot, GetExpectedName<GCed>(),
GetCppSize(base_without_name->next.Get()));
EXPECT_EQ(GetCppSize(base_without_name.Get()), GetExtraNativeBytes(snapshot));
}
TEST_F(UnifiedHeapSnapshotTest, PendingCallStack) {
// Test ensures that the algorithm handles references into the current call
// stack.
//
// Graph:
// Persistent -> BaseWithoutName (2) <-> BaseWithoutName (1) -> GCed (3)
//
// Visitation order is (1)->(2)->(3) which is a corner case, as when following
// back from (2)->(1) the object in (1) is already visited and will only later
// be marked as visible.
auto* first =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
auto* second =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
first->next = second;
first->next->next = first;
auto* third = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
first->next2 = third;
cppgc::Persistent<BaseWithoutName> holder(second);
const v8::HeapSnapshot* snapshot =
TakeHeapSnapshot(cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode::kRegular);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, cppgc::NameProvider::kHiddenName,
cppgc::NameProvider::kHiddenName, GetExpectedName<GCed>()}));
CheckSize(snapshot, cppgc::NameProvider::kHiddenName, 0);
CheckSize(snapshot, GetExpectedName<GCed>(), GetCppSize(third));
EXPECT_EQ(GetCppSize(first) + GetCppSize(second),
GetExtraNativeBytes(snapshot));
}
TEST_F(UnifiedHeapSnapshotTest, ReferenceToFinishedSCC) {
// Test ensures that the algorithm handles reference into an already finished
// SCC that is marked as hidden whereas the current SCC would resolve to
// visible.
//
// Graph:
// Persistent -> BaseWithoutName (1)
// Persistent -> BaseWithoutName (2)
// + <-> BaseWithoutName (3) -> BaseWithoutName (1)
// + -> GCed (4)
//
// Visitation order (1)->(2)->(3)->(1) which is a corner case as (3) would set
// a dependency on (1) which is hidden. Instead (3) should set a dependency on
// (2) as (1) resolves to hidden whereas (2) resolves to visible. The test
// ensures that resolved hidden dependencies are ignored.
cppgc::Persistent<BaseWithoutName> hidden_holder(
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle()));
auto* first =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
auto* second =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
first->next = second;
second->next = *hidden_holder;
second->next2 = first;
first->next2 = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
cppgc::Persistent<BaseWithoutName> holder(first);
const v8::HeapSnapshot* snapshot =
TakeHeapSnapshot(cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode::kRegular);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, cppgc::NameProvider::kHiddenName,
cppgc::NameProvider::kHiddenName,
cppgc::NameProvider::kHiddenName, GetExpectedName<GCed>()}));
}
namespace {
class GCedWithJSRef : public cppgc::GarbageCollected<GCedWithJSRef> {
public:
static constexpr const char kExpectedName[] =
"v8::internal::(anonymous namespace)::GCedWithJSRef";
virtual void Trace(cppgc::Visitor* v) const { v->Trace(v8_object_); }
void SetV8Object(v8::Isolate* isolate, v8::Local<v8::Object> object) {
v8_object_.Reset(isolate, object);
}
TracedReference<v8::Object>& wrapper() { return v8_object_; }
void set_detachedness(v8::EmbedderGraph::Node::Detachedness detachedness) {
detachedness_ = detachedness;
}
v8::EmbedderGraph::Node::Detachedness detachedness() const {
return detachedness_;
}
private:
TracedReference<v8::Object> v8_object_;
v8::EmbedderGraph::Node::Detachedness detachedness_ =
v8::EmbedderGraph::Node::Detachedness ::kUnknown;
};
constexpr const char GCedWithJSRef::kExpectedName[];
class V8_NODISCARD JsTestingScope {
public:
explicit JsTestingScope(v8::Isolate* isolate)
: isolate_(isolate),
handle_scope_(isolate),
context_(v8::Context::New(isolate)),
context_scope_(context_) {}
v8::Isolate* isolate() const { return isolate_; }
v8::Local<v8::Context> context() const { return context_; }
private:
v8::Isolate* isolate_;
v8::HandleScope handle_scope_;
v8::Local<v8::Context> context_;
v8::Context::Scope context_scope_;
};
cppgc::Persistent<GCedWithJSRef> SetupWrapperWrappablePair(
JsTestingScope& testing_scope, cppgc::AllocationHandle& allocation_handle,
const char* name,
v8::EmbedderGraph::Node::Detachedness detachedness =
v8::EmbedderGraph::Node::Detachedness::kUnknown) {
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref =
cppgc::MakeGarbageCollected<GCedWithJSRef>(allocation_handle);
v8::Local<v8::Object> wrapper_object = WrapperHelper::CreateWrapper(
testing_scope.context(), gc_w_js_ref.Get(), name);
gc_w_js_ref->SetV8Object(testing_scope.isolate(), wrapper_object);
gc_w_js_ref->set_detachedness(detachedness);
return gc_w_js_ref;
}
} // namespace
TEST_F(UnifiedHeapSnapshotTest, JSReferenceForcesVisibleObject) {
// Test ensures that a C++->JS reference forces an object to be visible in the
// snapshot.
JsTestingScope testing_scope(v8_isolate());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "LeafJSObject");
// Reset the JS->C++ ref or otherwise the nodes would be merged.
WrapperHelper::ResetWrappableConnection(
v8_isolate(), gc_w_js_ref->wrapper().Get(v8_isolate()));
const v8::HeapSnapshot* snapshot =
TakeHeapSnapshot(cppgc::EmbedderStackState::kMayContainHeapPointers,
v8::HeapProfiler::HeapSnapshotMode::kRegular);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot,
{kExpectedCppRootsName, cppgc::NameProvider::kHiddenName, "LeafJSObject"},
true));
}
template <typename TMixin>
void WithUnifiedHeapSnapshot<TMixin>::TestMergedWrapperNode(
v8::HeapProfiler::HeapSnapshotMode snapshot_mode) {
// Test ensures that the snapshot sets a wrapper node for C++->JS references
// that have a valid back reference and that object nodes are merged. In
// practice, the C++ node is merged into the existing JS node.
JsTestingScope testing_scope(TMixin::v8_isolate());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
testing_scope, TMixin::allocation_handle(), "MergedObject");
v8::Local<v8::Object> next_object = WrapperHelper::CreateWrapper(
testing_scope.context(), nullptr, "NextObject");
v8::Local<v8::Object> wrapper_object =
gc_w_js_ref->wrapper().Get(TMixin::v8_isolate());
// Chain another object to `wrapper_object`. Since `wrapper_object` should be
// merged into `GCedWithJSRef`, the additional object must show up as direct
// child from `GCedWithJSRef`.
wrapper_object
->Set(testing_scope.context(),
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), "link")
.ToLocalChecked(),
next_object)
.ToChecked();
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot(
cppgc::EmbedderStackState::kMayContainHeapPointers, snapshot_mode);
EXPECT_TRUE(IsValidSnapshot(snapshot));
const char* kExpectedName =
snapshot_mode == v8::HeapProfiler::HeapSnapshotMode::kExposeInternals
? GetExpectedName<GCedWithJSRef>()
: cppgc::NameProvider::kHiddenName;
EXPECT_TRUE(ContainsRetainingPath(
*snapshot,
{kExpectedCppRootsName, kExpectedName,
// GCedWithJSRef is merged into MergedObject, replacing its name.
"NextObject"}));
const size_t js_size = Utils::OpenDirectHandle(*wrapper_object)->Size();
if (snapshot_mode == v8::HeapProfiler::HeapSnapshotMode::kExposeInternals) {
const size_t cpp_size = GetCppSize(gc_w_js_ref.Get());
CheckSize(snapshot, kExpectedName, cpp_size + js_size);
} else {
CheckSize(snapshot, kExpectedName, js_size);
}
}
TEST_F(UnifiedHeapSnapshotTest, MergedWrapperNodeWithInternalDetails) {
TestMergedWrapperNode(v8::HeapProfiler::HeapSnapshotMode::kExposeInternals);
}
TEST_F(UnifiedHeapSnapshotTest, MergedWrapperNodeWithoutInternalDetails) {
TestMergedWrapperNode(v8::HeapProfiler::HeapSnapshotMode::kRegular);
}
namespace {
class DetachednessHandler {
public:
static size_t callback_count;
static v8::EmbedderGraph::Node::Detachedness GetDetachedness(
v8::Isolate* isolate, const v8::Local<v8::Value>& v8_value, uint16_t,
void*) {
callback_count++;
return WrapperHelper::UnwrapAs<GCedWithJSRef>(isolate,
v8_value.As<v8::Object>())
->detachedness();
}
static void Reset() { callback_count = 0; }
};
// static
size_t DetachednessHandler::callback_count = 0;
constexpr uint8_t kExpectedDetachedValueForUnknown =
static_cast<uint8_t>(v8::EmbedderGraph::Node::Detachedness::kUnknown);
constexpr uint8_t kExpectedDetachedValueForAttached =
static_cast<uint8_t>(v8::EmbedderGraph::Node::Detachedness::kAttached);
constexpr uint8_t kExpectedDetachedValueForDetached =
static_cast<uint8_t>(v8::EmbedderGraph::Node::Detachedness::kDetached);
} // namespace
TEST_F(UnifiedHeapSnapshotTest, DetachedObjectsRetainedByJSReference) {
v8::Isolate* isolate = v8_isolate();
v8::HandleScope scope(isolate);
v8::HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
heap_profiler->SetGetDetachednessCallback(
DetachednessHandler::GetDetachedness, nullptr);
// Test ensures that objects that are retained by a JS reference are obtained
// by the GetDetachedJSWrapperObjects() function
JsTestingScope testing_scope(v8_isolate());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "Obj",
v8::EmbedderGraph::Node::Detachedness ::kDetached);
// Ensure we are obtaining a Detached Wrapper
CHECK_EQ(1, heap_profiler->GetDetachedJSWrapperObjects().size());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref_not_detached =
SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "Obj",
v8::EmbedderGraph::Node::Detachedness ::kAttached);
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref_unknown =
SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "Obj",
v8::EmbedderGraph::Node::Detachedness ::kUnknown);
// Ensure we are only obtaining Wrappers that are Detached
CHECK_EQ(1, heap_profiler->GetDetachedJSWrapperObjects().size());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref2 = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "Obj",
v8::EmbedderGraph::Node::Detachedness ::kDetached);
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref3 = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "Obj",
v8::EmbedderGraph::Node::Detachedness ::kDetached);
// Ensure we are obtaining all Detached Wrappers
CHECK_EQ(3, heap_profiler->GetDetachedJSWrapperObjects().size());
}
TEST_F(UnifiedHeapSnapshotTest, NoTriggerForStandAloneTracedReference) {
// Test ensures that C++ objects with TracedReference have their V8 objects
// not merged and queried for detachedness if the backreference is invalid.
JsTestingScope testing_scope(v8_isolate());
// Marking the object as attached. The check below queries for unknown, making
// sure that the state is not propagated.
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "MergedObject",
v8::EmbedderGraph::Node::Detachedness::kAttached);
DetachednessHandler::Reset();
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
DetachednessHandler::GetDetachedness, nullptr);
WrapperHelper::ResetWrappableConnection(
v8_isolate(), gc_w_js_ref->wrapper().Get(v8_isolate()));
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_EQ(0u, DetachednessHandler::callback_count);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot, {
kExpectedCppRootsName,
GetExpectedName<GCedWithJSRef>(),
}));
ForEachEntryWithName(
snapshot, GetExpectedName<GCedWithJSRef>(), [](const HeapEntry& entry) {
EXPECT_EQ(kExpectedDetachedValueForUnknown, entry.detachedness());
});
}
TEST_F(UnifiedHeapSnapshotTest, TriggerDetachednessCallbackSettingAttached) {
// Test ensures that objects with JS references that have a valid back
// reference set do have their detachedness state queried and set (attached
// version).
JsTestingScope testing_scope(v8_isolate());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "MergedObject",
v8::EmbedderGraph::Node::Detachedness::kAttached);
DetachednessHandler::Reset();
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
DetachednessHandler::GetDetachedness, nullptr);
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_EQ(1u, DetachednessHandler::callback_count);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot, {
kExpectedCppRootsName,
GetExpectedName<GCedWithJSRef>(),
}));
ForEachEntryWithName(
snapshot, GetExpectedName<GCedWithJSRef>(), [](const HeapEntry& entry) {
EXPECT_EQ(kExpectedDetachedValueForAttached, entry.detachedness());
});
}
TEST_F(UnifiedHeapSnapshotTest, TriggerDetachednessCallbackSettingDetached) {
// Test ensures that objects with JS references that have a valid back
// reference set do have their detachedness state queried and set (detached
// version).
JsTestingScope testing_scope(v8_isolate());
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
testing_scope, allocation_handle(), "MergedObject",
v8::EmbedderGraph::Node::Detachedness ::kDetached);
DetachednessHandler::Reset();
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
DetachednessHandler::GetDetachedness, nullptr);
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_EQ(1u, DetachednessHandler::callback_count);
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot, {
kExpectedCppRootsName,
GetExpectedName<GCedWithJSRef>(),
}));
ForEachEntryWithName(
snapshot, GetExpectedName<GCedWithJSRef>(), [](const HeapEntry& entry) {
EXPECT_EQ(kExpectedDetachedValueForDetached, entry.detachedness());
});
}
namespace {
class WrappedContext : public cppgc::GarbageCollected<WrappedContext>,
public cppgc::NameProvider {
public:
static constexpr const char kExpectedName[] = "cppgc WrappedContext";
// Cycle:
// Context -> EmbdderData -> WrappedContext JS object -> WrappedContext cppgc
// object -> Context
static cppgc::Persistent<WrappedContext> New(v8::Isolate* isolate) {
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Local<v8::Object> obj =
WrapperHelper::CreateWrapper(context, nullptr, "js WrappedContext");
context->SetEmbedderData(kContextDataIndex, obj);
cppgc::Persistent<WrappedContext> ref =
cppgc::MakeGarbageCollected<WrappedContext>(
isolate->GetCppHeap()->GetAllocationHandle(), isolate, obj,
context);
WrapperHelper::SetWrappableConnection(isolate, obj, ref.Get());
return ref;
}
static v8::EmbedderGraph::Node::Detachedness GetDetachedness(
v8::Isolate* isolate, const v8::Local<v8::Value>& v8_value, uint16_t,
void*) {
return WrapperHelper::UnwrapAs<WrappedContext>(isolate,
v8_value.As<v8::Object>())
->detachedness();
}
const char* GetHumanReadableName() const final { return kExpectedName; }
virtual void Trace(cppgc::Visitor* v) const {
v->Trace(object_);
v->Trace(context_);
}
WrappedContext(v8::Isolate* isolate, v8::Local<v8::Object> object,
v8::Local<v8::Context> context) {
object_.Reset(isolate, object);
context_.Reset(isolate, context);
}
v8::Local<v8::Context> context(v8::Isolate* isolate) {
return context_.Get(isolate);
}
void set_detachedness(v8::EmbedderGraph::Node::Detachedness detachedness) {
detachedness_ = detachedness;
}
v8::EmbedderGraph::Node::Detachedness detachedness() const {
return detachedness_;
}
private:
static constexpr int kContextDataIndex = 0;
// This is needed to merge the nodes in the heap snapshot.
TracedReference<v8::Object> object_;
TracedReference<v8::Context> context_;
v8::EmbedderGraph::Node::Detachedness detachedness_ =
v8::EmbedderGraph::Node::Detachedness::kUnknown;
};
} // anonymous namespace
TEST_F(UnifiedHeapSnapshotTest, WrappedContext) {
JsTestingScope testing_scope(v8_isolate());
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
WrappedContext::GetDetachedness, nullptr);
cppgc::Persistent<WrappedContext> wrapped = WrappedContext::New(v8_isolate());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot,
{kExpectedCppRootsName, wrapped->GetHumanReadableName(),
"system / NativeContext", "system / EmbedderDataArray",
wrapped->GetHumanReadableName()},
true));
wrapped->set_detachedness(v8::EmbedderGraph::Node::Detachedness::kDetached);
v8_isolate()->GetHeapProfiler()->DeleteAllHeapSnapshots();
snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot,
{kExpectedCppRootsName, wrapped->GetHumanReadableName(),
"system / NativeContext", "system / EmbedderDataArray",
wrapped->GetHumanReadableName()},
true));
ForEachEntryWithName(
snapshot, wrapped->GetHumanReadableName(), [](const HeapEntry& entry) {
EXPECT_EQ(kExpectedDetachedValueForDetached, entry.detachedness());
});
}
namespace {
class GCedWithDynamicName : public cppgc::GarbageCollected<GCedWithDynamicName>,
public cppgc::NameProvider {
public:
virtual void Trace(cppgc::Visitor* v) const {}
void SetValue(int value) { value_ = value; }
const char* GetHumanReadableName() const final {
v8::HeapProfiler* heap_profiler =
v8::Isolate::GetCurrent()->GetHeapProfiler();
if (heap_profiler->IsTakingSnapshot()) {
std::string name = "dynamic name " + std::to_string(value_);
return heap_profiler->CopyNameForHeapSnapshot(name.c_str());
}
return "static name";
}
private:
int value_ = 0;
};
} // namespace
TEST_F(UnifiedHeapSnapshotTest, DynamicName) {
cppgc::Persistent<GCedWithDynamicName> object_zero =
cppgc::MakeGarbageCollected<GCedWithDynamicName>(allocation_handle());
cppgc::Persistent<GCedWithDynamicName> object_one =
cppgc::MakeGarbageCollected<GCedWithDynamicName>(allocation_handle());
object_one->SetValue(1);
std::string static_name =
cppgc::internal::HeapObjectHeader::FromObject(object_one.Get())
.GetName()
.value;
EXPECT_EQ(static_name, std::string("static name"));
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(*snapshot,
{kExpectedCppRootsName, "dynamic name 0"}));
EXPECT_TRUE(ContainsRetainingPath(*snapshot,
{kExpectedCppRootsName, "dynamic name 1"}));
EXPECT_FALSE(
ContainsRetainingPath(*snapshot, {kExpectedCppRootsName, "static name"}));
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,970 @@
// 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 <memory>
#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-locker.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/flags/flags.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/heap/gc-tracer-inl.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<Wrappable> {
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<v8::Object> wrapper) {
wrapper_.Reset(isolate, wrapper);
}
TracedReference<v8::Object>& wrapper() { return wrapper_; }
private:
TracedReference<v8::Object> wrapper_;
};
size_t Wrappable::destructor_callcount = 0;
using UnifiedHeapDetachedTest = TestWithHeapInternals;
} // namespace
TEST_F(UnifiedHeapTest, OnlyGC) { CollectGarbageWithEmbedderStack(); }
TEST_F(UnifiedHeapTest, FindingV8ToCppReference) {
auto* wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), wrappable_object);
EXPECT_FALSE(api_object.IsEmpty());
// With direct locals, api_object may be invalid after a stackless GC.
auto handle_api_object = v8::Utils::OpenIndirectHandle(*api_object);
Wrappable::destructor_callcount = 0;
EXPECT_EQ(0u, Wrappable::destructor_callcount);
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(0u, Wrappable::destructor_callcount);
WrapperHelper::ResetWrappableConnection(
v8_isolate(), v8::Utils::ToLocal(handle_api_object));
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(1u, Wrappable::destructor_callcount);
}
TEST_F(UnifiedHeapTest, WriteBarrierV8ToCppReference) {
if (!v8_flags.incremental_marking) return;
void* wrappable = cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(v8_isolate()->GetCurrentContext(), nullptr);
EXPECT_FALSE(api_object.IsEmpty());
// With direct locals, api_object may be invalid after a stackless GC.
auto handle_api_object = v8::Utils::OpenIndirectHandle(*api_object);
// Create an additional Global that gets picked up by the incremetnal marker
// as root.
Global<v8::Object> global(v8_isolate(), api_object);
Wrappable::destructor_callcount = 0;
WrapperHelper::ResetWrappableConnection(v8_isolate(), api_object);
SimulateIncrementalMarking();
WrapperHelper::SetWrappableConnection(
v8_isolate(), v8::Utils::ToLocal(handle_api_object), wrappable);
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(0u, Wrappable::destructor_callcount);
}
#if DEBUG
namespace {
class Unreferenced : public cppgc::GarbageCollected<Unreferenced> {
public:
void Trace(cppgc::Visitor*) const {}
};
} // namespace
TEST_F(UnifiedHeapTest, FreeUnreferencedDuringNoGcScope) {
auto* unreferenced = cppgc::MakeGarbageCollected<Unreferenced>(
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<Unreferenced>(allocation_handle());
cpp_heap().stats_collector()->NotifySafePointForTesting();
}
#endif // DEBUG
TEST_F(UnifiedHeapTest, TracedReferenceRetainsFromStack) {
TracedReference<v8::Object> 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());
}
template <typename TMixin>
class WithCppHeapWithAllocationBeforeConfigureHeap : public TMixin {
public:
WithCppHeapWithAllocationBeforeConfigureHeap() {
auto heap =
v8::CppHeap::Create(V8::GetCurrentPlatform(), CppHeapCreateParams{{}});
auto* object =
cppgc::MakeGarbageCollected<Wrappable>(heap->GetAllocationHandle());
weak_holder_ = cppgc::WeakPersistent<Wrappable>{object};
IsolateWrapper::set_cpp_heap_for_next_isolate(std::move(heap));
}
cppgc::WeakPersistent<Wrappable> weak_holder_;
};
using UnifiedHeapTestWithAllocationBeforeConfigureHeap = WithUnifiedHeap< //
WithContextMixin< //
WithHeapInternals< //
WithInternalIsolateMixin< //
WithIsolateScopeMixin< //
WithIsolateMixin< //
WithCppHeapWithAllocationBeforeConfigureHeap< //
WithDefaultPlatformMixin< //
::testing::Test>>>>>>>>;
TEST_F(UnifiedHeapTestWithAllocationBeforeConfigureHeap,
AllocationBeforeConfigureHeap) {
auto& js_heap = *isolate()->heap();
auto& cpp_heap = *CppHeap::From(isolate()->heap()->cpp_heap());
auto weak_holder = std::move(weak_holder_);
{
InvokeMajorGC();
cpp_heap.AsBase().sweeper().FinishIfRunning();
EXPECT_TRUE(weak_holder);
}
{
EmbedderStackStateScope stack_scope(
&js_heap, EmbedderStackStateOrigin::kExplicitInvocation,
StackState::kNoHeapPointers);
InvokeMajorGC();
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{{}});
auto* object =
cppgc::MakeGarbageCollected<Wrappable>(heap->GetAllocationHandle());
cppgc::WeakPersistent<Wrappable> 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{{}});
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<CustomSpaceForTest> {
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<GCed> {
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<v8::internal::GCed> {
using Space = CustomSpaceForTest;
};
} // namespace cppgc
namespace v8::internal {
namespace {
template <typename TMixin>
class WithCppHeapWithCustomSpace : public TMixin {
public:
static std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>
GetCustomSpaces() {
std::vector<std::unique_ptr<cppgc::CustomSpaceBase>> custom_spaces;
custom_spaces.emplace_back(std::make_unique<cppgc::CustomSpaceForTest>());
return custom_spaces;
}
WithCppHeapWithCustomSpace() {
IsolateWrapper::set_cpp_heap_for_next_isolate(v8::CppHeap::Create(
V8::GetCurrentPlatform(), CppHeapCreateParams{GetCustomSpaces()}));
}
};
using UnifiedHeapWithCustomSpaceTest = WithUnifiedHeap< //
WithContextMixin< //
WithHeapInternals< //
WithInternalIsolateMixin< //
WithIsolateScopeMixin< //
WithIsolateMixin< //
WithCppHeapWithCustomSpace< //
WithDefaultPlatformMixin< //
::testing::Test>>>>>>>>;
} // namespace
TEST_F(UnifiedHeapWithCustomSpaceTest, CollectCustomSpaceStatisticsAtLastGC) {
StatisticsReceiver::num_calls_ = 0;
// Initial state.
cpp_heap().CollectCustomSpaceStatisticsAtLastGC(
{cppgc::CustomSpaceForTest::kSpaceIndex},
std::make_unique<StatisticsReceiver>(
cppgc::CustomSpaceForTest::kSpaceIndex, 0u));
EXPECT_EQ(1u, StatisticsReceiver::num_calls_);
// State unpdated only after GC.
cppgc::Persistent<GCed> live_obj =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
cpp_heap().CollectCustomSpaceStatisticsAtLastGC(
{cppgc::CustomSpaceForTest::kSpaceIndex},
std::make_unique<StatisticsReceiver>(
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<StatisticsReceiver>(
cppgc::CustomSpaceForTest::kSpaceIndex, GCed::GetAllocatedSize()));
EXPECT_EQ(3u, StatisticsReceiver::num_calls_);
// State callback delayed during sweeping.
cppgc::Persistent<GCed> another_live_obj =
cppgc::MakeGarbageCollected<GCed>(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<StatisticsReceiver>(
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<v8::Object> wrapper)
: wrapper_(reinterpret_cast<v8::Isolate*>(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<v8::Object>& GetWrapper() { return wrapper_; }
private:
TracedReference<v8::Object> 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<i::Isolate*>(v8_isolate())->heap(), local);
CHECK_NE(kGlobalHandleZapValue,
ValueHelper::ValueAsAddress(
ValueHelper::HandleAsValue(cpp_obj->GetWrapper())));
}
}
namespace {
class ResetReferenceInDestructorObject final
: public cppgc::GarbageCollected<ResetReferenceInDestructorObject> {
public:
ResetReferenceInDestructorObject(Heap* heap, v8::Local<v8::Object> wrapper)
: wrapper_(reinterpret_cast<v8::Isolate*>(heap->isolate()), wrapper) {}
~ResetReferenceInDestructorObject() { wrapper_.Reset(); }
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(wrapper_); }
private:
TracedReference<v8::Object> 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<ResetReferenceInDestructorObject>(
allocation_handle(),
reinterpret_cast<i::Isolate*>(v8_isolate())->heap(), local);
}
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
}
TEST_F(UnifiedHeapTest, OnStackReferencesAreTemporary) {
ManualGCScope manual_gc(i_isolate());
v8::Global<v8::Object> observer;
{
v8::TracedReference<v8::Value> stack_ref;
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Object> api_object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), 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<Isolate*>(v8_isolate())->heap());
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
}
EXPECT_TRUE(observer.IsEmpty());
}
TEST_F(UnifiedHeapTest, TracedReferenceOnStack) {
ManualGCScope manual_gc(i_isolate());
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_ref;
{
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), nullptr);
stack_ref.Reset(v8_isolate(), object);
observer.Reset(v8_isolate(), object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
InvokeMajorGC();
EXPECT_FALSE(observer.IsEmpty());
}
namespace {
enum class Operation {
kCopy,
kMove,
};
template <typename T>
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<GCedWithHeapRef> {
public:
v8::TracedReference<v8::Value> 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) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i::ManualGCScope manual_gc_scope(i_isolate);
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::CppHeap* cpp_heap = v8_isolate->GetCppHeap();
cppgc::Persistent<GCedWithHeapRef> cpp_heap_obj =
cppgc::MakeGarbageCollected<GCedWithHeapRef>(
cpp_heap->GetAllocationHandle());
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object =
WrapperHelper::CreateWrapper(v8_isolate->GetCurrentContext(), nullptr);
EXPECT_TRUE(IsNewObjectInCorrectGeneration(
*v8::Utils::OpenDirectHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
InvokeMajorGC(i_isolate);
EXPECT_FALSE(i::HeapLayout::InYoungGeneration(
*v8::Utils::OpenDirectHandle(*to_object)));
}
cpp_heap_obj->heap_handle.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object =
WrapperHelper::CreateWrapper(v8_isolate->GetCurrentContext(), nullptr);
stack_handle.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
InvokeMajorGC(i_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &cpp_heap_obj->heap_handle, &stack_handle);
InvokeMajorGC(i_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(
i_isolate->heap());
InvokeMajorGC(i_isolate);
}
ASSERT_TRUE(observer.IsEmpty());
}
V8_NOINLINE void HeapToStackTest(v8::Isolate* v8_isolate, Operation op,
TargetHandling target_handling) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i::ManualGCScope manual_gc_scope(i_isolate);
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::CppHeap* cpp_heap = v8_isolate->GetCppHeap();
cppgc::Persistent<GCedWithHeapRef> cpp_heap_obj =
cppgc::MakeGarbageCollected<GCedWithHeapRef>(
cpp_heap->GetAllocationHandle());
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object =
WrapperHelper::CreateWrapper(v8_isolate->GetCurrentContext(), nullptr);
EXPECT_TRUE(IsNewObjectInCorrectGeneration(
*v8::Utils::OpenDirectHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
InvokeMajorGC(i_isolate);
EXPECT_FALSE(i::HeapLayout::InYoungGeneration(
*v8::Utils::OpenDirectHandle(*to_object)));
}
stack_handle.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object =
WrapperHelper::CreateWrapper(v8_isolate->GetCurrentContext(), nullptr);
cpp_heap_obj->heap_handle.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
InvokeMajorGC(i_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &stack_handle, &cpp_heap_obj->heap_handle);
InvokeMajorGC(i_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(
i_isolate->heap());
InvokeMajorGC(i_isolate);
}
EXPECT_TRUE(observer.IsEmpty());
}
V8_NOINLINE void StackToStackTest(v8::Isolate* v8_isolate, Operation op,
TargetHandling target_handling) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i::ManualGCScope manual_gc_scope(i_isolate);
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle1;
v8::TracedReference<v8::Value> stack_handle2;
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object =
WrapperHelper::CreateWrapper(v8_isolate->GetCurrentContext(), nullptr);
EXPECT_TRUE(IsNewObjectInCorrectGeneration(
*v8::Utils::OpenDirectHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
InvokeMajorGC(i_isolate);
EXPECT_FALSE(i::HeapLayout::InYoungGeneration(
*v8::Utils::OpenDirectHandle(*to_object)));
}
stack_handle2.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object =
WrapperHelper::CreateWrapper(v8_isolate->GetCurrentContext(), nullptr);
stack_handle1.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
InvokeMajorGC(i_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &stack_handle2, &stack_handle1);
InvokeMajorGC(i_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(
i_isolate->heap());
InvokeMajorGC(i_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());
Wrappable::destructor_callcount = 0;
v8::Local<v8::Object> key =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
DirectHandle<JSWeakMap> 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<Wrappable>(allocation_handle());
v8::Local<v8::Object> value = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), wrappable_object);
EXPECT_FALSE(value.IsEmpty());
DirectHandle<JSObject> js_key = direct_handle(
Cast<JSObject>(*v8::Utils::OpenDirectHandle(*key)), i_isolate());
DirectHandle<JSReceiver> js_value = v8::Utils::OpenDirectHandle(*value);
int32_t hash = Object::GetOrCreateHash(*js_key, 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::TracedReference<v8::Value> 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 {
class Wrappable2 final : public cppgc::GarbageCollected<Wrappable2> {
public:
static size_t destructor_call_count;
void Trace(cppgc::Visitor* visitor) const {}
~Wrappable2() { destructor_call_count++; }
};
size_t Wrappable2::destructor_call_count = 0;
} // namespace
namespace {
class WrappedData final : public cppgc::GarbageCollected<WrappedData> {
public:
WrappedData(v8::Isolate* isolate, v8::Local<v8::Private> data) {
data_.Reset(isolate, data);
}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(data_); }
v8::Local<v8::Private> data(v8::Isolate* isolate) {
return data_.Get(isolate);
}
private:
TracedReference<v8::Private> data_;
};
} // namespace
TEST_F(UnifiedHeapTest, WrapperWithTracedReferenceData) {
v8::Isolate* isolate = v8_isolate();
cppgc::Persistent<WrappedData> live_wrap;
{
live_wrap = cppgc::MakeGarbageCollected<WrappedData>(
allocation_handle(), isolate,
v8::Private::New(isolate,
v8::String::NewFromUtf8Literal(isolate, "test")));
}
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
{
v8::Local<v8::Value> name = live_wrap.Get()->data(isolate)->Name();
CHECK(name->IsString());
CHECK(name.As<v8::String>()->StringEquals(
v8::String::NewFromUtf8Literal(isolate, "test")));
}
}
TEST_F(UnifiedHeapTest, CppgcSweepingDuringMinorV8Sweeping) {
if (!v8_flags.minor_ms) return;
if (v8_flags.single_generation) return;
// Heap verification finalizes sweeping in the atomic pause.
if (v8_flags.verify_heap) return;
bool single_threaded_gc_flag = v8_flags.single_threaded_gc;
// Single threaded gc force non-concurrent sweeping in cppgc, which makes
// CppHeap bail out of `FinishSweepingIfOutOfWork`.
v8_flags.single_threaded_gc = true;
ManualGCScope manual_gc(isolate());
Heap* heap = isolate()->heap();
CppHeap* cppheap = CppHeap::From(heap->cpp_heap());
cppheap->UpdateGCCapabilitiesFromFlagsForTesting();
CHECK_NOT_NULL(heap->cpp_heap());
heap->CollectGarbage(AllocationSpace::OLD_SPACE,
GarbageCollectionReason::kTesting,
GCCallbackFlags::kNoGCCallbackFlags);
CHECK(heap->sweeping_in_progress());
CHECK(cppheap->sweeper().IsSweepingInProgress());
heap->EnsureSweepingCompleted(Heap::SweepingForcedFinalizationMode::kV8Only);
CHECK(!heap->sweeping_in_progress());
CHECK(cppheap->sweeper().IsSweepingInProgress());
heap->CollectGarbage(AllocationSpace::NEW_SPACE,
GarbageCollectionReason::kTesting,
GCCallbackFlags::kNoGCCallbackFlags);
CHECK(!heap->major_sweeping_in_progress());
CHECK(heap->minor_sweeping_in_progress());
CHECK(cppheap->sweeper().IsSweepingInProgress());
cppheap->sweeper().FinishIfRunning();
CHECK(!heap->major_sweeping_in_progress());
CHECK(heap->minor_sweeping_in_progress());
CHECK(!cppheap->sweeper().IsSweepingInProgress());
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kUnifiedHeap);
v8_flags.single_threaded_gc = single_threaded_gc_flag;
}
#ifdef V8_ENABLE_ALLOCATION_TIMEOUT
struct RandomGCIntervalTestSetter {
RandomGCIntervalTestSetter() {
static constexpr int kInterval = 87;
v8_flags.cppgc_random_gc_interval = kInterval;
}
~RandomGCIntervalTestSetter() { v8_flags.cppgc_random_gc_interval = 0; }
};
struct UnifiedHeapTestWithRandomGCInterval : RandomGCIntervalTestSetter,
UnifiedHeapTest {};
TEST_F(UnifiedHeapTestWithRandomGCInterval, AllocationTimeout) {
if (v8_flags.stress_incremental_marking) return;
if (v8_flags.stress_concurrent_allocation) return;
auto& cpp_heap = *CppHeap::From(isolate()->heap()->cpp_heap());
auto& allocator = cpp_heap.object_allocator();
const int initial_allocation_timeout =
allocator.get_allocation_timeout_for_testing();
ASSERT_GT(initial_allocation_timeout, 0);
const auto current_epoch = isolate()->heap()->tracer()->CurrentEpoch(
GCTracer::Scope::MARK_COMPACTOR);
for (int i = 0; i < initial_allocation_timeout - 1; ++i) {
MakeGarbageCollected<Wrappable>(allocation_handle());
}
// Expect no GC happened so far.
EXPECT_EQ(current_epoch, isolate()->heap()->tracer()->CurrentEpoch(
GCTracer::Scope::MARK_COMPACTOR));
// This allocation must cause a GC.
MakeGarbageCollected<Wrappable>(allocation_handle());
EXPECT_EQ(current_epoch + 1, isolate()->heap()->tracer()->CurrentEpoch(
GCTracer::Scope::MARK_COMPACTOR));
}
#endif // V8_ENABLE_ALLOCATION_TIMEOUT
namespace {
using UnifiedHeapMinimalTest = WithIsolateMixin< //
WithCppHeap< //
WithDefaultPlatformMixin< //
::testing::Test>>>;
class ThreadUsingV8Locker final : public v8::base::Thread {
public:
ThreadUsingV8Locker(v8::Isolate* isolate, CppHeap* heap,
cppgc::Persistent<Wrappable>& holder)
: v8::base::Thread(Options("Thread using V8::Locker.")),
isolate_(isolate),
heap_(heap),
holder_(holder) {}
void Run() final {
v8::Locker locker(isolate_);
v8::Isolate::Scope isolate_scope(isolate_);
// This should not trigger a DCHECK (when allocating a persistent).
cppgc::Persistent<Wrappable> obj =
cppgc::MakeGarbageCollected<Wrappable>(heap_->object_allocator());
// This should not trigger a DCHECK (when invoking prefinalizers).
InvokeMajorGC(heap_->isolate());
// This should not trigger a DCHECK (upon assignment, due to pointer
// policies).
holder_ = obj;
}
private:
v8::Isolate* isolate_;
CppHeap* heap_;
cppgc::Persistent<Wrappable>& holder_;
};
} // anonymous namespace
TEST_F(UnifiedHeapMinimalTest, UsingV8Locker) {
Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate());
i::CppHeap* cpp_heap = i::CppHeap::From(isolate->heap()->cpp_heap());
// The use of v8::Locker in this test should suppress DCHECKs and CHECKS
// that enforce that the current thread is the creation thread of the heap
// or of a persistent.
cppgc::Persistent<Wrappable> obj;
{
v8::Locker locker(v8_isolate());
v8::Isolate::Scope isolate_scope(v8_isolate());
obj = cppgc::MakeGarbageCollected<Wrappable>(cpp_heap->object_allocator());
}
// Exit and unlock the isolate, allowing the thread to lock and enter.
auto thread =
std::make_unique<ThreadUsingV8Locker>(v8_isolate(), cpp_heap, obj);
CHECK(thread->Start());
thread->Join();
{
v8::Locker locker(v8_isolate());
v8::Isolate::Scope isolate_scope(v8_isolate());
obj.Clear();
}
}
namespace {
class WrappedWithConservativeGCInCtor final
: public cppgc::GarbageCollected<WrappedWithConservativeGCInCtor> {
public:
template <typename GCCallback>
WrappedWithConservativeGCInCtor(v8::Isolate* isolate,
v8::Local<v8::Private> data, GCCallback gc)
: data_(isolate, data) {
// A GC here means that the object is in construction and data_ will be
// traced conservatively. If we miss out on handling the TracedReference it
// will be zapped.
gc();
}
void Trace(cppgc::Visitor* visitor) const {
// For completeness only as GC in ctor won't use the `Trace()` method.
visitor->Trace(data_);
}
v8::Local<v8::Private> data(v8::Isolate* isolate) {
return data_.Get(isolate);
}
private:
TracedReference<v8::Private> data_;
};
} // namespace
TEST_F(UnifiedHeapTest, WrappedWithConservativeGCInCtor) {
v8::Isolate* isolate = v8_isolate();
WrappedWithConservativeGCInCtor* object =
cppgc::MakeGarbageCollected<WrappedWithConservativeGCInCtor>(
allocation_handle(), isolate,
v8::Private::New(isolate,
v8::String::NewFromUtf8Literal(isolate, "test")),
[this]() {
this->CollectGarbageWithEmbedderStack(
cppgc::Heap::SweepingType::kAtomic);
});
v8::Local<v8::Value> name = object->data(isolate)->Name();
CHECK(name->IsString());
}
} // namespace v8::internal

View File

@ -0,0 +1,78 @@
// 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 "test/unittests/heap/cppgc-js/unified-heap-utils.h"
#include "include/cppgc/platform.h"
#include "include/v8-cppgc.h"
#include "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/heap/heap.h"
#include "src/objects/js-objects.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
namespace internal {
// static
v8::Local<v8::Object> WrapperHelper::CreateWrapper(
v8::Local<v8::Context> context, void* wrappable_object,
const char* class_name) {
v8::EscapableHandleScope scope(context->GetIsolate());
v8::Local<v8::FunctionTemplate> function_t =
v8::FunctionTemplate::New(context->GetIsolate());
if (class_name && strlen(class_name) != 0) {
function_t->SetClassName(
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), class_name)
.ToLocalChecked());
}
v8::Local<v8::Function> function =
function_t->GetFunction(context).ToLocalChecked();
v8::Local<v8::Object> instance =
function->NewInstance(context).ToLocalChecked();
SetWrappableConnection(context->GetIsolate(), instance, wrappable_object);
CHECK(!instance.IsEmpty());
CHECK_EQ(wrappable_object,
ReadWrappablePointer(context->GetIsolate(), instance));
i::DirectHandle<i::JSReceiver> js_obj =
v8::Utils::OpenDirectHandle(*instance);
CHECK_EQ(i::JS_API_OBJECT_TYPE, js_obj->map()->instance_type());
return scope.Escape(instance);
}
// static
void WrapperHelper::ResetWrappableConnection(v8::Isolate* isolate,
v8::Local<v8::Object> api_object) {
i::DirectHandle<i::JSReceiver> js_obj =
v8::Utils::OpenDirectHandle(*api_object);
JSApiWrapper(Cast<JSObject>(*js_obj))
.SetCppHeapWrappable<CppHeapPointerTag::kDefaultTag>(
reinterpret_cast<i::Isolate*>(isolate), nullptr);
}
// static
void WrapperHelper::SetWrappableConnection(v8::Isolate* isolate,
v8::Local<v8::Object> api_object,
void* instance) {
i::DirectHandle<i::JSReceiver> js_obj =
v8::Utils::OpenDirectHandle(*api_object);
JSApiWrapper(Cast<JSObject>(*js_obj))
.SetCppHeapWrappable<CppHeapPointerTag::kDefaultTag>(
reinterpret_cast<i::Isolate*>(isolate), instance);
}
// static
void* WrapperHelper::ReadWrappablePointer(v8::Isolate* isolate,
v8::Local<v8::Object> api_object) {
i::DirectHandle<i::JSReceiver> js_obj =
v8::Utils::OpenDirectHandle(*api_object);
return JSApiWrapper(Cast<JSObject>(*js_obj))
.GetCppHeapWrappable(reinterpret_cast<i::Isolate*>(isolate),
kAnyCppHeapPointer);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,120 @@
// 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.
#ifndef V8_UNITTESTS_HEAP_CPPGC_JS_UNIFIED_HEAP_UTILS_H_
#define V8_UNITTESTS_HEAP_CPPGC_JS_UNIFIED_HEAP_UTILS_H_
#include "include/cppgc/heap.h"
#include "include/v8-cppgc.h"
#include "include/v8-local-handle.h"
#include "src/objects/js-objects.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
class CppHeap;
namespace internal {
class CppHeap;
template <typename TMixin>
class WithUnifiedHeap : public TMixin {
public:
WithUnifiedHeap() = default;
~WithUnifiedHeap() override = default;
void CollectGarbageWithEmbedderStack(cppgc::Heap::SweepingType sweeping_type =
cppgc::Heap::SweepingType::kAtomic) {
EmbedderStackStateScope stack_scope(
TMixin::heap(), EmbedderStackStateOrigin::kExplicitInvocation,
StackState::kMayContainHeapPointers);
TMixin::InvokeMajorGC();
if (sweeping_type == cppgc::Heap::SweepingType::kAtomic) {
cpp_heap().AsBase().sweeper().FinishIfRunning();
}
}
void CollectGarbageWithoutEmbedderStack(
cppgc::Heap::SweepingType sweeping_type =
cppgc::Heap::SweepingType::kAtomic) {
EmbedderStackStateScope stack_scope(
TMixin::heap(), EmbedderStackStateOrigin::kExplicitInvocation,
StackState::kNoHeapPointers);
TMixin::InvokeMajorGC();
if (sweeping_type == cppgc::Heap::SweepingType::kAtomic) {
cpp_heap().AsBase().sweeper().FinishIfRunning();
}
}
void CollectYoungGarbageWithEmbedderStack(
cppgc::Heap::SweepingType sweeping_type =
cppgc::Heap::SweepingType::kAtomic) {
EmbedderStackStateScope stack_scope(
TMixin::heap(), EmbedderStackStateOrigin::kExplicitInvocation,
StackState::kMayContainHeapPointers);
TMixin::InvokeMinorGC();
if (sweeping_type == cppgc::Heap::SweepingType::kAtomic) {
cpp_heap().AsBase().sweeper().FinishIfRunning();
}
}
void CollectYoungGarbageWithoutEmbedderStack(
cppgc::Heap::SweepingType sweeping_type =
cppgc::Heap::SweepingType::kAtomic) {
EmbedderStackStateScope stack_scope(
TMixin::heap(), EmbedderStackStateOrigin::kExplicitInvocation,
StackState::kNoHeapPointers);
TMixin::InvokeMinorGC();
if (sweeping_type == cppgc::Heap::SweepingType::kAtomic) {
cpp_heap().AsBase().sweeper().FinishIfRunning();
}
}
CppHeap& cpp_heap() const {
return *CppHeap::From(TMixin::isolate()->heap()->cpp_heap());
}
cppgc::AllocationHandle& allocation_handle() {
return cpp_heap().object_allocator();
}
};
using UnifiedHeapTest = WithUnifiedHeap<TestWithHeapInternalsAndContext>;
// Helpers for managed wrappers using a single header field.
class WrapperHelper {
public:
// Sets up a V8 API object so that it points back to a C++ object. The setup
// used is recognized by the GC and references will be followed for liveness
// analysis (marking) as well as tooling (snapshot).
static v8::Local<v8::Object> CreateWrapper(v8::Local<v8::Context> context,
void* wrappable_object,
const char* class_name = nullptr);
// Resets the connection of a wrapper (JS) to its wrappable (C++), meaning
// that the wrappable object is not longer kept alive by the wrapper object.
static void ResetWrappableConnection(v8::Isolate* isolate,
v8::Local<v8::Object> api_object);
// Sets up the connection of a wrapper (JS) to its wrappable (C++). Does not
// emit any possibly needed write barrier.
static void SetWrappableConnection(v8::Isolate* isolate,
v8::Local<v8::Object> api_object, void*);
template <typename T>
static T* UnwrapAs(v8::Isolate* isolate, v8::Local<v8::Object> api_object) {
return reinterpret_cast<T*>(ReadWrappablePointer(isolate, api_object));
}
private:
static void* ReadWrappablePointer(v8::Isolate* isolate,
v8::Local<v8::Object> api_object);
};
} // namespace internal
} // namespace v8
#endif // V8_UNITTESTS_HEAP_CPPGC_JS_UNIFIED_HEAP_UTILS_H_

View File

@ -0,0 +1,376 @@
// 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.
#if defined(CPPGC_YOUNG_GENERATION)
#include <algorithm>
#include <memory>
#include <vector>
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/testing.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/objects/objects-inl.h"
#include "test/common/flag-utils.h"
#include "test/unittests/heap/cppgc-js/unified-heap-utils.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace {
bool IsHeapObjectYoung(void* obj) {
return cppgc::internal::HeapObjectHeader::FromObject(obj).IsYoung();
}
bool IsHeapObjectOld(void* obj) { return !IsHeapObjectYoung(obj); }
class Wrappable final : public cppgc::GarbageCollected<Wrappable> {
public:
static size_t destructor_callcount;
Wrappable() = default;
Wrappable(v8::Isolate* isolate, v8::Local<v8::Object> local)
: wrapper_(isolate, local) {}
Wrappable(const Wrappable&) = default;
Wrappable(Wrappable&&) = default;
Wrappable& operator=(const Wrappable&) = default;
Wrappable& operator=(Wrappable&&) = default;
~Wrappable() { destructor_callcount++; }
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(wrapper_); }
void SetWrapper(v8::Isolate* isolate, v8::Local<v8::Object> wrapper) {
wrapper_.Reset(isolate, wrapper);
}
TracedReference<v8::Object>& wrapper() { return wrapper_; }
private:
TracedReference<v8::Object> wrapper_;
};
size_t Wrappable::destructor_callcount = 0;
class MinorMSEnabler {
public:
MinorMSEnabler()
: minor_ms_(&v8_flags.minor_ms, true),
cppgc_young_generation_(&v8_flags.cppgc_young_generation, true) {}
private:
FlagScope<bool> minor_ms_;
FlagScope<bool> cppgc_young_generation_;
};
class YoungWrapperCollector : public RootVisitor {
public:
using YoungWrappers = std::set<Address>;
void VisitRootPointers(Root root, const char*, FullObjectSlot start,
FullObjectSlot end) override {
for (FullObjectSlot p = start; p < end; ++p) {
all_young_wrappers_.insert(*p.location());
}
}
YoungWrappers get_wrappers() { return std::move(all_young_wrappers_); }
private:
YoungWrappers all_young_wrappers_;
};
class ExpectCppGCToV8GenerationalBarrierToFire {
public:
ExpectCppGCToV8GenerationalBarrierToFire(
v8::Isolate& isolate, std::initializer_list<Address> expected_wrappers)
: isolate_(reinterpret_cast<Isolate&>(isolate)),
expected_wrappers_(expected_wrappers) {
YoungWrapperCollector visitor;
isolate_.traced_handles()->IterateYoungRootsWithOldHostsForTesting(
&visitor);
young_wrappers_before_ = visitor.get_wrappers();
std::vector<Address> diff;
std::set_intersection(young_wrappers_before_.begin(),
young_wrappers_before_.end(),
expected_wrappers_.begin(), expected_wrappers_.end(),
std::back_inserter(diff));
EXPECT_TRUE(diff.empty());
}
~ExpectCppGCToV8GenerationalBarrierToFire() {
YoungWrapperCollector visitor;
isolate_.traced_handles()->IterateYoungRootsWithOldHostsForTesting(
&visitor);
const auto young_wrappers_after = visitor.get_wrappers();
EXPECT_GE(young_wrappers_after.size(), young_wrappers_before_.size());
EXPECT_TRUE(
std::includes(young_wrappers_after.begin(), young_wrappers_after.end(),
expected_wrappers_.begin(), expected_wrappers_.end()));
EXPECT_EQ(expected_wrappers_.size(),
young_wrappers_after.size() - young_wrappers_before_.size());
}
private:
Isolate& isolate_;
YoungWrapperCollector::YoungWrappers expected_wrappers_;
YoungWrapperCollector::YoungWrappers young_wrappers_before_;
};
class ExpectCppGCToV8NoGenerationalBarrier {
public:
explicit ExpectCppGCToV8NoGenerationalBarrier(v8::Isolate& isolate)
: isolate_(reinterpret_cast<Isolate&>(isolate)) {
YoungWrapperCollector visitor;
isolate_.traced_handles()->IterateYoungRootsWithOldHostsForTesting(
&visitor);
young_wrappers_before_ = visitor.get_wrappers();
}
~ExpectCppGCToV8NoGenerationalBarrier() {
YoungWrapperCollector visitor;
isolate_.traced_handles()->IterateYoungRootsWithOldHostsForTesting(
&visitor);
const auto young_wrappers_after = visitor.get_wrappers();
EXPECT_EQ(young_wrappers_before_, young_wrappers_after);
}
private:
Isolate& isolate_;
YoungWrapperCollector::YoungWrappers young_wrappers_before_;
};
} // namespace
class YoungUnifiedHeapTest : public MinorMSEnabler, public UnifiedHeapTest {
public:
YoungUnifiedHeapTest() {
// Enable young generation flag and run GC. After the first run the heap
// will enable minor GC.
CollectGarbageWithoutEmbedderStack();
}
};
TEST_F(YoungUnifiedHeapTest, OnlyGC) { CollectYoungGarbageWithEmbedderStack(); }
TEST_F(YoungUnifiedHeapTest, CollectUnreachableCppGCObject) {
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context(), nullptr);
EXPECT_FALSE(api_object.IsEmpty());
Wrappable::destructor_callcount = 0;
CollectYoungGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(1u, Wrappable::destructor_callcount);
}
TEST_F(YoungUnifiedHeapTest, FindingV8ToCppGCReference) {
auto* wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context(), wrappable_object);
EXPECT_FALSE(api_object.IsEmpty());
// With direct locals, api_object may be invalid after a stackless GC.
auto handle_api_object = v8::Utils::OpenIndirectHandle(*api_object);
Wrappable::destructor_callcount = 0;
CollectYoungGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(0u, Wrappable::destructor_callcount);
WrapperHelper::ResetWrappableConnection(
v8_isolate(), v8::Utils::ToLocal(handle_api_object));
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(1u, Wrappable::destructor_callcount);
}
TEST_F(YoungUnifiedHeapTest, FindingCppGCToV8Reference) {
auto* wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
{
v8::HandleScope inner_handle_scope(v8_isolate());
auto local = v8::Object::New(v8_isolate());
EXPECT_TRUE(local->IsObject());
wrappable_object->SetWrapper(v8_isolate(), local);
}
CollectYoungGarbageWithEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
auto local = wrappable_object->wrapper().Get(v8_isolate());
EXPECT_TRUE(local->IsObject());
}
TEST_F(YoungUnifiedHeapTest, GenerationalBarrierV8ToCppGCReference) {
if (i::v8_flags.single_generation) return;
FlagScope<bool> no_incremental_marking(&v8_flags.incremental_marking, false);
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context(), nullptr);
// With direct locals, api_object may be invalid after a stackless GC.
auto handle_api_object = v8::Utils::OpenIndirectHandle(*api_object);
EXPECT_TRUE(HeapLayout::InYoungGeneration(*handle_api_object));
InvokeMemoryReducingMajorGCs();
EXPECT_EQ(0u, Wrappable::destructor_callcount);
EXPECT_FALSE(HeapLayout::InYoungGeneration(*handle_api_object));
auto* wrappable = cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
WrapperHelper::SetWrappableConnection(
v8_isolate(), v8::Utils::ToLocal(handle_api_object), wrappable);
Wrappable::destructor_callcount = 0;
CollectYoungGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(0u, Wrappable::destructor_callcount);
}
TEST_F(YoungUnifiedHeapTest,
GenerationalBarrierCppGCToV8NoInitializingStoreBarrier) {
if (i::v8_flags.single_generation) return;
FlagScope<bool> no_incremental_marking(&v8_flags.incremental_marking, false);
auto local = v8::Object::New(v8_isolate());
{
ExpectCppGCToV8NoGenerationalBarrier expect_no_barrier(*v8_isolate());
auto* wrappable = cppgc::MakeGarbageCollected<Wrappable>(
allocation_handle(), v8_isolate(), local);
auto* copied_wrappable =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle(), *wrappable);
auto* moved_wrappable = cppgc::MakeGarbageCollected<Wrappable>(
allocation_handle(), std::move(*wrappable));
USE(moved_wrappable);
USE(copied_wrappable);
USE(wrappable);
}
}
TEST_F(YoungUnifiedHeapTest, GenerationalBarrierCppGCToV8ReferenceReset) {
if (i::v8_flags.single_generation) return;
FlagScope<bool> no_incremental_marking(&v8_flags.incremental_marking, false);
cppgc::Persistent<Wrappable> wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
EXPECT_TRUE(IsHeapObjectYoung(wrappable_object.Get()));
InvokeMemoryReducingMajorGCs();
EXPECT_EQ(0u, Wrappable::destructor_callcount);
EXPECT_TRUE(IsHeapObjectOld(wrappable_object.Get()));
{
v8::HandleScope inner_handle_scope(v8_isolate());
auto local = v8::Object::New(v8_isolate());
EXPECT_TRUE(local->IsObject());
{
ExpectCppGCToV8GenerationalBarrierToFire expect_barrier(
*v8_isolate(), {i::ValueHelper::ValueAsAddress(*local)});
wrappable_object->SetWrapper(v8_isolate(), local);
}
}
CollectYoungGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
auto local = wrappable_object->wrapper().Get(v8_isolate());
EXPECT_TRUE(local->IsObject());
}
TEST_F(YoungUnifiedHeapTest, GenerationalBarrierCppGCToV8ReferenceCopy) {
if (i::v8_flags.single_generation) return;
FlagScope<bool> no_incremental_marking(&v8_flags.incremental_marking, false);
cppgc::Persistent<Wrappable> wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
EXPECT_TRUE(IsHeapObjectYoung(wrappable_object.Get()));
InvokeMemoryReducingMajorGCs();
EXPECT_EQ(0u, Wrappable::destructor_callcount);
EXPECT_TRUE(IsHeapObjectOld(wrappable_object.Get()));
{
v8::HandleScope inner_handle_scope(v8_isolate());
auto local = v8::Object::New(v8_isolate());
EXPECT_TRUE(local->IsObject());
Wrappable* another_wrappable_object = nullptr;
{
// Assign to young host and expect no barrier.
ExpectCppGCToV8NoGenerationalBarrier expect_no_barrier(*v8_isolate());
another_wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
another_wrappable_object->SetWrapper(v8_isolate(), local);
}
{
// Assign to old object using TracedReference::operator= and expect
// the barrier to trigger.
ExpectCppGCToV8GenerationalBarrierToFire expect_barrier(
*v8_isolate(), {i::ValueHelper::ValueAsAddress(*local)});
*wrappable_object = *another_wrappable_object;
}
}
CollectYoungGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
auto local = wrappable_object->wrapper().Get(v8_isolate());
EXPECT_TRUE(local->IsObject());
}
TEST_F(YoungUnifiedHeapTest, GenerationalBarrierCppGCToV8ReferenceMove) {
if (i::v8_flags.single_generation) return;
FlagScope<bool> no_incremental_marking(&v8_flags.incremental_marking, false);
cppgc::Persistent<Wrappable> wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
EXPECT_TRUE(IsHeapObjectYoung(wrappable_object.Get()));
InvokeMemoryReducingMajorGCs();
EXPECT_EQ(0u, Wrappable::destructor_callcount);
EXPECT_TRUE(IsHeapObjectOld(wrappable_object.Get()));
{
v8::HandleScope inner_handle_scope(v8_isolate());
auto local = v8::Object::New(v8_isolate());
EXPECT_TRUE(local->IsObject());
Wrappable* another_wrappable_object = nullptr;
{
// Assign to young host and expect no barrier.
ExpectCppGCToV8NoGenerationalBarrier expect_no_barrier(*v8_isolate());
another_wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
another_wrappable_object->SetWrapper(v8_isolate(), local);
}
{
// Assign to old object using TracedReference::operator= and expect
// the barrier to trigger.
ExpectCppGCToV8GenerationalBarrierToFire expect_barrier(
*v8_isolate(), {i::ValueHelper::ValueAsAddress(*local)});
*wrappable_object = std::move(*another_wrappable_object);
}
}
CollectYoungGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
auto local = wrappable_object->wrapper().Get(v8_isolate());
EXPECT_TRUE(local->IsObject());
}
} // namespace internal
} // namespace v8
#endif // defined(CPPGC_YOUNG_GENERATION)