// 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 context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); v8::TracedReference ref; { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::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 context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref(v8_isolate(), local); EXPECT_FALSE(ref.IsEmpty()); EXPECT_EQ(ref, local); } } TEST_F(TracedReferenceTest, Reset) { v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference 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 context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref(v8_isolate(), local); v8::TracedReference ref_copy1(ref); v8::TracedReference ref_copy2 = ref; EXPECT_EQ(ref, local); EXPECT_EQ(ref_copy1, local); EXPECT_EQ(ref_copy2, local); } } TEST_F(TracedReferenceTest, CopyHeterogenous) { v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref(v8_isolate(), local); v8::TracedReference ref_copy1(ref); v8::TracedReference ref_copy2 = ref; EXPECT_EQ(ref, local); EXPECT_EQ(ref_copy1, local); EXPECT_EQ(ref_copy2, local); } } TEST_F(TracedReferenceTest, Move) { v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref(v8_isolate(), local); v8::TracedReference ref_moved1(std::move(ref)); v8::TracedReference 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 context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref1(v8_isolate(), local); v8::TracedReference ref_moved1(std::move(ref1)); v8::TracedReference ref2(v8_isolate(), local); v8::TracedReference 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 context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local1 = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref1(v8_isolate(), local1); v8::TracedReference ref2(v8_isolate(), local1); EXPECT_EQ(ref1, ref2); EXPECT_EQ(ref2, ref1); v8::Local local2 = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref3(v8_isolate(), local2); EXPECT_NE(ref2, ref3); EXPECT_NE(ref3, ref2); } } TEST_F(TracedReferenceTest, EqualityHeterogenous) { v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local1 = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref1(v8_isolate(), local1); v8::TracedReference ref2(v8_isolate(), local1); EXPECT_EQ(ref1, ref2); EXPECT_EQ(ref2, ref1); v8::Local local2 = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference 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 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 expected_object_; size_t visit_count_ = 0; }; } // namespace TEST_F(TracedReferenceTest, TracedReferenceTrace) { v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference 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(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 context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); auto ref = std::make_unique>(v8_isolate(), local); USE(ref); EXPECT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } TEST_F(TracedReferenceTest, WriteBarrierForOnHeapReset) { if (!v8_flags.incremental_marking) GTEST_SKIP() << "Write barrier tests require incremental marking"; v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); auto ref = std::make_unique>(); SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); ref->Reset(v8_isolate(), local); EXPECT_FALSE( state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } TEST_F(TracedReferenceTest, WriteBarrierForOnStackReset) { if (!v8_flags.incremental_marking) GTEST_SKIP() << "Write barrier tests require incremental marking"; v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); v8::TracedReference ref; SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); ref.Reset(v8_isolate(), local); EXPECT_FALSE( state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } TEST_F(TracedReferenceTest, WriteBarrierOnHeapCopy) { if (!v8_flags.incremental_marking) GTEST_SKIP() << "Write barrier tests require incremental marking"; v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); auto ref_from = std::make_unique>(v8_isolate(), local); auto ref_to = std::make_unique>(); SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); *ref_to = *ref_from; EXPECT_TRUE(!ref_from->IsEmpty()); EXPECT_FALSE( state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } TEST_F(TracedReferenceTest, WriteBarrierForOnStackCopy) { if (!v8_flags.incremental_marking) GTEST_SKIP() << "Write barrier tests require incremental marking"; v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); auto ref_from = std::make_unique>(v8_isolate(), local); v8::TracedReference ref_to; SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); ref_to = *ref_from; EXPECT_TRUE(!ref_from->IsEmpty()); EXPECT_FALSE( state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } TEST_F(TracedReferenceTest, WriteBarrierForOnHeapMove) { if (!v8_flags.incremental_marking) GTEST_SKIP() << "Write barrier tests require incremental marking"; v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); auto ref_from = std::make_unique>(v8_isolate(), local); auto ref_to = std::make_unique>(); SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); *ref_to = std::move(*ref_from); ASSERT_TRUE(ref_from->IsEmpty()); EXPECT_FALSE( state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } TEST_F(TracedReferenceTest, WriteBarrierForOnStackMove) { if (!v8_flags.incremental_marking) GTEST_SKIP() << "Write barrier tests require incremental marking"; v8::Local context = v8::Context::New(v8_isolate()); v8::Context::Scope context_scope(context); { v8::HandleScope handles(v8_isolate()); v8::Local local = v8::Local::New(v8_isolate(), v8::Object::New(v8_isolate())); auto ref_from = std::make_unique>(v8_isolate(), local); v8::TracedReference ref_to; SimulateIncrementalMarking(); MarkingState state(i_isolate()); ASSERT_TRUE(state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); ref_to = std::move(*ref_from); ASSERT_TRUE(ref_from->IsEmpty()); EXPECT_FALSE( state.IsUnmarked(HeapObject::cast(*Utils::OpenHandle(*local)))); } } } // namespace internal } // namespace v8