// Copyright 2023 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 "src/heap/heap.h" #include "src/heap/local-heap.h" #include "src/heap/parked-scope-inl.h" #include "test/unittests/heap/heap-utils.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { using DirectHandlesTest = TestWithIsolate; TEST_F(DirectHandlesTest, CreateDirectHandleFromLocal) { HandleScope scope(isolate()); Local foo = String::NewFromUtf8Literal(isolate(), "foo"); i::DirectHandle direct = Utils::OpenDirectHandle(*foo); i::IndirectHandle handle = Utils::OpenIndirectHandle(*foo); EXPECT_EQ(*direct, *handle); } TEST_F(DirectHandlesTest, CreateLocalFromDirectHandle) { HandleScope scope(isolate()); i::IndirectHandle handle = i_isolate()->factory()->NewStringFromAsciiChecked("foo"); i::DirectHandle direct = handle; Local l1 = Utils::ToLocal(direct); Local l2 = Utils::ToLocal(handle); EXPECT_EQ(l1, l2); } TEST_F(DirectHandlesTest, CreateMaybeDirectHandle) { HandleScope scope(isolate()); i::Handle handle = i_isolate()->factory()->NewStringFromAsciiChecked("foo"); i::DirectHandle direct = handle; i::MaybeDirectHandle maybe_direct(direct); i::MaybeIndirectHandle maybe_handle(handle); EXPECT_EQ(*maybe_direct.ToHandleChecked(), *maybe_handle.ToHandleChecked()); } TEST_F(DirectHandlesTest, CreateMaybeDirectObjectHandle) { HandleScope scope(isolate()); i::IndirectHandle handle = i_isolate()->factory()->NewStringFromAsciiChecked("foo"); i::DirectHandle direct = handle; i::MaybeObjectDirectHandle maybe_direct(direct); i::MaybeObjectIndirectHandle maybe_handle(handle); EXPECT_EQ(*maybe_direct, *maybe_handle); } TEST_F(DirectHandlesTest, IsIdenticalTo) { i::DirectHandle d1 = i_isolate()->factory()->NewStringFromAsciiChecked("foo"); i::DirectHandle d2(d1); i::DirectHandle d3 = i_isolate()->factory()->NewStringFromAsciiChecked("bar"); i::DirectHandle d4; i::DirectHandle d5; EXPECT_TRUE(d1.is_identical_to(d2)); EXPECT_TRUE(d2.is_identical_to(d1)); EXPECT_FALSE(d1.is_identical_to(d3)); EXPECT_FALSE(d1.is_identical_to(d4)); EXPECT_FALSE(d4.is_identical_to(d1)); EXPECT_TRUE(d4.is_identical_to(d5)); } TEST_F(DirectHandlesTest, MaybeObjectDirectHandleIsIdenticalTo) { i::DirectHandle foo = i_isolate()->factory()->NewStringFromAsciiChecked("foo"); i::DirectHandle bar = i_isolate()->factory()->NewStringFromAsciiChecked("bar"); i::MaybeObjectDirectHandle d1(foo); i::MaybeObjectDirectHandle d2(foo); i::MaybeObjectDirectHandle d3(bar); i::MaybeObjectDirectHandle d4; i::MaybeObjectDirectHandle d5; EXPECT_TRUE(d1.is_identical_to(d2)); EXPECT_TRUE(d2.is_identical_to(d1)); EXPECT_FALSE(d1.is_identical_to(d3)); EXPECT_FALSE(d1.is_identical_to(d4)); EXPECT_FALSE(d4.is_identical_to(d1)); EXPECT_TRUE(d4.is_identical_to(d5)); } // Tests to check DirectHandle usage. // Such usage violations are only detected in debug builds with slow DCHECKs. #ifdef ENABLE_SLOW_DCHECKS namespace { template void ExpectFailure(Callback callback) { EXPECT_DEATH_IF_SUPPORTED(callback(), ""); } } // anonymous namespace // Out-of-stack allocation of direct handles should fail. TEST_F(DirectHandlesTest, DirectHandleOutOfStackFailsDefault) { ExpectFailure([]() { // Default constructor. auto ptr = std::make_unique>(); USE(ptr); }); } TEST_F(DirectHandlesTest, DirectHandleOutOfStackFailsInit) { ExpectFailure([isolate = i_isolate()]() { i::Tagged object; // Constructor with initialization. auto ptr = std::make_unique>(object, isolate); USE(ptr); }); } TEST_F(DirectHandlesTest, DirectHandleOutOfStackFailsCopy) { ExpectFailure([]() { i::DirectHandle h; // Copy constructor. auto ptr = std::make_unique>(h); USE(ptr); }); } TEST_F(DirectHandlesTest, DirectHandleOutOfStackFailsCopyHeteroDirect) { ExpectFailure([]() { i::DirectHandle h; // Copy of heterogeneous direct handle. auto ptr = std::make_unique>(h); USE(ptr); }); } TEST_F(DirectHandlesTest, DirectHandleOutOfStackFailsCopyHeteroIndirect) { ExpectFailure([]() { i::IndirectHandle h; // Copy of heterogeneous indirect handle. auto ptr = std::make_unique>(h); USE(ptr); }); } namespace { class BackgroundThread final : public v8::base::Thread { public: explicit BackgroundThread(i::Isolate* isolate, bool park_and_wait) : v8::base::Thread(base::Thread::Options("BackgroundThread")), isolate_(isolate), park_and_wait_(park_and_wait) {} void Run() override { i::LocalIsolate isolate(isolate_, i::ThreadKind::kBackground); i::UnparkedScope unparked_scope(&isolate); i::LocalHandleScope handle_scope(&isolate); // Using a direct handle when unparked is allowed. i::DirectHandle direct = isolate.factory()->empty_string(); // Park and wait, if we must. if (park_and_wait_) { // Parking a background thread through the trampoline while holding a // direct handle is also allowed. isolate.heap()->ExecuteWhileParked([]() { // nothing }); } // Keep the direct handle alive. CHECK_EQ(0, direct->length()); } private: i::Isolate* isolate_; bool park_and_wait_; }; } // anonymous namespace TEST_F(DirectHandlesTest, DirectHandleInBackgroundThread) { i::LocalHeap lh(i_isolate()->heap(), i::ThreadKind::kMain); lh.SetUpMainThreadForTesting(); auto thread = std::make_unique(i_isolate(), false); CHECK(thread->Start()); thread->Join(); } TEST_F(DirectHandlesTest, DirectHandleInParkedBackgroundThread) { i::LocalHeap lh(i_isolate()->heap(), i::ThreadKind::kMain); lh.SetUpMainThreadForTesting(); auto thread = std::make_unique(i_isolate(), true); CHECK(thread->Start()); thread->Join(); } #if V8_CAN_CREATE_SHARED_HEAP_BOOL using DirectHandlesSharedTest = i::TestJSSharedMemoryWithIsolate; namespace { class ClientThread final : public i::ParkingThread { public: ClientThread() : ParkingThread(base::Thread::Options("ClientThread")) {} void Run() override { IsolateWrapper isolate_wrapper(kNoCounters); // Direct handles can be used in the main thread of client isolates. i::DirectHandle direct; USE(direct); } }; } // anonymous namespace TEST_F(DirectHandlesSharedTest, DirectHandleInClient) { auto thread = std::make_unique(); CHECK(thread->Start()); thread->ParkedJoin(i_isolate()->main_thread_local_isolate()); } namespace { class ClientMainThread final : public i::ParkingThread { public: explicit ClientMainThread(bool background_park_and_wait) : ParkingThread(base::Thread::Options("ClientMainThread")), background_park_and_wait_(background_park_and_wait) {} void Run() override { IsolateWrapper isolate_wrapper(kNoCounters); i::Isolate* i_client_isolate = reinterpret_cast(isolate_wrapper.isolate()); i::LocalHeap lh(i_client_isolate->heap(), i::ThreadKind::kMain); lh.SetUpMainThreadForTesting(); auto thread = std::make_unique(i_client_isolate, background_park_and_wait_); CHECK(thread->Start()); thread->Join(); } private: bool background_park_and_wait_; }; } // anonymous namespace TEST_F(DirectHandlesSharedTest, DirectHandleInClientBackgroundThread) { auto thread = std::make_unique(false); CHECK(thread->Start()); thread->ParkedJoin(i_isolate()->main_thread_local_isolate()); } TEST_F(DirectHandlesSharedTest, DirectHandleInParkedClientBackgroundThread) { auto thread = std::make_unique(true); CHECK(thread->Start()); thread->ParkedJoin(i_isolate()->main_thread_local_isolate()); } #endif // V8_CAN_CREATE_SHARED_HEAP_BOOL #endif // ENABLE_SLOW_DCHECKS using DirectHandlesContainerTest = DirectHandlesTest; namespace { template void TestContainerOfDirectHandles(i::Isolate* isolate, Container& container, int capacity) { Isolate* v8_isolate = reinterpret_cast(isolate); Local context = Context::New(v8_isolate); Context::Scope scope(context); for (int i = 0; i < capacity; ++i) { std::ostringstream os; os << i; container.push_back( isolate->factory()->NewStringFromAsciiChecked(os.str().c_str())); } EXPECT_EQ(static_cast(capacity), container.size()); for (i::DirectHandle string : container) { EXPECT_FALSE(string.is_null()); } for (int i = 0; i < capacity; ++i) { Local string = Utils::ToLocal(container[i]); EXPECT_EQ(i, string->ToNumber(context).ToLocalChecked()->Value()); } } void VerifyNoRemainingDirectHandles() { #if defined(V8_ENABLE_DIRECT_HANDLE) && defined(ENABLE_SLOW_DCHECKS) DCHECK_EQ(0, i::DirectHandleBase::NumberOfHandles()); #endif } } // anonymous namespace TEST_F(DirectHandlesContainerTest, Vector) { { HandleScope scope(isolate()); i::DirectHandleVector vec(i_isolate()); TestContainerOfDirectHandles(i_isolate(), vec, 42); } VerifyNoRemainingDirectHandles(); } TEST_F(DirectHandlesContainerTest, SmallVectorSmall) { { HandleScope scope(isolate()); i::DirectHandleSmallVector vec(i_isolate()); TestContainerOfDirectHandles(i_isolate(), vec, 7); } VerifyNoRemainingDirectHandles(); } TEST_F(DirectHandlesContainerTest, SmallVectorBig) { { HandleScope scope(isolate()); i::DirectHandleSmallVector vec(i_isolate()); TestContainerOfDirectHandles(i_isolate(), vec, 42); } VerifyNoRemainingDirectHandles(); } } // namespace v8