The situation: My code uses a reference-counting pointer class (similar in spirit to boost::intrusive_ptr) to manage its dynamic allocations and avoid memory leaks.
AFAICT this works fine and does not leak memory; however, when I run Clang Static Analyzer on my code that uses this class, Clang doesn't seem to understand the reference-counting logic and therefore generates warnings about memory-leaks and use-after-free errors.
The toy code below (which is greatly simplified for clarity) demonstrates the problem -- it compiles and runs without leaking memory (as indicated by running allocated-object-count it prints to stdout
as it goes), but if I run scan-build
on it, I get this output:
$ scan-build g++ testrefcount.cpp -o testrefcount
scan-build: Using '/Users/jaf/checker-279/bin/clang' for static analysis
testrefcount.cpp:43:54: warning: Use of memory after it is freed
const MyRefCountedObject * GetObject() const {return _obj;}
^~~~~~~~~~~
testrefcount.cpp:52:8: warning: Potential memory leak
}
^
2 warnings generated.
scan-build: 2 bugs found.
scan-build: Run 'scan-view /var/folders/q9/9w3p5x650wgfpbcws3zhsc6h0000gn/T/scan-build-2019-01-12-122209-5219-1' to examine bug reports.
My question is, is there any way to modify my code such that Clang's Static Analyzer won't emit false-positives here? I suppose I can just wrap the refence-counting methods in #ifndef __clang_analyzer__
, but that seems like an ugly solution (analogous to putting tape over the "check engine" light) which would prevent CSA from detecting any actual problems with this code, so I'd prefer to avoid that if there is a better approach. (Yes, I know about shared_ptr
and intrusive_ptr
and so on -- I wonder how they deal with this issue?)
Self-contained example code follows:
#include <stdio.h>
// This value will keep track of the number of MyRefCountedObject objects alive in
// the process -- just to verify that this program doesn't actually have a memory leak
static int _objectCounter = 0;
// Objects of this class will be reference-counted by the MyRef class
class MyRefCountedObject
{
public:
MyRefCountedObject(int value) : _value(value), _refCount(0)
{
printf("MyRefCountedObject %p with value %i created. Current number of MyRefCountedObjects is now %i\n", this, _value, ++_objectCounter);
}
~MyRefCountedObject()
{
printf("MyRefCountedObject %p with value %i destroyed. Current number of MyRefCountedObjects is now %i\n", this, _value, --_objectCounter);
}
int GetValue() const {return _value;}
inline void IncrementRefCount() const {_refCount++;}
inline bool DecrementRefCount() const {return (--_refCount == 0);}
private:
inline MyRefCountedObject &operator=(const MyRefCountedObject &); // deliberately unimplemented
const int _value; // An arbitrary payload/data value
mutable int _refCount; // how my MyRef's are currently pointing at this object
};
// Represents one reference to a MyRefCountedObject (like a toy shared_ptr)
class MyRef
{
public:
MyRef(const MyRefCountedObject * item = NULL) : _obj(item) {RefObject();}
MyRef(const MyRef & rhs) : _obj(NULL) {*this = rhs;}
~MyRef() {UnrefObject();}
inline MyRef &operator=(const MyRef & rhs) {this->SetRef(rhs._obj); return *this;}
const MyRefCountedObject * GetObject() const {return _obj;}
private:
void RefObject() {if (_obj) _obj->IncrementRefCount();}
void UnrefObject()
{
if ((_obj)&&(_obj->DecrementRefCount())) delete _obj;
_obj = NULL;
}
void SetRef(const MyRefCountedObject * newObj)
{
if (newObj != _obj)
{
UnrefObject();
_obj = newObj;
RefObject();
}
}
const MyRefCountedObject * _obj;
};
int main(void)
{
MyRef ref;
{
MyRef ref2(new MyRefCountedObject(555));
ref = ref2;
}
printf("At end of main, (ref)'s value is: %i\n", ref.GetObject()->GetValue());
return 0;
}
std::shared_ptr
andstd::enable_shared_from_this
, and (as you probably had already discovered / known) did not get a CSA issue with those. I don't see what those C++ constructs did different to avoid getting flagged. I didn't see anyattribute
tags. So I presume CSA itself only considers the file and not the headers or perhaps not any of the system headers (free pass...?). Harumph. – Forehand