// Copyright 2018 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 "src/execution/isolate.h" #include "src/handles/handles-inl.h" #include "src/heap/factory-inl.h" #include "src/heap/heap-inl.h" #include "src/objects/js-objects.h" #include "src/objects/js-weak-refs-inl.h" #include "test/cctest/cctest.h" #include "test/cctest/heap/heap-utils.h" namespace v8 { namespace internal { namespace { Handle ConstructJSFinalizationRegistry( Isolate* isolate) { Factory* factory = isolate->factory(); Handle finalization_registry_name = factory->NewStringFromStaticChars("FinalizationRegistry"); Handle global = handle(isolate->native_context()->global_object(), isolate); Handle finalization_registry_fun = Handle::cast( Object::GetProperty(isolate, global, finalization_registry_name) .ToHandleChecked()); auto finalization_registry = Handle::cast( JSObject::New(finalization_registry_fun, finalization_registry_fun, Handle::null()) .ToHandleChecked()); // JSObject::New filled all of the internal fields with undefined. Some of // them have more restrictive types, so set those now. finalization_registry->set_native_context(*isolate->native_context()); finalization_registry->set_cleanup( isolate->native_context()->empty_function()); finalization_registry->set_flags(0); #ifdef VERIFY_HEAP finalization_registry->JSFinalizationRegistryVerify(isolate); #endif // VERIFY_HEAP return finalization_registry; } Handle ConstructJSWeakRef(Handle target, Isolate* isolate) { Factory* factory = isolate->factory(); Handle weak_ref_name = factory->WeakRef_string(); Handle global = handle(isolate->native_context()->global_object(), isolate); Handle weak_ref_fun = Handle::cast( Object::GetProperty(isolate, global, weak_ref_name).ToHandleChecked()); auto weak_ref = Handle::cast( JSObject::New(weak_ref_fun, weak_ref_fun, Handle::null()) .ToHandleChecked()); weak_ref->set_target(*target); #ifdef VERIFY_HEAP weak_ref->JSWeakRefVerify(isolate); #endif // VERIFY_HEAP return weak_ref; } Handle CreateKey(const char* key_prop_value, Isolate* isolate) { Factory* factory = isolate->factory(); Handle key_string = factory->NewStringFromStaticChars("key_string"); Handle key = isolate->factory()->NewJSObject(isolate->object_function()); JSObject::AddProperty(isolate, key, key_string, factory->NewStringFromAsciiChecked(key_prop_value), NONE); return key; } Handle FinalizationRegistryRegister( Handle finalization_registry, Handle target, Handle held_value, Handle unregister_token, Isolate* isolate) { Factory* factory = isolate->factory(); Handle regfunc = Handle::cast( Object::GetProperty(isolate, finalization_registry, factory->NewStringFromStaticChars("register")) .ToHandleChecked()); Handle args[] = {target, held_value, unregister_token}; Execution::Call(isolate, regfunc, finalization_registry, arraysize(args), args) .ToHandleChecked(); CHECK(finalization_registry->active_cells().IsWeakCell()); Handle weak_cell = handle(WeakCell::cast(finalization_registry->active_cells()), isolate); #ifdef VERIFY_HEAP weak_cell->WeakCellVerify(isolate); #endif // VERIFY_HEAP return weak_cell; } Handle FinalizationRegistryRegister( Handle finalization_registry, Handle target, Isolate* isolate) { Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); return FinalizationRegistryRegister(finalization_registry, target, undefined, undefined, isolate); } void NullifyWeakCell(Handle weak_cell, Isolate* isolate) { auto empty_func = [](HeapObject object, ObjectSlot slot, Object target) {}; weak_cell->Nullify(isolate, empty_func); #ifdef VERIFY_HEAP weak_cell->WeakCellVerify(isolate); #endif // VERIFY_HEAP } Object PopClearedCellHoldings( Handle finalization_registry, Isolate* isolate) { // PopClearedCell is implemented in Torque. Reproduce that implementation here // for testing. Handle weak_cell = handle(WeakCell::cast(finalization_registry->cleared_cells()), isolate); DCHECK(weak_cell->prev().IsUndefined(isolate)); finalization_registry->set_cleared_cells(weak_cell->next()); weak_cell->set_next(ReadOnlyRoots(isolate).undefined_value()); if (finalization_registry->cleared_cells().IsWeakCell()) { WeakCell cleared_cells_head = WeakCell::cast(finalization_registry->cleared_cells()); DCHECK_EQ(cleared_cells_head.prev(), *weak_cell); cleared_cells_head.set_prev(ReadOnlyRoots(isolate).undefined_value()); } else { DCHECK(finalization_registry->cleared_cells().IsUndefined(isolate)); } if (!weak_cell->unregister_token().IsUndefined(isolate)) { JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap( isolate, finalization_registry->ptr(), weak_cell->ptr()); } return weak_cell->holdings(); } // Usage: VerifyWeakCellChain(isolate, list_head, n, cell1, cell2, ..., celln); // verifies that list_head == cell1 and cell1, cell2, ..., celln. form a list. void VerifyWeakCellChain(Isolate* isolate, Object list_head, int n_args, ...) { CHECK_GE(n_args, 0); va_list args; va_start(args, n_args); if (n_args == 0) { // Verify empty list CHECK(list_head.IsUndefined(isolate)); } else { WeakCell current = WeakCell::cast(Object(va_arg(args, Address))); CHECK_EQ(current, list_head); CHECK(current.prev().IsUndefined(isolate)); for (int i = 1; i < n_args; i++) { WeakCell next = WeakCell::cast(Object(va_arg(args, Address))); CHECK_EQ(current.next(), next); CHECK_EQ(next.prev(), current); current = next; } CHECK(current.next().IsUndefined(isolate)); } va_end(args); } // Like VerifyWeakCellChain but verifies the chain created with key_list_prev // and key_list_next instead of prev and next. void VerifyWeakCellKeyChain(Isolate* isolate, SimpleNumberDictionary key_map, Object unregister_token, int n_args, ...) { CHECK_GE(n_args, 0); va_list args; va_start(args, n_args); Object hash = unregister_token.GetHash(); InternalIndex entry = InternalIndex::NotFound(); if (!hash.IsUndefined(isolate)) { uint32_t key = Smi::ToInt(hash); entry = key_map.FindEntry(isolate, key); } if (n_args == 0) { // Verify empty list CHECK(entry.is_not_found()); } else { CHECK(entry.is_found()); WeakCell current = WeakCell::cast(Object(va_arg(args, Address))); Object list_head = key_map.ValueAt(entry); CHECK_EQ(current, list_head); CHECK(current.key_list_prev().IsUndefined(isolate)); for (int i = 1; i < n_args; i++) { WeakCell next = WeakCell::cast(Object(va_arg(args, Address))); CHECK_EQ(current.key_list_next(), next); CHECK_EQ(next.key_list_prev(), current); current = next; } CHECK(current.key_list_next().IsUndefined(isolate)); } va_end(args); } Handle MakeWeakRefAndKeepDuringJob(Isolate* isolate) { HandleScope inner_scope(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle inner_weak_ref = ConstructJSWeakRef(js_object, isolate); isolate->heap()->KeepDuringJob(js_object); return inner_scope.CloseAndEscape(inner_weak_ref); } } // namespace TEST(TestRegister) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); // Register a weak reference and verify internal data structures. Handle weak_cell1 = FinalizationRegistryRegister(finalization_registry, js_object, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 1, *weak_cell1); CHECK(weak_cell1->key_list_prev().IsUndefined(isolate)); CHECK(weak_cell1->key_list_next().IsUndefined(isolate)); CHECK(finalization_registry->cleared_cells().IsUndefined(isolate)); // No key was used during registration, key-based map stays uninitialized. CHECK(finalization_registry->key_map().IsUndefined(isolate)); // Register another weak reference and verify internal data structures. Handle weak_cell2 = FinalizationRegistryRegister(finalization_registry, js_object, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 2, *weak_cell2, *weak_cell1); CHECK(weak_cell2->key_list_prev().IsUndefined(isolate)); CHECK(weak_cell2->key_list_next().IsUndefined(isolate)); CHECK(finalization_registry->cleared_cells().IsUndefined(isolate)); CHECK(finalization_registry->key_map().IsUndefined(isolate)); } TEST(TestRegisterWithKey) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle token2 = CreateKey("token2", isolate); Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); // Register a weak reference with a key and verify internal data structures. Handle weak_cell1 = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1); VerifyWeakCellKeyChain(isolate, key_map, *token2, 0); } // Register another weak reference with a different key and verify internal // data structures. Handle weak_cell2 = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1); VerifyWeakCellKeyChain(isolate, key_map, *token2, 1, *weak_cell2); } // Register another weak reference with token1 and verify internal data // structures. Handle weak_cell3 = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell3, *weak_cell1); VerifyWeakCellKeyChain(isolate, key_map, *token2, 1, *weak_cell2); } } TEST(TestWeakCellNullify1) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle weak_cell1 = FinalizationRegistryRegister(finalization_registry, js_object, isolate); Handle weak_cell2 = FinalizationRegistryRegister(finalization_registry, js_object, isolate); // Nullify the first WeakCell and verify internal data structures. NullifyWeakCell(weak_cell1, isolate); CHECK_EQ(finalization_registry->active_cells(), *weak_cell2); CHECK(weak_cell2->prev().IsUndefined(isolate)); CHECK(weak_cell2->next().IsUndefined(isolate)); CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell1); CHECK(weak_cell1->prev().IsUndefined(isolate)); CHECK(weak_cell1->next().IsUndefined(isolate)); // Nullify the second WeakCell and verify internal data structures. NullifyWeakCell(weak_cell2, isolate); CHECK(finalization_registry->active_cells().IsUndefined(isolate)); CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell2); CHECK_EQ(weak_cell2->next(), *weak_cell1); CHECK(weak_cell2->prev().IsUndefined(isolate)); CHECK_EQ(weak_cell1->prev(), *weak_cell2); CHECK(weak_cell1->next().IsUndefined(isolate)); } TEST(TestWeakCellNullify2) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle weak_cell1 = FinalizationRegistryRegister(finalization_registry, js_object, isolate); Handle weak_cell2 = FinalizationRegistryRegister(finalization_registry, js_object, isolate); // Like TestWeakCellNullify1 but nullify the WeakCells in opposite order. NullifyWeakCell(weak_cell2, isolate); CHECK_EQ(finalization_registry->active_cells(), *weak_cell1); CHECK(weak_cell1->prev().IsUndefined(isolate)); CHECK(weak_cell1->next().IsUndefined(isolate)); CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell2); CHECK(weak_cell2->prev().IsUndefined(isolate)); CHECK(weak_cell2->next().IsUndefined(isolate)); NullifyWeakCell(weak_cell1, isolate); CHECK(finalization_registry->active_cells().IsUndefined(isolate)); CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell1); CHECK_EQ(weak_cell1->next(), *weak_cell2); CHECK(weak_cell1->prev().IsUndefined(isolate)); CHECK_EQ(weak_cell2->prev(), *weak_cell1); CHECK(weak_cell2->next().IsUndefined(isolate)); } TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); Handle holdings1 = factory->NewStringFromAsciiChecked("holdings1"); Handle weak_cell1 = FinalizationRegistryRegister( finalization_registry, js_object, holdings1, undefined, isolate); Handle holdings2 = factory->NewStringFromAsciiChecked("holdings2"); Handle weak_cell2 = FinalizationRegistryRegister( finalization_registry, js_object, holdings2, undefined, isolate); Handle holdings3 = factory->NewStringFromAsciiChecked("holdings3"); Handle weak_cell3 = FinalizationRegistryRegister( finalization_registry, js_object, holdings3, undefined, isolate); NullifyWeakCell(weak_cell2, isolate); NullifyWeakCell(weak_cell3, isolate); CHECK(finalization_registry->NeedsCleanup()); Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate); CHECK_EQ(cleared1, *holdings3); CHECK(weak_cell3->prev().IsUndefined(isolate)); CHECK(weak_cell3->next().IsUndefined(isolate)); CHECK(finalization_registry->NeedsCleanup()); Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate); CHECK_EQ(cleared2, *holdings2); CHECK(weak_cell2->prev().IsUndefined(isolate)); CHECK(weak_cell2->next().IsUndefined(isolate)); CHECK(!finalization_registry->NeedsCleanup()); NullifyWeakCell(weak_cell1, isolate); CHECK(finalization_registry->NeedsCleanup()); Object cleared3 = PopClearedCellHoldings(finalization_registry, isolate); CHECK_EQ(cleared3, *holdings1); CHECK(weak_cell1->prev().IsUndefined(isolate)); CHECK(weak_cell1->next().IsUndefined(isolate)); CHECK(!finalization_registry->NeedsCleanup()); CHECK(finalization_registry->active_cells().IsUndefined(isolate)); CHECK(finalization_registry->cleared_cells().IsUndefined(isolate)); } TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) { // Test that when all WeakCells for a key are popped, the key is removed from // the key map. CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle holdings1 = factory->NewStringFromAsciiChecked("holdings1"); Handle weak_cell1 = FinalizationRegistryRegister( finalization_registry, js_object, holdings1, token1, isolate); Handle holdings2 = factory->NewStringFromAsciiChecked("holdings2"); Handle weak_cell2 = FinalizationRegistryRegister( finalization_registry, js_object, holdings2, token1, isolate); NullifyWeakCell(weak_cell1, isolate); NullifyWeakCell(weak_cell2, isolate); // Nullifying doesn't affect the key chains (just moves WeakCells from // active_cells to cleared_cells). { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell2, *weak_cell1); } Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate); CHECK_EQ(cleared1, *holdings2); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1); } Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate); CHECK_EQ(cleared2, *holdings1); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 0); } } TEST(TestUnregisterActiveCells) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle token2 = CreateKey("token2", isolate); Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); Handle weak_cell1a = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); Handle weak_cell1b = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); Handle weak_cell2a = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); Handle weak_cell2b = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 4, *weak_cell2b, *weak_cell2a, *weak_cell1b, *weak_cell1a); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b, *weak_cell1a); VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b, *weak_cell2a); } JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 0); VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b, *weak_cell2a); } // Both weak_cell1a and weak_cell1b removed from active_cells. VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 2, *weak_cell2b, *weak_cell2a); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); } TEST(TestUnregisterActiveAndClearedCells) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle token2 = CreateKey("token2", isolate); Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); Handle weak_cell1a = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); Handle weak_cell1b = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); Handle weak_cell2a = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); Handle weak_cell2b = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); NullifyWeakCell(weak_cell2a, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 3, *weak_cell2b, *weak_cell1b, *weak_cell1a); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 1, *weak_cell2a); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b, *weak_cell1a); VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b, *weak_cell2a); } JSFinalizationRegistry::Unregister(finalization_registry, token2, isolate); // Both weak_cell2a and weak_cell2b removed. VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 2, *weak_cell1b, *weak_cell1a); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b, *weak_cell1a); VerifyWeakCellKeyChain(isolate, key_map, *token2, 0); } } TEST(TestWeakCellUnregisterTwice) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); Handle weak_cell1 = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 1, *weak_cell1); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1); } JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 0); } JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 0); } } TEST(TestWeakCellUnregisterPopped) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle holdings1 = factory->NewStringFromAsciiChecked("holdings1"); Handle weak_cell1 = FinalizationRegistryRegister( finalization_registry, js_object, holdings1, token1, isolate); NullifyWeakCell(weak_cell1, isolate); CHECK(finalization_registry->NeedsCleanup()); Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate); CHECK_EQ(cleared1, *holdings1); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 0); } JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 0); } } TEST(TestWeakCellUnregisterNonexistentKey) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle token1 = CreateKey("token1", isolate); JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate); } TEST(TestJSWeakRef) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); HandleScope outer_scope(isolate); Handle weak_ref; { HandleScope inner_scope(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); // This doesn't add the target into the KeepDuringJob set. Handle inner_weak_ref = ConstructJSWeakRef(js_object, isolate); CcTest::CollectAllGarbage(); CHECK(!inner_weak_ref->target().IsUndefined(isolate)); weak_ref = inner_scope.CloseAndEscape(inner_weak_ref); } CHECK(!weak_ref->target().IsUndefined(isolate)); CcTest::CollectAllGarbage(); CHECK(weak_ref->target().IsUndefined(isolate)); } TEST(TestJSWeakRefIncrementalMarking) { if (!v8_flags.incremental_marking) { return; } ManualGCScope manual_gc_scope; CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); Heap* heap = isolate->heap(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); HandleScope outer_scope(isolate); Handle weak_ref; { HandleScope inner_scope(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); // This doesn't add the target into the KeepDuringJob set. Handle inner_weak_ref = ConstructJSWeakRef(js_object, isolate); heap::SimulateIncrementalMarking(heap, true); CcTest::CollectAllGarbage(); CHECK(!inner_weak_ref->target().IsUndefined(isolate)); weak_ref = inner_scope.CloseAndEscape(inner_weak_ref); } CHECK(!weak_ref->target().IsUndefined(isolate)); heap::SimulateIncrementalMarking(heap, true); CcTest::CollectAllGarbage(); CHECK(weak_ref->target().IsUndefined(isolate)); } TEST(TestJSWeakRefKeepDuringJob) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); HandleScope outer_scope(isolate); Handle weak_ref = MakeWeakRefAndKeepDuringJob(isolate); CHECK(!weak_ref->target().IsUndefined(isolate)); CcTest::CollectAllGarbage(); CHECK(!weak_ref->target().IsUndefined(isolate)); // Clears the KeepDuringJob set. context->GetIsolate()->ClearKeptObjects(); CcTest::CollectAllGarbage(); CHECK(weak_ref->target().IsUndefined(isolate)); weak_ref = MakeWeakRefAndKeepDuringJob(isolate); CHECK(!weak_ref->target().IsUndefined(isolate)); CcTest::CollectAllGarbage(); CHECK(!weak_ref->target().IsUndefined(isolate)); // ClearKeptObjects should be called by PerformMicrotasksCheckpoint. CcTest::isolate()->PerformMicrotaskCheckpoint(); CcTest::CollectAllGarbage(); CHECK(weak_ref->target().IsUndefined(isolate)); weak_ref = MakeWeakRefAndKeepDuringJob(isolate); CHECK(!weak_ref->target().IsUndefined(isolate)); CcTest::CollectAllGarbage(); CHECK(!weak_ref->target().IsUndefined(isolate)); // ClearKeptObjects should be called by MicrotasksScope::PerformCheckpoint. v8::MicrotasksScope::PerformCheckpoint(CcTest::isolate()); CcTest::CollectAllGarbage(); CHECK(weak_ref->target().IsUndefined(isolate)); } TEST(TestJSWeakRefKeepDuringJobIncrementalMarking) { if (!v8_flags.incremental_marking) { return; } ManualGCScope manual_gc_scope; CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); Heap* heap = isolate->heap(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); HandleScope outer_scope(isolate); Handle weak_ref = MakeWeakRefAndKeepDuringJob(isolate); CHECK(!weak_ref->target().IsUndefined(isolate)); heap::SimulateIncrementalMarking(heap, true); CcTest::CollectAllGarbage(); CHECK(!weak_ref->target().IsUndefined(isolate)); // Clears the KeepDuringJob set. context->GetIsolate()->ClearKeptObjects(); heap::SimulateIncrementalMarking(heap, true); CcTest::CollectAllGarbage(); CHECK(weak_ref->target().IsUndefined(isolate)); } TEST(TestRemoveUnregisterToken) { CcTest::InitializeVM(); LocalContext context; Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); Handle finalization_registry = ConstructJSFinalizationRegistry(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle token1 = CreateKey("token1", isolate); Handle token2 = CreateKey("token2", isolate); Handle undefined = handle(ReadOnlyRoots(isolate).undefined_value(), isolate); Handle weak_cell1a = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); Handle weak_cell1b = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token1, isolate); Handle weak_cell2a = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); Handle weak_cell2b = FinalizationRegistryRegister( finalization_registry, js_object, undefined, token2, isolate); NullifyWeakCell(weak_cell2a, isolate); VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 3, *weak_cell2b, *weak_cell1b, *weak_cell1a); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 1, *weak_cell2a); { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b, *weak_cell1a); VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b, *weak_cell2a); } finalization_registry->RemoveUnregisterToken( JSReceiver::cast(*token2), isolate, JSFinalizationRegistry::kKeepMatchedCellsInRegistry, [](HeapObject, ObjectSlot, Object) {}); // Both weak_cell2a and weak_cell2b remain on the weak cell chains. VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 3, *weak_cell2b, *weak_cell1b, *weak_cell1a); VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 1, *weak_cell2a); // But both weak_cell2a and weak_cell2b are removed from the key chain. { SimpleNumberDictionary key_map = SimpleNumberDictionary::cast(finalization_registry->key_map()); VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b, *weak_cell1a); VerifyWeakCellKeyChain(isolate, key_map, *token2, 0); } } TEST(JSWeakRefScavengedInWorklist) { if (!v8_flags.incremental_marking || v8_flags.single_generation) { return; } ManualGCScope manual_gc_scope; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Heap* heap = isolate->heap(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); { HandleScope outer_scope(isolate); Handle weak_ref; // Make a WeakRef that points to a target, both of which become unreachable. { HandleScope inner_scope(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle inner_weak_ref = ConstructJSWeakRef(js_object, isolate); CHECK(Heap::InYoungGeneration(*js_object)); CHECK(Heap::InYoungGeneration(*inner_weak_ref)); weak_ref = inner_scope.CloseAndEscape(inner_weak_ref); } // Store weak_ref in Global such that it is part of the root set when // starting incremental marking. v8::Global global_weak_ref( CcTest::isolate(), Utils::ToLocal(Handle::cast(weak_ref))); // Do marking. This puts the WeakRef above into the js_weak_refs worklist // since its target isn't marked. CHECK( heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty()); heap::SimulateIncrementalMarking(heap, true); heap->mark_compact_collector()->local_weak_objects()->Publish(); CHECK(!heap->mark_compact_collector() ->weak_objects() ->js_weak_refs.IsEmpty()); } // Now collect both weak_ref and its target. The worklist should be empty. CcTest::CollectGarbage(NEW_SPACE); CHECK(heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty()); // The mark-compactor shouldn't see zapped WeakRefs in the worklist. CcTest::CollectAllGarbage(); } TEST(JSWeakRefTenuredInWorklist) { if (!v8_flags.incremental_marking || v8_flags.single_generation || v8_flags.separate_gc_phases) { return; } ManualGCScope manual_gc_scope; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Heap* heap = isolate->heap(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); HandleScope outer_scope(isolate); Handle weak_ref; // Make a WeakRef that points to a target. The target becomes unreachable. { HandleScope inner_scope(isolate); Handle js_object = isolate->factory()->NewJSObject(isolate->object_function()); Handle inner_weak_ref = ConstructJSWeakRef(js_object, isolate); CHECK(Heap::InYoungGeneration(*js_object)); CHECK(Heap::InYoungGeneration(*inner_weak_ref)); weak_ref = inner_scope.CloseAndEscape(inner_weak_ref); } // Store weak_ref such that it is part of the root set when starting // incremental marking. v8::Global global_weak_ref( CcTest::isolate(), Utils::ToLocal(Handle::cast(weak_ref))); JSWeakRef old_weak_ref_location = *weak_ref; // Do marking. This puts the WeakRef above into the js_weak_refs worklist // since its target isn't marked. CHECK(heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty()); heap::SimulateIncrementalMarking(heap, true); heap->mark_compact_collector()->local_weak_objects()->Publish(); CHECK( !heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty()); // Now collect weak_ref's target. We still have a Handle to weak_ref, so it is // moved and remains on the worklist. CcTest::CollectGarbage(NEW_SPACE); JSWeakRef new_weak_ref_location = *weak_ref; CHECK_NE(old_weak_ref_location, new_weak_ref_location); CHECK( !heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty()); // The mark-compactor should see the moved WeakRef in the worklist. CcTest::CollectAllGarbage(); CHECK(heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty()); CHECK(weak_ref->target().IsUndefined(isolate)); } TEST(UnregisterTokenHeapVerifier) { if (!v8_flags.incremental_marking) return; ManualGCScope manual_gc_scope; #ifdef VERIFY_HEAP v8_flags.verify_heap = true; #endif CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Heap* heap = CcTest::heap(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); v8::HandleScope outer_scope(isolate); { // Make a new FinalizationRegistry and register two objects with the same // unregister token that's unreachable after the IIFE returns. v8::HandleScope scope(isolate); CompileRun( "var token = {}; " "var registry = new FinalizationRegistry(function () {}); " "(function () { " " let o1 = {}; " " let o2 = {}; " " registry.register(o1, {}, token); " " registry.register(o2, {}, token); " "})();"); } // GC so the WeakCell corresponding to o is moved from the active_cells to // cleared_cells. CcTest::CollectAllGarbage(); CcTest::CollectAllGarbage(); { // Override the unregister token to make the original object collectible. v8::HandleScope scope(isolate); CompileRun("token = 0;"); } heap::SimulateIncrementalMarking(heap, true); // Pump message loop to run the finalizer task, then the incremental marking // task. The finalizer task will pop the WeakCell from the cleared list. This // should make the unregister_token slot undefined. That slot is iterated as a // custom weak pointer, so if it is not made undefined, the verifier as part // of the incremental marking task will crash. EmptyMessageQueues(isolate); } TEST(UnregisteredAndUnclearedCellHeapVerifier) { if (!v8_flags.incremental_marking) return; ManualGCScope manual_gc_scope; #ifdef VERIFY_HEAP v8_flags.verify_heap = true; #endif CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Heap* heap = CcTest::heap(); i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); v8::HandleScope outer_scope(isolate); { // Make a new FinalizationRegistry and register an object with a token. v8::HandleScope scope(isolate); CompileRun( "var token = {}; " "var registry = new FinalizationRegistry(function () {}); " "registry.register({}, undefined, token);"); } // Start incremental marking to activate the marking barrier. heap::SimulateIncrementalMarking(heap, false); { // Make a WeakCell list with length >1, then unregister with the token to // the WeakCell from the registry. The linked list manipulation keeps the // unregistered WeakCell alive (i.e. not put into cleared_cells) due to the // marking barrier from incremental marking. Then make the original token // collectible. v8::HandleScope scope(isolate); CompileRun( "registry.register({}); " "registry.unregister(token); " "token = 0;"); } // Trigger GC. CcTest::CollectAllGarbage(); CcTest::CollectAllGarbage(); // Pump message loop to run the finalizer task, then the incremental marking // task. The verifier will verify that live WeakCells don't point to dead // unregister tokens. EmptyMessageQueues(isolate); } } // namespace internal } // namespace v8