/* * Copyright (c) 2026, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include class TestCell : public GC::Cell { GC_CELL(TestCell, GC::Cell); GC_DECLARE_ALLOCATOR(TestCell); u8 m_padding[16] {}; }; GC_DEFINE_ALLOCATOR(TestCell); static GC::Heap& test_heap() { static AK::NeverDestroyed heap([](auto&) { }); return *heap; } TEST_SETUP { GC::Heap::set_default_heap_for_testing(test_heap()); } class TestVisitor : public GC::Cell::Visitor { virtual void visit_impl(GC::Cell& cell) override { visited_cells.set(&cell); } virtual void visit_impl(ReadonlySpan span) override { last_nan_span_size = span.size(); } virtual void visit_possible_values(ReadonlyBytes) override { } public: HashTable visited_cells; Optional last_nan_span_size; }; class TestNanBox : public GC::NanBoxedValue { public: static TestNanBox from_cell(GC::Cell* cell) { TestNanBox box; box.m_value.encoded = GC::SHIFTED_IS_CELL_PATTERN | (bit_cast(cell) & 0x0000FFFFFFFFFFFFULL); return box; } static TestNanBox from_double(double value) { TestNanBox box; box.m_value.as_double = value; return box; } }; TEST_CASE(visit_cell_pointer_traces_cell) { auto& heap = test_heap(); auto cell = heap.allocate(); TestVisitor visitor; visitor.visit(static_cast(cell.ptr())); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_null_cell_pointer_traces_nothing) { TestVisitor visitor; visitor.visit(static_cast(nullptr)); EXPECT_EQ(visitor.visited_cells.size(), 0u); } TEST_CASE(visit_cell_reference_traces_cell) { auto& heap = test_heap(); auto cell = heap.allocate(); TestVisitor visitor; visitor.visit(static_cast(*cell)); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_const_cell_pointer_traces_cell) { auto& heap = test_heap(); auto cell = heap.allocate(); TestVisitor visitor; visitor.visit(static_cast(cell.ptr())); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_const_cell_reference_traces_cell) { auto& heap = test_heap(); auto cell = heap.allocate(); TestVisitor visitor; visitor.visit(static_cast(*cell)); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_ptr_traces_cell) { auto& heap = test_heap(); auto cell = heap.allocate(); GC::Ptr ptr { cell }; TestVisitor visitor; visitor.visit(ptr); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_null_ptr_traces_nothing) { GC::Ptr ptr; TestVisitor visitor; visitor.visit(ptr); EXPECT_EQ(visitor.visited_cells.size(), 0u); } TEST_CASE(visit_ref_traces_cell) { auto& heap = test_heap(); auto cell = heap.allocate(); GC::Ref ref { cell }; TestVisitor visitor; visitor.visit(ref); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_span_traces_cell_elements) { auto& heap = test_heap(); auto cell = heap.allocate(); Vector> elements; elements.append(cell); TestVisitor visitor; visitor.visit(elements.span()); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_readonly_span_traces_cell_elements) { auto& heap = test_heap(); auto cell = heap.allocate(); Vector> elements; elements.append(cell); TestVisitor visitor; visitor.visit(static_cast>>(elements.span())); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_vector_traces_cell_elements) { auto& heap = test_heap(); auto cell = heap.allocate(); Vector> vector; vector.append(cell); TestVisitor visitor; visitor.visit(vector); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_hash_table_traces_cell_elements) { auto& heap = test_heap(); auto cell = heap.allocate(); HashTable> table; table.set(cell); TestVisitor visitor; visitor.visit(table); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_ordered_hash_table_traces_cell_elements) { auto& heap = test_heap(); auto cell = heap.allocate(); OrderedHashTable> table; table.set(cell); TestVisitor visitor; visitor.visit(table); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_hash_map_traces_cell_value) { auto& heap = test_heap(); auto cell = heap.allocate(); HashMap> map; map.set(7, cell); TestVisitor visitor; visitor.visit(map); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_hash_map_traces_cell_key) { auto& heap = test_heap(); auto cell = heap.allocate(); HashMap, int> map; map.set(cell, 7); TestVisitor visitor; visitor.visit(map); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_ordered_hash_map_traces_cell_value) { auto& heap = test_heap(); auto cell = heap.allocate(); OrderedHashMap> map; map.set(7, cell); TestVisitor visitor; visitor.visit(map); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_optional_traces_cell_value) { auto& heap = test_heap(); auto cell = heap.allocate(); Optional> optional { cell }; TestVisitor visitor; visitor.visit(optional); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_empty_optional_traces_nothing) { Optional> optional; TestVisitor visitor; visitor.visit(optional); EXPECT_EQ(visitor.visited_cells.size(), 0u); } TEST_CASE(visit_nan_boxed_value_traces_cell_when_cell_held) { auto& heap = test_heap(); auto cell = heap.allocate(); auto value = TestNanBox::from_cell(cell.ptr()); TestVisitor visitor; visitor.visit(value); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_nan_boxed_value_traces_nothing_when_not_cell) { auto value = TestNanBox::from_double(3.14); TestVisitor visitor; visitor.visit(value); EXPECT_EQ(visitor.visited_cells.size(), 0u); } TEST_CASE(visit_readonly_span_of_nan_boxed_values_routes_to_visit_impl) { Vector values; values.append(TestNanBox::from_double(1.0)); values.append(TestNanBox::from_double(2.0)); values.append(TestNanBox::from_double(3.0)); TestVisitor visitor; visitor.visit(static_cast>(values.span())); EXPECT_EQ(visitor.last_nan_span_size, 3u); } TEST_CASE(visit_span_of_nan_boxed_values_routes_to_visit_impl) { Vector values; values.append(TestNanBox::from_double(1.0)); values.append(TestNanBox::from_double(2.0)); TestVisitor visitor; visitor.visit(values.span()); EXPECT_EQ(visitor.last_nan_span_size, 2u); } TEST_CASE(visit_vector_of_nan_boxed_values_routes_to_visit_impl) { Vector values; values.append(TestNanBox::from_double(1.0)); values.append(TestNanBox::from_double(2.0)); values.append(TestNanBox::from_double(3.0)); values.append(TestNanBox::from_double(4.0)); TestVisitor visitor; visitor.visit(values); EXPECT_EQ(visitor.last_nan_span_size, 4u); } TEST_CASE(visit_variant_traces_active_cell_alternative) { auto& heap = test_heap(); auto cell = heap.allocate(); Variant, int> variant { GC::Ref(cell) }; TestVisitor visitor; visitor.visit(variant); EXPECT(visitor.visited_cells.contains(cell.ptr())); } TEST_CASE(visit_variant_skips_untraced_alternative) { auto& heap = test_heap(); auto cell = heap.allocate(); Variant, int> variant { 42 }; TestVisitor visitor; visitor.visit(variant); EXPECT_EQ(visitor.visited_cells.size(), 0u); EXPECT(!visitor.visited_cells.contains(cell.ptr())); } static_assert(GC::IsVisitable>>::value); static_assert(GC::IsVisitable>>::value); static_assert(GC::IsVisitable>>::value); static_assert(GC::IsVisitable>>::value); static_assert(GC::IsVisitable>>::value); static_assert(GC::IsVisitable>>::value); static_assert(GC::IsVisitable>>>::value); static_assert(!GC::IsVisitable>::value); static_assert(!GC::IsVisitable>::value); static_assert(!GC::IsVisitable>::value); static_assert(!GC::IsVisitable>::value); static_assert(!GC::IsVisitable>::value); static_assert(!GC::IsVisitable>::value); static_assert(GC::IsVisitable, int>>::value); static_assert(!GC::IsVisitable>::value);