#include #include #include #include #include #include "reference.hpp" namespace { struct TestObject { int value; explicit TestObject(int v) : value(v) {} }; struct Node { int data; Ref next; WeakRef parent; explicit Node(int d) : data(d) {} }; // Classes for polymorphism testing struct Base { int base_value; explicit Base(int v) : base_value(v) {} virtual ~Base() = default; virtual int get_value() const { return base_value; } }; struct Derived : Base { int derived_value; explicit Derived(int base_v, int derived_v) : Base(base_v), derived_value(derived_v) {} int get_value() const override { return base_value + derived_value; } }; struct AnotherDerived : Base { int another_value; explicit AnotherDerived(int base_v, int another_v) : Base(base_v), another_value(another_v) {} int get_value() const override { return base_value * another_value; } }; // Classes to test polymorphic pointer address changes struct Interface1 { int interface1_data = 1; virtual ~Interface1() = default; virtual int get_interface1() const { return interface1_data; } }; struct Interface2 { int interface2_data = 2; virtual ~Interface2() = default; virtual int get_interface2() const { return interface2_data; } }; // Multiple inheritance - this will cause pointer address changes struct MultipleInheritance : Interface1, Interface2 { int own_data; explicit MultipleInheritance(int data) : own_data(data) {} int get_own_data() const { return own_data; } }; } // anonymous namespace TEST_CASE("Ref basic functionality") { SUBCASE("make_ref creates valid Ref") { auto ref = make_ref(42); CHECK(ref); CHECK(ref.get() != nullptr); CHECK(ref->value == 42); CHECK((*ref).value == 42); } SUBCASE("explicit copy increments reference count") { auto ref1 = make_ref(123); auto ref2 = ref1.copy(); CHECK(ref1); CHECK(ref2); CHECK(ref1.get() == ref2.get()); CHECK(ref1->value == 123); CHECK(ref2->value == 123); } SUBCASE("explicit copy assignment works correctly") { auto ref1 = make_ref(100); auto ref2 = make_ref(200); ref2 = ref1.copy(); CHECK(ref1.get() == ref2.get()); CHECK(ref1->value == 100); CHECK(ref2->value == 100); } SUBCASE("move construction transfers ownership") { auto ref1 = make_ref(456); auto *ptr = ref1.get(); auto ref2 = std::move(ref1); CHECK(!ref1); CHECK(ref2); CHECK(ref2.get() == ptr); CHECK(ref2->value == 456); } SUBCASE("move assignment transfers ownership") { auto ref1 = make_ref(789); auto ref2 = make_ref(999); auto *ptr = ref1.get(); ref2 = std::move(ref1); CHECK(!ref1); CHECK(ref2); CHECK(ref2.get() == ptr); CHECK(ref2->value == 789); } SUBCASE("reset clears reference") { auto ref = make_ref(111); CHECK(ref); ref.reset(); CHECK(!ref); CHECK(ref.get() == nullptr); } } TEST_CASE("WeakRef basic functionality") { SUBCASE("construction from Ref") { auto ref = make_ref(333); WeakRef weak_ref = ref.as_weak(); auto locked = weak_ref.lock(); CHECK(locked); CHECK(locked.get() == ref.get()); CHECK(locked->value == 333); } SUBCASE("lock() returns empty when object destroyed") { WeakRef weak_ref; { auto ref = make_ref(444); weak_ref = ref.as_weak(); } // ref goes out of scope, object should be destroyed auto locked = weak_ref.lock(); CHECK(!locked); } SUBCASE("copy and move semantics") { auto ref = make_ref(666); WeakRef weak1 = ref.as_weak(); WeakRef weak2 = weak1.copy(); // explicit copy WeakRef weak3 = std::move(weak1); // move auto locked2 = weak2.lock(); auto locked3 = weak3.lock(); CHECK(locked2); CHECK(locked3); CHECK(locked2->value == 666); CHECK(locked3->value == 666); } } TEST_CASE("Ref thread safety") { SUBCASE("concurrent copying") { const int num_threads = 4; const int copies_per_thread = 100; const int test_iterations = 1000; for (int iter = 0; iter < test_iterations; ++iter) { auto ref = make_ref(777); std::vector threads; std::latch start_latch{num_threads + 1}; for (int i = 0; i < num_threads; ++i) { threads.emplace_back([&]() { start_latch.arrive_and_wait(); for (int j = 0; j < copies_per_thread; ++j) { auto copy = ref.copy(); CHECK(copy); CHECK(copy->value == 777); } }); } start_latch.arrive_and_wait(); for (auto &t : threads) { t.join(); } CHECK(ref); CHECK(ref->value == 777); } } } TEST_CASE("Control block cleanup race condition test") { // This test specifically targets the race condition where both // the last strong reference and last weak reference are destroyed // simultaneously, potentially causing double-free of control block const int test_iterations = 10000; // Shared state for passing references between threads Ref ptr1; WeakRef ptr2; auto setup = [&]() { ptr1 = make_ref(0); ptr2 = ptr1.as_weak(); }; // Barrier for synchronization - 2 participants (main thread + worker thread) std::barrier sync_barrier{2}; std::thread worker_thread([&]() { for (int iter = 0; iter < test_iterations; ++iter) { // Wait for main thread to create the references sync_barrier.arrive_and_wait(); // Worker thread destroys the weak reference simultaneously with main // thread ptr2.reset(); // Wait for next iteration sync_barrier.arrive_and_wait(); } }); for (int iter = 0; iter < test_iterations; ++iter) { // Create references setup(); // Both threads are ready - synchronize for simultaneous destruction sync_barrier.arrive_and_wait(); // Main thread destroys the strong reference at the same time // as worker thread destroys the weak reference ptr1.reset(); // Wait for both destructions to complete sync_barrier.arrive_and_wait(); // Clean up for next iteration ptr1.reset(); ptr2.reset(); } worker_thread.join(); // If we reach here without segfault/double-free, the test passes // The bug would manifest as a crash or memory corruption } TEST_CASE("WeakRef prevents circular references") { SUBCASE("simple weak reference lifecycle") { WeakRef weak_ref; // Create object and weak reference { auto ref = make_ref(123); weak_ref = ref.as_weak(); // Should be able to lock while object exists auto locked = weak_ref.lock(); CHECK(locked); CHECK(locked->value == 123); } // Object destroyed when ref goes out of scope // Should not be able to lock after object destroyed auto locked = weak_ref.lock(); CHECK(!locked); } SUBCASE("parent-child cycle with WeakRef breaks cycle") { auto parent = make_ref(1); auto child = make_ref(2); // Create potential cycle parent->next = child.copy(); // Strong reference: parent → child child->parent = parent.as_weak(); // WeakRef: child ⇝ parent (breaks cycle) CHECK(parent->data == 1); CHECK(child->data == 2); CHECK(parent->next == child); // Verify weak reference works while parent exists CHECK(child->parent.lock() == parent); // Clear the only strong reference to parent parent.reset(); // This should destroy the parent object // Now child's weak reference should fail to lock since parent is destroyed CHECK(!child->parent.lock()); } } TEST_CASE("Polymorphic Ref conversions") { SUBCASE("copy construction from derived to base") { auto derived_ref = make_ref(10, 20); CHECK(derived_ref->get_value() == 30); // 10 + 20 // Convert Ref to Ref Ref base_ref = derived_ref.copy(); CHECK(base_ref); CHECK(base_ref->get_value() == 30); // Virtual dispatch works CHECK(base_ref->base_value == 10); // Both should point to same object CHECK(base_ref.get() == derived_ref.get()); } SUBCASE("copy assignment from derived to base") { auto derived_ref = make_ref(5, 15); auto base_ref = make_ref(100); // Before assignment CHECK(base_ref->get_value() == 100); // Assign derived to base base_ref = derived_ref.copy(); CHECK(base_ref->get_value() == 20); // 5 + 15 CHECK(base_ref.get() == derived_ref.get()); } SUBCASE("move construction from derived to base") { auto derived_ref = make_ref(7, 3); Base *original_ptr = derived_ref.get(); // Move construct base from derived Ref base_ref = std::move(derived_ref); CHECK(base_ref); CHECK(base_ref->get_value() == 10); // 7 + 3 CHECK(base_ref.get() == original_ptr); CHECK(!derived_ref); // Original should be empty after move } SUBCASE("move assignment from derived to base") { auto derived_ref = make_ref(8, 12); auto base_ref = make_ref(200); Base *derived_ptr = derived_ref.get(); // Move assign base_ref = std::move(derived_ref); CHECK(base_ref); CHECK(base_ref->get_value() == 20); // 8 + 12 CHECK(base_ref.get() == derived_ptr); CHECK(!derived_ref); // Should be empty after move } SUBCASE("multiple inheritance levels") { auto another_derived = make_ref(6, 4); CHECK(another_derived->get_value() == 24); // 6 * 4 // Convert to base Ref base_ref = another_derived.copy(); CHECK(base_ref->get_value() == 24); // Virtual dispatch CHECK(base_ref.get() == another_derived.get()); } } TEST_CASE("Polymorphic WeakRef conversions") { SUBCASE("WeakRef copy construction from derived to base") { auto derived_ref = make_ref(3, 7); // Create WeakRef WeakRef weak_derived = derived_ref.as_weak(); // Convert to WeakRef WeakRef weak_base = weak_derived.copy(); // Both should lock to same object auto locked_derived = weak_derived.lock(); auto locked_base = weak_base.lock(); CHECK(locked_derived); CHECK(locked_base); CHECK(locked_derived.get() == locked_base.get()); CHECK(locked_base->get_value() == 10); // 3 + 7 } SUBCASE("WeakRef copy assignment from derived to base") { auto derived_ref = make_ref(4, 6); auto base_ref = make_ref(999); WeakRef weak_derived = derived_ref.as_weak(); WeakRef weak_base = base_ref.as_weak(); // Assign derived weak ref to base weak ref weak_base = weak_derived.copy(); auto locked = weak_base.lock(); CHECK(locked); CHECK(locked->get_value() == 10); // 4 + 6 CHECK(locked.get() == derived_ref.get()); } SUBCASE("WeakRef from Ref to WeakRef") { auto derived_ref = make_ref(2, 8); // Create WeakRef directly from Ref WeakRef weak_base = derived_ref.as_weak(); auto locked = weak_base.lock(); CHECK(locked); CHECK(locked->get_value() == 10); // 2 + 8 CHECK(locked.get() == derived_ref.get()); } SUBCASE("WeakRef move operations") { auto derived_ref = make_ref(1, 9); WeakRef weak_derived = derived_ref.as_weak(); // Move construct WeakRef weak_base = std::move(weak_derived); // Original should be empty, new should work CHECK(!weak_derived.lock()); auto locked = weak_base.lock(); CHECK(locked); CHECK(locked->get_value() == 10); // 1 + 9 } } TEST_CASE("Polymorphic edge cases") { SUBCASE("empty Ref conversions") { Ref empty_derived; CHECK(!empty_derived); // Convert empty derived to base Ref empty_base = empty_derived.copy(); CHECK(!empty_base); // Move empty derived to base Ref moved_base = std::move(empty_derived); CHECK(!moved_base); } SUBCASE("empty WeakRef conversions") { WeakRef empty_weak_derived; CHECK(!empty_weak_derived.lock()); // Convert empty weak derived to weak base WeakRef empty_weak_base = empty_weak_derived.copy(); CHECK(!empty_weak_base.lock()); } SUBCASE("mixed Ref and WeakRef conversions") { auto derived_ref = make_ref(5, 5); // Ref → WeakRef WeakRef weak_base_from_ref = derived_ref.as_weak(); // WeakRef → Ref via lock auto base_ref_from_weak = weak_base_from_ref.lock(); CHECK(base_ref_from_weak); CHECK(base_ref_from_weak->get_value() == 10); // 5 + 5 CHECK(base_ref_from_weak.get() == derived_ref.get()); } SUBCASE("multiple inheritance pointer address bug test") { auto multi_ref = make_ref(42); // Get pointers to different base classes - these will have different // addresses Interface1 *interface1_ptr = multi_ref.get(); Interface2 *interface2_ptr = multi_ref.get(); // Verify that pointers are indeed different (demonstrating the issue) CHECK(static_cast(interface1_ptr) != static_cast(interface2_ptr)); // Create WeakRef to Interface2 (which has a different pointer address) WeakRef weak_interface2 = multi_ref.as_weak(); // Lock should return the correct Interface2 pointer, not miscalculated one auto locked_interface2 = weak_interface2.lock(); CHECK(locked_interface2); CHECK(locked_interface2.get() == interface2_ptr); // This might fail due to the bug! CHECK(locked_interface2->get_interface2() == 2); // Also test Interface1 WeakRef weak_interface1 = multi_ref.as_weak(); auto locked_interface1 = weak_interface1.lock(); CHECK(locked_interface1); CHECK(locked_interface1.get() == interface1_ptr); // This might also fail! CHECK(locked_interface1->get_interface1() == 1); } } // Should be run with asan or valgrind TEST_CASE("Self-referencing WeakRef pattern") { struct AmIAlive { volatile int x; ~AmIAlive() { x = 0; } }; struct SelfReferencing { AmIAlive am; WeakRef self_; }; auto x = make_ref(); x->self_ = x.as_weak(); }