Ideas on how to track boost::intrusive_ptr's

157 views Asked by At

I use boost::intrusive_ptr a lot to keep instances of certain classes alive. At some point my program expects all boost::intrusive_ptrs to have been deleted, so that the under laying object is released.

A problem that I seem to frequently run into lately is that not all boost::intrusive_ptr are deleted and said object is still "alive". This is a bug.

I need to be able to detect where those pointers are. Getting a list of the source-file/line numbers of where they were created is good enough (for example, if if the debug output tells me that a pointer is still alive that was created here:

foo.m_ptr = foo::create<Foo>();

where m_ptr is a boost::intrusive_ptr<Foo> and/or boost::intrusive<Foo const> then I can figure out that the pointer that still wasn't destructed is Foo::m_ptr ;).

Of course, in this case, m_ptr was already created before - maybe even assigned. So what is important here is not to track the constructor of boost::intrusive_ptr but rather the place where the reference count is (last) incremented.

The above assignment function looks as follows:

intrusive_ptr<T>& operator=(intrusive_ptr<T> const& rhs)
{
    intrusive_ptr<T>(rhs).swap(*this);
    return *this;
}

Hence, this copy-constructs a temporary from rhs, incrementing the reference count of the object that rhs points to, then swaps that with *this so that the temporary now points to what *this pointed to before and the current object points to what rhs points to. Finally the temporary is destructed causing a decrement on the ref count of what *this pointed to.

Getting the call address from the intrusive_ptr_add_ref would hence require unwinding the stack twice (if not three times), possibly depending on optimization level.

As such I think it cannot be avoided to replace boost::intrusive_ptr with my own class, lets say utils::intrusive_ptr, which would implement the above function as follows:

[[gnu::noinline]] intrusive_ptr<T>& operator=(intrusive_ptr<T> const& rhs)
{
    intrusive_ptr<T>(rhs, __builtin_return_address(0)).swap(*this);
    return *this;
}

which uses [[gnu::noinline]] to be sure that __builtin_return_address(0) returns the return address of the current function. This return address is passed to the constructor to register it (the return address can easily be converted to to call location later on, ie when printing the list of still existing pointers).

At the same time it is required to remove the location that was stored for rhs. It seems to me that the best way to make sure of this is to literally store the location in the intrusive_ptr itself. This location then would also be swapped by swap and thus destructed when leaving this scope.

Registration of existing intrusive_ptrs can be done by their address: every constructor has an address (its this pointer) that will be unique and can be used as a key in a map. Upon destruction this key is simply removed again.

This way, using the map, there is a list of all existing intrusive_ptr objects, which store their own location.

Is my reasoning correct here? Or is there another way to do this?

EDIT:

I added a new class that is basically a copy of boost::intrusive_ptr, but stripped of anything boost and that only supports c++17 (and up). I didn't compile it yet, but this should serve as the basis(?). See https://github.com/CarloWood/ai-utils/blob/master/intrusive_ptr.h

0

There are 0 answers