Memory Leak Tool


#1

I had a memory leak driving me insane last night.  The JUCE_LEAK_DETECTOR was firing but I couldn't work out where it was coming from.  

Anyway.  Solved now, but thought this might be very useful for someone else.  So here's a little class you can pop in a member variable to any object that's showing up as leaking, and then it gives you the stack trace showing where the leaked objects were created. 

It's based on the JUCE_LEAK_DETECTOR but prioritising information over performance...

/** Stick an instance of this in a class that JUCE is reporting memory leaks for
and you'll get a nice report with a stack trace which should lead you to a
happy place quite quickly.
It might slow down your program a bit, so don't forget to take it out once your
problems are all fixed. In fact. I've wrapped it in a #ifdef JUCE_DEBUG so you
can't forget. Forgetting would be bad.
If the JUCE_LEAK_DETECTOR assert fires first, just push the continue button
on your debugger and this should report straight afterwards.
*/
#ifdef JUCE_DEBUG
class AdvancedLeakDetector {
public:
AdvancedLeakDetector() {
getBackTraceHash().set((void *)this, SystemStats::getStackBacktrace());
}
~AdvancedLeakDetector() {
getBackTraceHash().remove((void *) this);
}
private:
typedef HashMap<void*, String> BackTraceHash;
struct HashHolder {
~HashHolder() {
if (traces.size()>0)
{
/* Memory leak info. */
DBG("Found " + String(traces.size()) + " possible leaks");
for (BackTraceHash::Iterator i (traces); i.next();)
{
DBG("-----");
DBG (i.getValue());
}
jassertfalse;
}
}
BackTraceHash traces;
};
BackTraceHash & getBackTraceHash() {
static HashHolder holder;
return holder.traces;
}
};
#endif

#2

Ah - that's a nice idea. And a good use for SystemStats::getStackBacktrace!


#3

You wouldn't believe how quickly I found my problem.  

I thought about having it as a #define on the existing JUCE_LEAK_DETECTOR but I think peformance will drop to a number close to zero...


#4

@bazrush Thanks a lot!

Here is a formatted and non-html-escaped version of the snippet (probably this broke during forum migration):

/** Stick an instance of this in a class that JUCE is reporting memory leaks for
and you'll get a nice report with a stack trace which should lead you to a
happy place quite quickly.
It might slow down your program a bit, so don't forget to take it out once your
problems are all fixed. In fact. I've wrapped it in a #ifdef JUCE_DEBUG so you
can't forget. Forgetting would be bad.
If the JUCE_LEAK_DETECTOR assert fires first, just push the continue button
on your debugger and this should report straight afterwards.
*/
#ifdef JUCE_DEBUG
class AdvancedLeakDetector {
public:

    AdvancedLeakDetector () {
        getBackTraceHash ().set ((void *)this, SystemStats::getStackBacktrace ());
    }

    ~AdvancedLeakDetector() {
        getBackTraceHash ().remove ((void *) this);
    }

private:

    typedef HashMap<void*, String> BackTraceHash;

    struct HashHolder {
        ~HashHolder () {
            if (traces.size () > 0)
            {
                /* Memory leak info. */
                DBG ("Found " + String (traces.size ()) + " possible leaks");

                for (BackTraceHash::Iterator i (traces); i.next ();)
                {
                    DBG ("-----");
                    DBG (i.getValue ());
                }

                jassertfalse;
            }
        }
        BackTraceHash traces;
    };

    BackTraceHash& getBackTraceHash () {
        static HashHolder holder;
        return holder.traces;
    }

};
#endif

#5

Very neat! :slight_smile:


#6

integrating this in the juce LeakDetector and enable a global macro to switch it on would be extremely profitable. you get the report of the standard juce LeakDetector, if you cannot understand what’s going on you enable this extra reporting macro in Projucer, rebuild, run again, and everything should be more clear :slight_smile:


#7

I think enabling it globally would be too expensive in performance, but I like the idea of being able to add it to a class that you’re struggling to debug.


#8

yes, something like

#define JUCE_ADVANCED_LEAK_DETECTOR_FOR_CLASS MyLeakingClass

#9

Yeah - if memory serves me correctly you definitely don’t want to enable this for all the classes at once. It was slow enough on the one class (although definitely valuable).


#10

Resurrecting this old thread to say thanks @jimc! Your leak detector was super helpful in tracking down this bug. I’ve added a HeavyweightLeakedObjectDetector and corresponding JUCE_HEAVYWEIGHT_LEAK_DETECTOR macro to JUCE in this commit so other people can use it without patching JUCE.


#11

Fantastic :slight_smile:

I should make a list of my other debugging ideas… :wink:


#12

Another thank you @jimc! The HeavyweightLeakedObjectDetector class just saved me at least half an hour tracking down an intermittent leak in someone else’s code.


#13

But this is already defined when I use

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EqEditor)

Isn’t it?

When I add the Detector macro in this class, I get the error message

Error C2535 'const char *EqEditor::getLeakedObjectClassName(void) noexcept': member function already defined or declared (compiling source file .-- include file --position --

Which points to the former macro


#14

Both the JUCE_HEAVYWEIGHT_LEAK_DETECTOR and the normal JUCE_LEAK_DETECTOR macros declare a getLeakedObjectClassName() so you’ll get duplicate symbol errors if you have both in the class at once. You should never leave one of the heavyweight detectors hanging around inside a class though so you can just swap it in when you need to track down a leak.


#15

Clear! Thanks! :+1: