diff --git a/src/reference.hpp b/src/reference.hpp index ca56a84..585051f 100644 --- a/src/reference.hpp +++ b/src/reference.hpp @@ -270,6 +270,8 @@ private: // If this was the last strong reference, destroy the object if (prev_strong == 1) { + // We need to call the destructor before we decrement the weak count, to + // account for the possibility that T has a WeakRef to itself. ptr->~T(); // Release the bias - decrement weak count for strong references @@ -304,6 +306,10 @@ private: * - Returns empty Ref if object was already destroyed * - Use reset() to stop observing * + * Self-referencing pattern: Objects can safely contain WeakRef members + * pointing to themselves. The implementation ensures proper destruction + * order to prevent use-after-free when the object destructor runs. + * * Thread safety: All operations are thread-safe. The observed object * may be destroyed by other threads at any time. */ diff --git a/tests/test_reference.cpp b/tests/test_reference.cpp index 6968779..a86ea1b 100644 --- a/tests/test_reference.cpp +++ b/tests/test_reference.cpp @@ -445,3 +445,17 @@ TEST_CASE("Polymorphic edge cases") { CHECK(base_ref_from_weak.get() == derived_ref.get()); } } + +// 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; +}