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).
This commit is contained in:
Michael Davidsaver
2017-06-26 15:59:45 +02:00
parent 66633a7728
commit 568ee1fa85
6 changed files with 843 additions and 8 deletions

View File

@@ -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

145
src/misc/debugPtr.cpp Normal file
View File

@@ -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 <pv/debugPtr.h>
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<std::mutex> 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<std::mutex> 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<std::mutex> G(track->mutex);
track->refs.insert(this);
}
snap_stack();
}
}
void shared_ptr_base::track_clear()
{
if(track) {
std::lock_guard<std::mutex> 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<std::mutex> G(track->mutex);
track->refs.insert(&o);
track->refs.erase(this);
}
track.swap(o.track);
if(track) {
std::lock_guard<std::mutex> 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 "<<this;
if(m_depth<=0) return;
#if 0 && defined(EXCEPT_USE_BACKTRACE)
{
char **symbols=backtrace_symbols(m_stack, m_depth);
strm<<": ";
for(int i=0; i<m_depth; i++) {
strm<<symbols[i]<<", ";
}
std::free(symbols);
}
#else
{
strm<<": ";
for(int i=0; i<m_depth; i++) {
strm<<std::hex<<m_stack[i]<<" ";
}
}
#endif
}
void ptr_base::show_refs(std::ostream& strm, bool self, bool weak) const
{
if(!track) {
strm<<"# No refs\n";
} else {
std::lock_guard<std::mutex> 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<std::mutex> G(track->mutex);
refs.insert(track->refs.begin(), track->refs.end());
}
}
}} // namespace epics::debug

322
src/misc/pv/debugPtr.h Normal file
View File

@@ -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 <ostream>
#include <memory>
#include <thread>
#include <mutex>
#include <set>
#include <pv/epicsException.h>
//! 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<typename A>
friend class shared_ptr;
template<typename A>
friend class weak_ptr;
protected:
typedef std::shared_ptr<tracker> 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<const shared_ptr_base *> 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<typename T>
class shared_ptr;
template<typename T>
class weak_ptr;
template<class Base>
class enable_shared_from_this;
template<typename Store, typename Actual>
void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
);
template<typename T>
inline void
do_enable_shared_from_this(const shared_ptr<T>&, ...) {}
template<typename T>
class shared_ptr : public shared_ptr_base {
typedef ::std::shared_ptr<T> real_type;
real_type real;
template<typename A>
friend class shared_ptr;
template<typename A>
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<T> 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<typename A>
shared_ptr(const shared_ptr<A>& o) :shared_ptr_base(o.track), real(o.real) {track_new();}
// construct around new pointer
template<typename A, class ... Args>
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<typename A>
shared_ptr(const weak_ptr<A>& 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<typename A>
shared_ptr& operator=(const shared_ptr<A>& o) {
if(get()!=o.get()) {
real = o.real;
track_assign(o);
}
return *this;
}
void reset() noexcept { real.reset(); track_clear(); }
template<typename A, class ... Args>
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<T>::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<T>& o) const { return real==o.real; }
bool operator!=(const shared_ptr<T>& o) const { return real!=o.real; }
bool operator<(const shared_ptr<T>& o) const { return real<o.real; }
template<typename A>
bool owner_before(const shared_ptr<A>& o) { return real.owner_before(o); }
template<typename A>
bool owner_before(const weak_ptr<A>& o) { return real.owner_before(o); }
template<typename TO, typename FROM>
friend
shared_ptr<TO> static_pointer_cast(const shared_ptr<FROM>& src);
template<typename TO, typename FROM>
friend
shared_ptr<TO> const_pointer_cast(const shared_ptr<FROM>& src);
template<typename TO, typename FROM>
friend
shared_ptr<TO> dynamic_pointer_cast(const shared_ptr<FROM>& src);
template<typename Store, typename Actual>
friend void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
);
};
template<typename TO, typename FROM>
shared_ptr<TO> static_pointer_cast(const shared_ptr<FROM>& src) {
return shared_ptr<TO>(std::static_pointer_cast<TO>(src.real), src.track);
}
template<typename TO, typename FROM>
shared_ptr<TO> const_pointer_cast(const shared_ptr<FROM>& src) {
return shared_ptr<TO>(std::const_pointer_cast<TO>(src.real), src.track);
}
template<typename TO, typename FROM>
shared_ptr<TO> dynamic_pointer_cast(const shared_ptr<FROM>& src) {
return shared_ptr<TO>(std::dynamic_pointer_cast<TO>(src.real), src.track);
}
template<typename T>
class weak_ptr : public weak_ptr_base {
typedef ::std::weak_ptr<T> real_type;
real_type real;
template<typename A>
friend class shared_ptr;
template<typename A>
friend class weak_ptr;
public:
typedef typename real_type::element_type element_type;
typedef weak_ptr<T> 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<typename A>
weak_ptr(const weak_ptr<A>& o) :weak_ptr_base(o.track), real(o.real) {}
// create week ref from strong ref
template<typename A>
weak_ptr(const shared_ptr<A>& 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<typename A>
weak_ptr& operator=(const shared_ptr<A>& o) {
real = o.real;
track = o.track;
return *this;
}
shared_ptr<T> lock() const noexcept { return shared_ptr<T>(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 Base>
class enable_shared_from_this {
mutable weak_ptr<Base> xxInternalSelf;
template<typename Store, typename Actual>
friend
void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
);
public:
shared_ptr<Base> shared_from_this() const {
return shared_ptr<Base>(xxInternalSelf);
}
};
template<typename Store, typename Actual>
void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
)
{
shared_ptr<Actual> actual(dynamic_pointer_cast<Actual>(dest));
if(!actual)
throw std::logic_error("epics::debug::enabled_shared_from_this fails");
self->xxInternalSelf = actual;
}
}} // namespace epics::debug
template<typename T>
std::ostream& operator<<(std::ostream& strm, const epics::debug::shared_ptr<T>& ptr)
{
strm<<ptr.get();
return strm;
}
#endif // DEBUGPTR_H

View File

@@ -31,6 +31,32 @@
* # boost version of tr1/memory
*/
/* Debugging shared_ptr with debugPtr.h requires >= 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
*
* # <addr>: <IP0> <IP1> ...
*
* 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 <memory>
#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 <tr1/memory>
@@ -101,6 +148,42 @@ namespace std {
# undef SHARED_FROM_BOOST
#endif
namespace detail {
template<typename T>
struct ref_shower {
const std::tr1::shared_ptr<T>& ptr;
bool self, weak;
ref_shower(const std::tr1::shared_ptr<T>& 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<int> x;
std::cout << show_referrers(x);
@endcode
*/
template<typename T>
inline detail::ref_shower<T> show_referrers(const std::tr1::shared_ptr<T>& ptr, bool self=true, bool weak=false)
{
return detail::ref_shower<T>(ptr, self, weak);
}
namespace std{
template<typename T>
inline std::ostream& operator<<(std::ostream& strm, const ::detail::ref_shower<T>& 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<clazz> shared_pointer; \
typedef std::tr1::shared_ptr<const clazz> const_shared_pointer; \

View File

@@ -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

View File

@@ -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 <iostream>
#include <sstream>
#include <exception>
#include <ostream>
#include <iterator>
#include <epicsUnitTest.h>
#include <testMain.h>
#if __cplusplus>=201103L
#include <pv/debugPtr.h>
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<const void*>(strm, ", "));
testDiag("refs: %s", strm.str().c_str());
ref.show_refs(std::cout);
}
void testEmpty()
{
testDiag("testEmpty()");
epics::debug::shared_ptr<int> 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<int> 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<int> 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<int> one(new int(42));
testOk1(!!one);
epics::debug::shared_ptr<void> two(one);
testOk1(one.get()==two.get());
epics::debug::shared_ptr<const int> three(one);
testOk1(one.get()==three.get());
epics::debug::shared_ptr<int> four(epics::debug::const_pointer_cast<int>(three));
testOk1(one.get()==four.get());
epics::debug::shared_ptr<Base> X(new Derv);
epics::debug::shared_ptr<Derv> Y(epics::debug::static_pointer_cast<Derv>(X));
testOk1(X.get()==Y.get());
}
void testWeak()
{
testDiag("testWeak()");
epics::debug::shared_ptr<int> one(new int(42));
{
epics::debug::weak_ptr<int> two(one);
epics::debug::shared_ptr<int> three(two);
testOk1(three.get()==one.get());
}
{
epics::debug::weak_ptr<int> two(one);
epics::debug::shared_ptr<int> three(two.lock());
testOk1(three.get()==one.get());
}
}
struct MySelf : public epics::debug::enable_shared_from_this<MySelf>
{};
void testEnable()
{
testDiag("testEnable()");
epics::debug::shared_ptr<MySelf> self(new MySelf),
other(self->shared_from_this());
testOk1(!!self);
testOk1(self.get()==other.get());
}
template<typename T>
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<Derv> x(new Derv, set_flag_dtor<Derv>(&flag));
testOk1(!!x);
testOk1(!flag);
}
testOk1(flag);
flag = false;
bool flag2 = false;
{
epics::debug::shared_ptr<Derv> x(new Derv, set_flag_dtor<Derv>(&flag));
testOk1(!!x);
Derv *old = x.get();
testOk1(!flag);
testOk1(!flag2);
x.reset(new Derv, set_flag_dtor<Derv>(&flag2));
testOk1(!!x);
testOk1(old!=x.get());
testOk1(flag);
testOk1(!flag2);
}
testOk1(flag2);
testDiag("destroy Derv as Base");
flag = false;
{
epics::debug::shared_ptr<Base> x(new Derv, set_flag_dtor<Base>(&flag));
testOk1(!!x);
testOk1(!flag);
}
testOk1(flag);
}
struct BaseEnable {
virtual ~BaseEnable() {}
virtual epics::debug::shared_ptr<BaseEnable> self() =0;
};
struct DervEnable : public BaseEnable,
public epics::debug::enable_shared_from_this<DervEnable>
{
virtual ~DervEnable() {}
virtual epics::debug::shared_ptr<BaseEnable> self() {
return shared_from_this();
}
};
void testEnableDerv()
{
testDiag("testEnableDerv()");
epics::debug::shared_ptr<BaseEnable> 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