#include #include #include #include #include #include #include #include "node_test_fixture.h" // This tests that Node.js can work with an existing CppHeap. // Mimic a class that does not know about Node.js. class CppGCed : public cppgc::GarbageCollected { public: static int kConstructCount; static int kDestructCount; static int kTraceCount; static void New(const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); v8::Local js_object = args.This(); auto* heap = isolate->GetCppHeap(); CHECK_NOT_NULL(heap); CppGCed* gc_object = cppgc::MakeGarbageCollected(heap->GetAllocationHandle()); node::SetCppgcReference(isolate, js_object, gc_object); kConstructCount++; args.GetReturnValue().Set(js_object); } static v8::Local GetConstructor( v8::Local context) { auto ft = v8::FunctionTemplate::New(context->GetIsolate(), New); return ft->GetFunction(context).ToLocalChecked(); } CppGCed() = default; ~CppGCed() { kDestructCount++; } void Trace(cppgc::Visitor* visitor) const { kTraceCount++; } }; int CppGCed::kConstructCount = 0; int CppGCed::kDestructCount = 0; int CppGCed::kTraceCount = 0; TEST_F(NodeZeroIsolateTestFixture, ExistingCppHeapTest) { v8::Isolate* isolate = node::NewIsolate(allocator.get(), ¤t_loop, platform.get()); // Create and attach the CppHeap before we set up the IsolateData so that // it recognizes the existing heap. std::unique_ptr cpp_heap = v8::CppHeap::Create(platform.get(), v8::CppHeapCreateParams{{}}); // TODO(joyeecheung): pass it into v8::Isolate::CreateParams and let V8 // own it when we can keep the isolate registered/task runner discoverable // during isolate disposal. isolate->AttachCppHeap(cpp_heap.get()); // Try creating Context + IsolateData + Environment. { v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); std::unique_ptr isolate_data{ node::CreateIsolateData(isolate, ¤t_loop, platform.get()), node::FreeIsolateData}; CHECK(isolate_data); { auto context = node::NewContext(isolate); CHECK(!context.IsEmpty()); v8::Context::Scope context_scope(context); // An Environment should already contain a few BaseObjects that are // supposed to have non-cppgc IDs. std::unique_ptr environment{ node::CreateEnvironment(isolate_data.get(), context, {}, {}), node::FreeEnvironment}; CHECK(environment); context->Global() ->Set(context, v8::String::NewFromUtf8(isolate, "CppGCed").ToLocalChecked(), CppGCed::GetConstructor(context)) .FromJust(); const char* code = "globalThis.array = [];" "for (let i = 0; i < 100; ++i) { array.push(new CppGCed()); }"; node::LoadEnvironment(environment.get(), code).ToLocalChecked(); // Request a GC and check if CppGCed::Trace() is invoked. isolate->LowMemoryNotification(); int exit_code = SpinEventLoop(environment.get()).FromJust(); EXPECT_EQ(exit_code, 0); } platform->DrainTasks(isolate); // Cleanup. isolate->DetachCppHeap(); cpp_heap->Terminate(); platform->DrainTasks(isolate); } platform->UnregisterIsolate(isolate); isolate->Dispose(); // Check that all the objects are created and destroyed properly. EXPECT_EQ(CppGCed::kConstructCount, 100); EXPECT_EQ(CppGCed::kDestructCount, 100); // GC does not have to feel pressured enough to visit all of them to // reclaim memory before we are done with the isolate and the entire // heap can be reclaimed. So just check at least some of them are traced. EXPECT_GT(CppGCed::kTraceCount, 0); }