Decoding OSX crash


#1

I’ve got an initalization problem on the latest Logic sometimes with one of my plugins. Probably timing related as the debug version doesn’t crash for people - and the release build only sometimes crashes (and I can’t replicate it).

However - this is the start of the crash report. What exactly is this saying? What would cause a release build to issue a SIGTRAP?

Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY

Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]


#2

I should add that last thing in the stack trace is the third recursive call to this:
0x00000001cbd49088 juce::ValueTree::SharedObject::SharedObject(juce::ValueTree::SharedObject const&) + 200


#3

don’t the crash reports normally say which thread crashed?


#4

Yeah, the main thread. There aren’t any other threads running at this point in my plugin either - the audio processor is still constructing. So it’s bloody weird!


#5

Do you have the dSYM files?
@samuel wrote a nice post about how to read the symbols here:

I am about to dive into that topic, but we didn’t generate the dSYMs for our current release, so now I only see the stacks coming in with tied hands…
I will read here with great curiosity :slight_smile:

Good luck!


#6

I might be able to find them … it’s an old build before we got really organised!


#7

FWIW I’m pretty sure I’ve had a similar crash in that copy constructor when the ValueTree it’s copying has already been deleted.

The upshot was that we were using valueTreePropertyChanged() callbacks which were resulting in other ValueTree objects being destroyed. Of course, using component-type listeners there are the bailout checkers to prevent this kind of thing. But you can get bitten by ValuerTrees if you’re not careful.


#8

That’s odd, since the ValueTree is simply wrapping a SharedObject inheriting a ReferenceCountedObject. If a ReferenceCountedObject is deleted while being accessed, it means,
a) there is a threading problem, or
b) could handing ValueTrees as references create such problems (by not increasing the reference count)?

HTH


#9

Yeah, it was a reference. Trying to minimise unnecessary inc/dec ref counts, but in this case it was needed.


#10

:joy: - SCNR


#11

TBH it was a useful optimisation where it is unnecesary. We have only one edge case where this causes the issue as the object containing the callback actually owns the ValueTree


#12

"Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an attached debugger the chance to interrupt the process at a specific point in its execution. You can trigger this exception from your own code using the __builtin_trap() function. If no debugger is attached, the process is terminated and a crash report is generated.

Lower-level libraries (e.g. libdispatch) will trap the process upon encountering a fatal error. Additional information about the error can be found in the Additional Diagnostic Information section of the crash report, or in the device’s console."

Apparently…


#13

Right, RDI should be the ‘this’ pointer right?

Martin - how did that work, as the copy constructor doesn’t trigger any of the callbacks?


#14

So, slightly longer story… We’re using value trees to manage almost all the callbacks instead of using buttonClicked(), sliderValueChanged() etc (except to update the appropriate value tree property).

So we have a ValueTree member accessed by reference using a getValueTree() method. (Actaully, @daniel returning by value wouldn’t help in this case.)

void MyButton::mouseUp (const MouseEvent& e)
{
    Button::mouseUp (e);

    auto properties = getValueTree();
    
    ///.... do something with properties
}

The base class mouseUp() can ultimately trigger an action that causes this button to be deleted (e.g., in a save dialog or something like that). So when we get to the getValueTree() call then the ValueTree has been destroyed.

This form is also problematic:

void MyButton::mouseUp (const MouseEvent& e)
{
    auto& properties = getValueTree();

    Button::mouseUp (e);
    
    ///.... do something with properties
}

So grabbing the ValueTree by value before the mouseUp() base class call is the solution:

void MyButton::mouseUp (const MouseEvent& e)
{
    auto properties = getValueTree();

    Button::mouseUp (e);
    
    ///.... do something with properties
}

We have a property that gets set in the component destructor to indicate the component connected to the ValueTree has been deleted. So we can bail immediately in that case.

Anyway, this is a very specific use case, but I recognised the same crash. Sorry, it may be a red herring!


#15

It’s a good point. I don’t even have many red herrings right now.

I’ve written the whole thing up on stack overflow now (link below), it really doesn’t make any sense at the moment.

  • Why EXC_BREAKPOINT
  • Why at that instruction
  • Is the instruction pointer reporting reliable at +200 from the start of the function

#16

Can you elaborate about that?

So you incremented the reference count in the method body, because you didn’t want to increment it before. Seems to me like you are rescuing yourself from undefined behaviour into undefined behaviour. It simply changes the timings. If Button::mouseUp() is completely synchronous, it might solve your problem, but it stays kludgy…

That can’t happen, if you use the ValueTree correctly. All logic, even adding and removing listeners, happens on the shared object, which is reference counted. So if your structure, be it a method body or a class member, has a ValueTree object (not a reference), it will stop the reference counter from dropping to 0.

If that was not the case, it would put the whole ValueTree and ReferenceCountedObject implementation in question.

Here is the addListener method:


#17

Well, in this case:

void MyButton::mouseUp (const MouseEvent& e)
{
    Button::mouseUp (e);

    auto properties = getValueTree();
    
    ///.... do something with properties
}

getValueTree() was returning a reference to a deleted object, so even if it was by value it was UB. Continuing within a object method when the this pointer has been deleted is OK as long as you don’t use it. That happens all the time in the methods that delete this kind of smart pointer.

This form has the same issue of course as it doesn’t retain the ref count:

void MyButton::mouseUp (const MouseEvent& e)
{
    auto& properties = getValueTree();

    Button::mouseUp (e);
    
    ///.... do something with properties
}

… and the object gets deleted.

I don’t see anything UB about this:

void MyButton::mouseUp (const MouseEvent& e)
{
    auto properties = getValueTree();

    Button::mouseUp (e);
    
    ///.... do something with properties
}

As the calls are synchronous. The technique is analogous to the BailOutChecker stuff JUCE does in these cases.