Should we use JUCE_LEAK_DETECTOR in base classes?


#1

I’ve been chasing a brain-melting, keyboard smashing bug for the last three days where the leak detector has been firing off in one class no matter what I do - even if I just do a local allocation on the stack, as soon as the object goes out of scope I get a dangling pointer leaked object assertion claiming I’m trying to double delete which obviously makes no sense.

The base class uses the JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR macro, as does the derived class. Looking through JUCE code, I couldn’t find any instances of base classes using this macro. I recognize the overhead + redundancy of having two instances of the leak detector in the class hierarchy, but is it actually harmful to do so? If so, can we get some documentation on it so nobody else has to endure the debugging suffering I have experienced?


#2

Component uses JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR as do the derived classes such as Slider. Could it be something like having virtual methods without a virtual destructor in the base class? I’m not sure why it would think you had deleted it twice though. Also is it possible to accidently pass the same value to both instances of JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR

I’m not sure this would even compile, but maybe something like…

class Base
{
    //... stuff goes here
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Base)
}

class Derived : public Base
{
    //... stuff goes here
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Base) // this should be Derived not Base
}

#3

OK I’ve just checked and sure enough I think it’s likely that your Base class doesn’t have a virtual destructor. For example this causes a leak.

struct Base
{
    Base() {}
    ~Base() {} // add virtual keyword here to stop the leak
    virtual void someMethod() = 0;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Base)
};

struct Derived : public Base
{
    Derived() {}
    ~Derived() {}
    void someMethod() override {}
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Derived)
};

// do this in the code somewhere
ScopedPointer<Base> obj = new Derived();

#4

Thanks for thinking about this and sharing ideas. My destructor is virtual, and the specified classes in the leak detector macro are correct. Great point on Component being a base class which contains the leak detector.

I legitimately cannot pinpoint what is causing this. As a workaround I’m not using the leak detector macro in my base class and only in the derived classes - that seems to work it out, hack as it may be.

In an extremely strange twist, I made a direct copy of the class header/implementation file, renamed it to something similar, and ran my same tests - the copied class doesn’t produce leak errors, but the original class does. I quadruple checked there were no conflicting/duplicate symbols, nuked the build folders + derived data, nothing seems to produce logic.

For some insight into the code itself, you can check it out (rev. 93062f6). The base class is app/Source/hmnz_ValueTreeObject.h, the derived class is app/Source/hmnz_Automation.h. Setting a breakpoint in app/Source/hmnz_AutomationUnitTest.h should allow you to step through instantiation and deletion - that’s where it’s blowing up. FWIW this is under clang, and the .jucer project should compile out of the box on macOS.


#5

The plot thickens. After removing the base class leak detector I get a crash in the actual app when beginning an undo transaction, with the debugger complaining about an access violation on a const string. According to @jules this is likely because of a dangling pointer, which is believed to be the culprit already, so I guess there is in fact some kind of problem with the code…?


#6

Hmmm I can’t replicate anything in my simple example but I wonder if this might have something to do with a mixing of templates and virtual methods? Certainly seems odd though.