diff --git a/Tests/LibJS/test-js.cpp b/Tests/LibJS/test-js.cpp index 1927b67a39..1393d24d51 100644 --- a/Tests/LibJS/test-js.cpp +++ b/Tests/LibJS/test-js.cpp @@ -32,6 +32,19 @@ TESTJS_GLOBAL_FUNCTION(run_queued_promise_jobs, runQueuedPromiseJobs) return JS::js_undefined(); } +TESTJS_GLOBAL_FUNCTION(get_weak_set_size, getWeakSetSize) +{ + auto* object = vm.argument(0).to_object(global_object); + if (!object) + return {}; + if (!is(object)) { + vm.throw_exception(global_object, JS::ErrorType::NotA, "WeakSet"); + return {}; + } + auto* weak_set = static_cast(object); + return JS::Value(weak_set->values().size()); +} + TESTJS_RUN_FILE_FUNCTION(const String& test_file, JS::Interpreter&) { if (!test262_parser_tests) diff --git a/Userland/Libraries/LibJS/Heap/Heap.cpp b/Userland/Libraries/LibJS/Heap/Heap.cpp index 5d54058689..5d55715955 100644 --- a/Userland/Libraries/LibJS/Heap/Heap.cpp +++ b/Userland/Libraries/LibJS/Heap/Heap.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include namespace JS { @@ -182,18 +183,22 @@ void Heap::sweep_dead_cells(bool print_report, const Core::ElapsedTimer& measure dbgln_if(HEAP_DEBUG, "sweep_dead_cells:"); Vector empty_blocks; Vector full_blocks_that_became_usable; + Vector sweeped_cells; size_t collected_cells = 0; size_t live_cells = 0; size_t collected_cell_bytes = 0; size_t live_cell_bytes = 0; + auto should_store_sweeped_cells = !m_weak_sets.is_empty(); for_each_block([&](auto& block) { bool block_has_live_cells = false; bool block_was_full = block.is_full(); block.template for_each_cell_in_state([&](Cell* cell) { if (!cell->is_marked()) { dbgln_if(HEAP_DEBUG, " ~ {}", cell); + if (should_store_sweeped_cells) + sweeped_cells.append(cell); block.deallocate(cell); ++collected_cells; collected_cell_bytes += block.cell_size(); @@ -221,6 +226,9 @@ void Heap::sweep_dead_cells(bool print_report, const Core::ElapsedTimer& measure allocator_for_size(block->cell_size()).block_did_become_usable({}, *block); } + for (auto* weak_set : m_weak_sets) + weak_set->remove_sweeped_cells({}, sweeped_cells); + if constexpr (HEAP_DEBUG) { for_each_block([&](auto& block) { dbgln(" > Live HeapBlock @ {}: cell_size={}", &block, block.cell_size()); @@ -272,6 +280,18 @@ void Heap::did_destroy_marked_value_list(Badge, MarkedValueList m_marked_value_lists.remove(&list); } +void Heap::did_create_weak_set(Badge, WeakSet& set) +{ + VERIFY(!m_weak_sets.contains(&set)); + m_weak_sets.set(&set); +} + +void Heap::did_destroy_weak_set(Badge, WeakSet& set) +{ + VERIFY(m_weak_sets.contains(&set)); + m_weak_sets.remove(&set); +} + void Heap::defer_gc(Badge) { ++m_gc_deferrals; diff --git a/Userland/Libraries/LibJS/Heap/Heap.h b/Userland/Libraries/LibJS/Heap/Heap.h index 6e45121241..624efa9d5d 100644 --- a/Userland/Libraries/LibJS/Heap/Heap.h +++ b/Userland/Libraries/LibJS/Heap/Heap.h @@ -70,6 +70,9 @@ public: void did_create_marked_value_list(Badge, MarkedValueList&); void did_destroy_marked_value_list(Badge, MarkedValueList&); + void did_create_weak_set(Badge, WeakSet&); + void did_destroy_weak_set(Badge, WeakSet&); + void defer_gc(Badge); void undefer_gc(Badge); @@ -106,6 +109,8 @@ private: HashTable m_marked_value_lists; + HashTable m_weak_sets; + BlockAllocator m_block_allocator; size_t m_gc_deferrals { 0 }; diff --git a/Userland/Libraries/LibJS/Runtime/WeakSet.cpp b/Userland/Libraries/LibJS/Runtime/WeakSet.cpp index 50bf41c0ba..ff64c0858d 100644 --- a/Userland/Libraries/LibJS/Runtime/WeakSet.cpp +++ b/Userland/Libraries/LibJS/Runtime/WeakSet.cpp @@ -16,10 +16,18 @@ WeakSet* WeakSet::create(GlobalObject& global_object) WeakSet::WeakSet(Object& prototype) : Object(prototype) { + heap().did_create_weak_set({}, *this); } WeakSet::~WeakSet() { + heap().did_destroy_weak_set({}, *this); +} + +void WeakSet::remove_sweeped_cells(Badge, Vector& cells) +{ + for (auto* cell : cells) + m_values.remove(cell); } } diff --git a/Userland/Libraries/LibJS/Runtime/WeakSet.h b/Userland/Libraries/LibJS/Runtime/WeakSet.h index f8c06ad017..0062d4eef4 100644 --- a/Userland/Libraries/LibJS/Runtime/WeakSet.h +++ b/Userland/Libraries/LibJS/Runtime/WeakSet.h @@ -21,11 +21,13 @@ public: explicit WeakSet(Object& prototype); virtual ~WeakSet() override; - HashTable const& values() const { return m_values; }; - HashTable& values() { return m_values; }; + HashTable const& values() const { return m_values; }; + HashTable& values() { return m_values; }; + + void remove_sweeped_cells(Badge, Vector&); private: - HashTable m_values; + HashTable m_values; // This stores Cell pointers instead of Object pointers to aide with sweeping }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/WeakSet/WeakSet.prototype.add.js b/Userland/Libraries/LibJS/Tests/builtins/WeakSet/WeakSet.prototype.add.js index 0f77e94fe6..d9dd1eee2e 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/WeakSet/WeakSet.prototype.add.js +++ b/Userland/Libraries/LibJS/Tests/builtins/WeakSet/WeakSet.prototype.add.js @@ -14,3 +14,13 @@ test("invalid values", () => { }).toThrowWithMessage(TypeError, "is not an object"); }); }); + +test("automatic removal of garbage-collected values", () => { + const weakSet = new WeakSet(); + { + expect(weakSet.add({ a: 1 })).toBe(weakSet); + expect(getWeakSetSize(weakSet)).toBe(1); + } + gc(); + expect(getWeakSetSize(weakSet)).toBe(0); +}); diff --git a/Userland/Libraries/LibTest/JavaScriptTestRunner.h b/Userland/Libraries/LibTest/JavaScriptTestRunner.h index fcb7a64f18..e85d7edfe2 100644 --- a/Userland/Libraries/LibTest/JavaScriptTestRunner.h +++ b/Userland/Libraries/LibTest/JavaScriptTestRunner.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include