From 568ee1fa859de2b0dfa226cd19b2e98b1877767a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 26 Jun 2017 15:59:45 +0200 Subject: [PATCH] add debugPtr.h to troubleshoot shared_ptr problems A wrapper around shared_ptr which tracks backwards references to help untangle complicated ownership situations (aka. ref loop waiting to happen). --- src/misc/Makefile | 3 +- src/misc/debugPtr.cpp | 145 +++++++++++++++ src/misc/pv/debugPtr.h | 322 ++++++++++++++++++++++++++++++++++ src/misc/pv/sharedPtr.h | 97 +++++++++- testApp/misc/Makefile | 4 + testApp/misc/testDebugPtr.cpp | 280 +++++++++++++++++++++++++++++ 6 files changed, 843 insertions(+), 8 deletions(-) create mode 100644 src/misc/debugPtr.cpp create mode 100644 src/misc/pv/debugPtr.h create mode 100644 testApp/misc/testDebugPtr.cpp diff --git a/src/misc/Makefile b/src/misc/Makefile index 82e6107..2f854ab 100644 --- a/src/misc/Makefile +++ b/src/misc/Makefile @@ -20,6 +20,7 @@ INC += pv/messageQueue.h INC += pv/destroyable.h INC += pv/status.h INC += pv/sharedPtr.h +INC += pv/debugPtr.h INC += pv/localStaticLock.h INC += pv/typeCast.h INC += pv/sharedVector.h @@ -40,4 +41,4 @@ LIBSRCS += messageQueue.cpp LIBSRCS += localStaticLock.cpp LIBSRCS += typeCast.cpp LIBSRCS += parseToPOD.cpp - +LIBSRCS += debugPtr.cpp diff --git a/src/misc/debugPtr.cpp b/src/misc/debugPtr.cpp new file mode 100644 index 0000000..cadd9c6 --- /dev/null +++ b/src/misc/debugPtr.cpp @@ -0,0 +1,145 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include + +namespace epics { +namespace debug { + +struct tracker { + std::mutex mutex; + ptr_base::ref_set_t refs; +}; + +void shared_ptr_base::track_new() +{ + if(track) { + std::lock_guard G(track->mutex); + track->refs.insert(this); + } + snap_stack(); +} + +// create new tracker if ptr!=nullptr, otherwise clear +void shared_ptr_base::track_new(void* ptr) +{ + track_clear(); + if(ptr){ + track.reset(new tracker); + std::lock_guard G(track->mutex); + track->refs.insert(this); + } + snap_stack(); +} + +void shared_ptr_base::track_assign(const shared_ptr_base &o) +{ + if(track!=o.track) { + track_clear(); + track = o.track; + if(track) { + std::lock_guard G(track->mutex); + track->refs.insert(this); + } + snap_stack(); + } +} + +void shared_ptr_base::track_clear() +{ + if(track) { + std::lock_guard G(track->mutex); + track->refs.erase(this); + } + track.reset(); + m_depth = 0; +} + +void shared_ptr_base::swap(shared_ptr_base &o) +{ + // we cheat a bit here to avoid lock order, and to lock only twice + if(track) { + std::lock_guard G(track->mutex); + track->refs.insert(&o); + track->refs.erase(this); + } + track.swap(o.track); + if(track) { + std::lock_guard G(track->mutex); + track->refs.insert(this); + track->refs.erase(&o); + } + //TODO: keep original somehow??? + snap_stack(); + o.snap_stack(); +} + +void shared_ptr_base::snap_stack() +{ + if(!track) { + m_depth = 0; + return; + } +#if defined(EXCEPT_USE_BACKTRACE) + { + m_depth=backtrace(m_stack,EXCEPT_DEPTH); + } +#else + {} +#endif + +} + +void shared_ptr_base::show_stack(std::ostream& strm) const +{ + strm<<"ptr "< G(track->mutex); + for(auto ref : track->refs) { + if(!self && ref==this) continue; + strm<<'#'; + ref->show_stack(strm); + strm<<'\n'; + } + } +} + +void ptr_base::spy_refs(ref_set_t &refs) const +{ + if(track) { + std::lock_guard G(track->mutex); + refs.insert(track->refs.begin(), track->refs.end()); + } +} + +}} // namespace epics::debug diff --git a/src/misc/pv/debugPtr.h b/src/misc/pv/debugPtr.h new file mode 100644 index 0000000..626265a --- /dev/null +++ b/src/misc/pv/debugPtr.h @@ -0,0 +1,322 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ +/* Author: Michael Davidsaver */ +/* wrapper around shared_ptr which tracks backwards references. + * Can help to find ref. leaks, loops, and other exciting bugs. + * See comments in sharedPtr.h + */ +#ifndef DEBUGPTR_H +#define DEBUGPTR_H + +#if __cplusplus<201103L +# error c++11 required +#endif + +#include +#include +#include +#include +#include + +#include + +//! User code should test this macro +//! before calling epics::debug::shared_ptr::show_refs() +#define HAVE_SHOW_REFS + +namespace epics { +namespace debug { + +struct tracker; +class shared_ptr_base; + +class ptr_base { + friend class shared_ptr_base; + template + friend class shared_ptr; + template + friend class weak_ptr; +protected: + typedef std::shared_ptr track_t; + track_t track; + + ptr_base() noexcept : track() {} + ptr_base(const track_t& track) :track(track) {} + ptr_base(const ptr_base&) = delete; + ptr_base(ptr_base&&) = delete; + + ptr_base& operator=(const ptr_base&) = delete; + +public: + typedef std::set ref_set_t; + void show_refs(std::ostream&, bool self=true, bool weak=false) const; + void spy_refs(ref_set_t&) const; +}; +class weak_ptr_base : public ptr_base { +protected: + weak_ptr_base() {} + weak_ptr_base(const track_t& track) :ptr_base(track) {} +}; +class shared_ptr_base : public ptr_base { +protected: + shared_ptr_base() noexcept +#ifndef EXCEPT_USE_NONE + :m_stack(), m_depth(0) +#endif + {} + shared_ptr_base(const track_t& track) :ptr_base(track) + #ifndef EXCEPT_USE_NONE + ,m_stack(), m_depth(0) + #endif + {} + ~shared_ptr_base() {track_clear();} + + // add ourselves to tracker + void track_new(); + // create new tracker if ptr!=nullptr, otherwise clear + void track_new(void* ptr); + // copy tracker and add ourself + void track_assign(const shared_ptr_base& o); + void track_clear(); + void swap(shared_ptr_base& o); + void snap_stack(); + +#ifndef EXCEPT_USE_NONE + void *m_stack[EXCEPT_DEPTH]; + int m_depth; // always <= EXCEPT_DEPTH +#endif + +public: + void show_stack(std::ostream&) const; +}; + + + +template +class shared_ptr; +template +class weak_ptr; +template +class enable_shared_from_this; + +template +void +do_enable_shared_from_this(const shared_ptr& dest, + enable_shared_from_this* self + ); + +template +inline void +do_enable_shared_from_this(const shared_ptr&, ...) {} + +template +class shared_ptr : public shared_ptr_base { + typedef ::std::shared_ptr real_type; + + real_type real; + + template + friend class shared_ptr; + template + friend class weak_ptr; + + // ctor for casts + shared_ptr(const real_type& r, const ptr_base::track_t& t) + :shared_ptr_base(t), real(r) + {track_new();} +public: + typedef typename real_type::element_type element_type; + typedef weak_ptr weak_type; + + // new NULL + shared_ptr() noexcept {} + // copy existing same type + shared_ptr(const shared_ptr& o) :shared_ptr_base(o.track), real(o.real) {track_new();} + // copy existing of implicitly castable type + template + shared_ptr(const shared_ptr& o) :shared_ptr_base(o.track), real(o.real) {track_new();} + + // construct around new pointer + template + explicit shared_ptr(A* a, Args ... args) : shared_ptr_base(), real(a, args...) { + track_new(a); + do_enable_shared_from_this(*this, a); + } + + // make strong ref from weak + template + shared_ptr(const weak_ptr& o) :shared_ptr_base(o.track), real(o.real) {track_new();} + + ~shared_ptr() {} + + shared_ptr& operator=(const shared_ptr& o) { + if(this!=&o) { + real = o.real; + track_assign(o); + } + return *this; + } + template + shared_ptr& operator=(const shared_ptr& o) { + if(get()!=o.get()) { + real = o.real; + track_assign(o); + } + return *this; + } + + void reset() noexcept { real.reset(); track_clear(); } + template + void reset(A* a, Args ... args) + { + real.reset(a, args...); + track_new(a); + do_enable_shared_from_this(*this, a); + } + void swap(shared_ptr &o) noexcept + { + if(this!=&o) { + real.swap(o.real); + shared_ptr_base::swap(o); + } + } + + // proxy remaining to underlying shared_ptr + + T* get() const noexcept { return real.get(); } + typename std::add_lvalue_reference::type operator*() const noexcept { return *real; } + T* operator->() const noexcept { return real.get(); } + long use_count() const noexcept { return real.use_count(); } + bool unique() const noexcept { return real.unique(); } + explicit operator bool() const noexcept { return bool(real); } + + bool operator==(const shared_ptr& o) const { return real==o.real; } + bool operator!=(const shared_ptr& o) const { return real!=o.real; } + bool operator<(const shared_ptr& o) const { return real + bool owner_before(const shared_ptr& o) { return real.owner_before(o); } + template + bool owner_before(const weak_ptr& o) { return real.owner_before(o); } + + template + friend + shared_ptr static_pointer_cast(const shared_ptr& src); + template + friend + shared_ptr const_pointer_cast(const shared_ptr& src); + template + friend + shared_ptr dynamic_pointer_cast(const shared_ptr& src); + template + friend void + do_enable_shared_from_this(const shared_ptr& dest, + enable_shared_from_this* self + ); +}; + +template +shared_ptr static_pointer_cast(const shared_ptr& src) { + return shared_ptr(std::static_pointer_cast(src.real), src.track); +} + +template +shared_ptr const_pointer_cast(const shared_ptr& src) { + return shared_ptr(std::const_pointer_cast(src.real), src.track); +} + +template +shared_ptr dynamic_pointer_cast(const shared_ptr& src) { + return shared_ptr(std::dynamic_pointer_cast(src.real), src.track); +} + +template +class weak_ptr : public weak_ptr_base { + typedef ::std::weak_ptr real_type; + + real_type real; + + template + friend class shared_ptr; + template + friend class weak_ptr; + +public: + typedef typename real_type::element_type element_type; + typedef weak_ptr weak_type; + + // new NULL + weak_ptr() noexcept {} + // copy existing same type + weak_ptr(const weak_ptr& o) :weak_ptr_base(o.track), real(o.real) {} + // copy existing of similar type + template + weak_ptr(const weak_ptr& o) :weak_ptr_base(o.track), real(o.real) {} + + // create week ref from strong ref + template + weak_ptr(const shared_ptr& o) :weak_ptr_base(o.track), real(o.real) {} + + ~weak_ptr() {} + + weak_ptr& operator=(const weak_ptr& o) { + if(this!=&o) { + real = o.real; + track = o.track; + } + return *this; + } + template + weak_ptr& operator=(const shared_ptr& o) { + real = o.real; + track = o.track; + return *this; + } + + shared_ptr lock() const noexcept { return shared_ptr(real.lock(), track); } + void reset() noexcept { track.reset(); real.reset(); } + + long use_count() const noexcept { return real.use_count(); } + bool unique() const noexcept { return real.unique(); } +}; + +template +class enable_shared_from_this { + mutable weak_ptr xxInternalSelf; + + template + friend + void + do_enable_shared_from_this(const shared_ptr& dest, + enable_shared_from_this* self + ); +public: + shared_ptr shared_from_this() const { + return shared_ptr(xxInternalSelf); + } +}; + +template +void +do_enable_shared_from_this(const shared_ptr& dest, + enable_shared_from_this* self + ) +{ + shared_ptr actual(dynamic_pointer_cast(dest)); + if(!actual) + throw std::logic_error("epics::debug::enabled_shared_from_this fails"); + self->xxInternalSelf = actual; +} + +}} // namespace epics::debug + +template +std::ostream& operator<<(std::ostream& strm, const epics::debug::shared_ptr& ptr) +{ + strm<= c++11 + * + * Define DEBUG_SHARED_PTR globally to cause epics::debug::shared_ptr + * to be injected as std::tr1::shared_ptr and the macro + * HAVE_SHOW_REFS will be defined. + * + * epics::debug::shared_ptr wraps std::shared_ptr with additional + * tracking of backwards references. + * std::shared_ptr::use_count() gives the number of shared_ptr + * (strong refs) to the referenced object. + * + * If use_count()==5 then epics::debug::shared_ptr::show_refs() will print + * 5 lines of the format + * + * # : ... + * + * Given the numberic address of each shared_ptr as well as the call stack + * at the point where it was initialized. + * Use the 'addr2line' utility to interpret the stack addresses. + * + * On linux w/ ASLR it is necessary to turn on static linking to meaningfully + * interpret call stack addresses. + * Append "STATIC_BUILD=YES" to configure/CONFIG_SITE + */ +//#define DEBUG_SHARED_PTR + #if defined(SHARED_FROM_MANUAL) // define SHARED_FROM_MANUAL if from some reason it is desirable to manually select // which shared_ptr implementation to use @@ -59,18 +85,39 @@ #include +#ifndef DEBUG_SHARED_PTR + namespace std { namespace tr1 { - using std::shared_ptr; - using std::weak_ptr; - using std::static_pointer_cast; - using std::dynamic_pointer_cast; - using std::const_pointer_cast; - using std::enable_shared_from_this; - using std::bad_weak_ptr; + using ::std::shared_ptr; + using ::std::weak_ptr; + using ::std::static_pointer_cast; + using ::std::dynamic_pointer_cast; + using ::std::const_pointer_cast; + using ::std::enable_shared_from_this; + using ::std::bad_weak_ptr; } } +#else // DEBUG_SHARED_PTR + +#include "debugPtr.h" + +namespace std { + namespace tr1 { + using ::epics::debug::shared_ptr; + using ::epics::debug::weak_ptr; + using ::epics::debug::static_pointer_cast; + using ::epics::debug::dynamic_pointer_cast; + using ::epics::debug::const_pointer_cast; + using ::epics::debug::enable_shared_from_this; + using ::std::bad_weak_ptr; + } +} + + +#endif // DEBUG_SHARED_PTR + #elif defined(SHARED_FROM_TR1) # include @@ -101,6 +148,42 @@ namespace std { # undef SHARED_FROM_BOOST #endif +namespace detail { +template +struct ref_shower { + const std::tr1::shared_ptr& ptr; + bool self, weak; + ref_shower(const std::tr1::shared_ptr& ptr, bool self, bool weak) :ptr(ptr),self(self),weak(weak) {} +}; +} + +/** Print a list (one per line) of shared_ptr which refer to the same object + * + * @param ptr Use the object pointed to by this shared_ptr + * @param self include or omit a line for this shared_ptr + * @param weak include a line for each weak_ptr (not implemented) + @code + shared_ptr x; + std::cout << show_referrers(x); + @endcode + */ +template +inline detail::ref_shower show_referrers(const std::tr1::shared_ptr& ptr, bool self=true, bool weak=false) +{ + return detail::ref_shower(ptr, self, weak); +} + +namespace std{ +template +inline std::ostream& operator<<(std::ostream& strm, const ::detail::ref_shower& refs) +{ +#ifdef HAVE_SHOW_REFS + refs.ptr.show_refs(strm, refs.self, refs.weak); +#endif // HAVE_SHOW_REFS + return strm; +} +}//namespace std + #define POINTER_DEFINITIONS(clazz) \ typedef std::tr1::shared_ptr shared_pointer; \ typedef std::tr1::shared_ptr const_shared_pointer; \ diff --git a/testApp/misc/Makefile b/testApp/misc/Makefile index 8cf1ef6..d4099a0 100644 --- a/testApp/misc/Makefile +++ b/testApp/misc/Makefile @@ -45,6 +45,10 @@ testSharedVector_SRCS += testSharedVector.cpp testHarness_SRCS += testSharedVector.cpp TESTS += testSharedVector +TESTPROD_HOST += testDebugPtr +testDebugPtr_SRCS += testDebugPtr.cpp +TESTS += testDebugPtr + TESTPROD_HOST += testSerialization testSerialization_SRCS += testSerialization.cpp testHarness_SRCS += testSerialization.cpp diff --git a/testApp/misc/testDebugPtr.cpp b/testApp/misc/testDebugPtr.cpp new file mode 100644 index 0000000..3b8a0e9 --- /dev/null +++ b/testApp/misc/testDebugPtr.cpp @@ -0,0 +1,280 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ +/* Author: Michael Davidsaver */ + +#include +#include +#include +#include +#include + +#include +#include + +#if __cplusplus>=201103L + +#include + +namespace { +void show(const epics::debug::shared_ptr_base& ref) +{ + epics::debug::ptr_base::ref_set_t refs; + ref.spy_refs(refs); + std::ostringstream strm; + std::copy(refs.begin(), refs.end(), std::ostream_iterator(strm, ", ")); + testDiag("refs: %s", strm.str().c_str()); + ref.show_refs(std::cout); +} + +void testEmpty() +{ + testDiag("testEmpty()"); + + epics::debug::shared_ptr empty; + testOk1(!empty); + + epics::debug::ptr_base::ref_set_t refs; + empty.spy_refs(refs); + testOk1(refs.empty()); +} + +void testSimple() +{ + testDiag("testSimple()"); + + testDiag("ctor"); + epics::debug::shared_ptr one(new int(42)), + two; + testDiag("one = %p two = %p", &one, &two); + show(one); + show(two); + { + epics::debug::ptr_base::ref_set_t refs; + one.spy_refs(refs); + testOk1(refs.size()==1); + testOk1(refs.find(&one)!=refs.end()); + testOk1(refs.find(&two)==refs.end()); + } + + testDiag("assign non-NULL"); + two = one; + show(one); + show(two); + { + epics::debug::ptr_base::ref_set_t refs; + one.spy_refs(refs); + testOk1(refs.size()==2); + testOk1(refs.find(&one)!=refs.end()); + testOk1(refs.find(&two)!=refs.end()); + } + + testDiag("reset"); + one.reset(); + show(one); + show(two); + { + epics::debug::ptr_base::ref_set_t refs; + one.spy_refs(refs); + testOk1(refs.size()==0); + } + { + epics::debug::ptr_base::ref_set_t refs; + two.spy_refs(refs); + testOk1(refs.size()==1); + testOk1(refs.find(&one)==refs.end()); + testOk1(refs.find(&two)!=refs.end()); + } + + testDiag("copy ctor"); + epics::debug::shared_ptr three(two), + empty(one); + show(three); + show(empty); + testDiag("three = %p empty = %p", &three, &empty); + { + epics::debug::ptr_base::ref_set_t refs; + empty.spy_refs(refs); + testOk1(refs.size()==0); + } + { + epics::debug::ptr_base::ref_set_t refs; + three.spy_refs(refs); + testOk1(refs.size()==2); + testOk1(refs.find(&one)==refs.end()); + testOk1(refs.find(&two)!=refs.end()); + testOk1(refs.find(&three)!=refs.end()); + testOk1(refs.find(&empty)==refs.end()); + } +} + +struct Base {virtual ~Base(){}}; +struct Derv : public Base {virtual ~Derv(){}}; + +void testCast() +{ + testDiag("testCast()"); + + epics::debug::shared_ptr one(new int(42)); + testOk1(!!one); + + epics::debug::shared_ptr two(one); + testOk1(one.get()==two.get()); + + epics::debug::shared_ptr three(one); + testOk1(one.get()==three.get()); + + epics::debug::shared_ptr four(epics::debug::const_pointer_cast(three)); + testOk1(one.get()==four.get()); + + epics::debug::shared_ptr X(new Derv); + epics::debug::shared_ptr Y(epics::debug::static_pointer_cast(X)); + testOk1(X.get()==Y.get()); +} + +void testWeak() +{ + testDiag("testWeak()"); + + epics::debug::shared_ptr one(new int(42)); + { + epics::debug::weak_ptr two(one); + epics::debug::shared_ptr three(two); + testOk1(three.get()==one.get()); + } + + { + epics::debug::weak_ptr two(one); + epics::debug::shared_ptr three(two.lock()); + testOk1(three.get()==one.get()); + } +} + +struct MySelf : public epics::debug::enable_shared_from_this +{}; + +void testEnable() +{ + testDiag("testEnable()"); + + epics::debug::shared_ptr self(new MySelf), + other(self->shared_from_this()); + + testOk1(!!self); + testOk1(self.get()==other.get()); +} + +template +struct set_flag_dtor { + bool *pflag; + set_flag_dtor(bool *pflag) :pflag(pflag) {} + void operator()(T* x) { + delete x; + *pflag = true; + pflag = 0; // paranoia + } +}; + +void testDtor() +{ + testDiag("testDtor()"); + + bool flag = false; + { + epics::debug::shared_ptr x(new Derv, set_flag_dtor(&flag)); + testOk1(!!x); + testOk1(!flag); + } + testOk1(flag); + + flag = false; + bool flag2 = false; + { + epics::debug::shared_ptr x(new Derv, set_flag_dtor(&flag)); + testOk1(!!x); + Derv *old = x.get(); + testOk1(!flag); + testOk1(!flag2); + x.reset(new Derv, set_flag_dtor(&flag2)); + testOk1(!!x); + testOk1(old!=x.get()); + testOk1(flag); + testOk1(!flag2); + } + testOk1(flag2); + + testDiag("destroy Derv as Base"); + flag = false; + { + epics::debug::shared_ptr x(new Derv, set_flag_dtor(&flag)); + testOk1(!!x); + testOk1(!flag); + } + testOk1(flag); +} + +struct BaseEnable { + virtual ~BaseEnable() {} + virtual epics::debug::shared_ptr self() =0; +}; + +struct DervEnable : public BaseEnable, + public epics::debug::enable_shared_from_this +{ + virtual ~DervEnable() {} + virtual epics::debug::shared_ptr self() { + return shared_from_this(); + } +}; + +void testEnableDerv() +{ + testDiag("testEnableDerv()"); + epics::debug::shared_ptr x(new DervEnable), + y(x->self()); + testOk1(!!x); + testOk1(x.get()==y.get()); + + BaseEnable *old = x.get(); + + x.reset(); + testOk1(!x); + testOk1(y.unique()); + + x.reset(new DervEnable); + y = x->self(); + testOk1(!!x); + testOk1(x.get()!=old); + testOk1(x.get()==y.get()); +} + +} // namespace + +MAIN(testDebugPtr) +{ + testPlan(48); + try { + testEmpty(); + testSimple(); + testCast(); + testWeak(); + testEnable(); + testDtor(); + testEnableDerv(); + }catch(std::exception& e){ + testAbort("Unhandled exception: %s", e.what()); + } + return testDone(); +} + +#else // __cplusplus>=201103L + +MAIN(testDebugPtr) +{ + testPlan(1); + testSkip(1, "Not c++11"); + return testDone(); +} + +#endif // __cplusplus>=201103L