Can anybody explain this crash in ModalComponentManager?

Unhandled exception at 0x0000000141591633 (Waveform 13.exe) in 1000000002192e18f4d87b8b8_UnknownLocation.dmp: 0xC0000005: Access violation reading location 0x0000000000000000.

This is the #1 crash I’m seeing from users.

'
rax=0000000000000001 rbx=0000000000000000 rcx=0000000000000000
rdx=00000000005e0000 rsi=000000000603b360 rdi=ffffffffffffffff
rip=0000000141591633 rsp=000000000014fcf0 rbp=0000000000000000
 r8=0000000000402340  r9=0000000000000001 r10=0000000000000003
r11=000000000014fc50 r12=0000000000000000 r13=000000014d71ffa0
r14=00000000037d1978 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
Waveform_13!juce::OwnedArray::getUnchecked+0x3 [inlined in Waveform_13!juce::ModalComponentManager::handleAsyncUpdate+0x43]:
00000001`41591633 498b1c0f        mov     rbx,qword ptr [r15+rcx] ds:00000000`00000000=????????????????
  *** Stack trace for last set context - .thread/.cxr resets it
Call Site
Waveform_13!juce::OwnedArray::getUnchecked
Waveform_13!juce::ModalComponentManager::handleAsyncUpdate
Waveform_13!juce::InternalMessageQueue::dispatchMessage
Waveform_13!juce::InternalMessageQueue::dispatchMessages
Waveform_13!juce::InternalMessageQueue::dispatchNextMessage
Waveform_13!juce::detail::dispatchNextMessageOnSystemQueue
Waveform_13!juce::MessageManager::runDispatchLoop
Waveform_13!juce::JUCEApplicationBase::main
*** WARNING: Unable to verify timestamp for kernel32.dll

It’s crashing on the call to getUnchecked(). I can’t really think of a reason other than heap corruption, but it’s always here. I’d expect a corrupt heap to be more random where it crashed.

void ModalComponentManager::handleAsyncUpdate()
{
    for (int i = stack.size(); --i >= 0;)
    {
        auto* item = stack.getUnchecked (i);

        if (! item->isActive)
        {
            std::unique_ptr<ModalItem> deleter (stack.removeAndReturn (i));
            Component::SafePointer<Component> compToDelete (item->autoDelete ? item->component : nullptr);

            for (int j = item->callbacks.size(); --j >= 0;)
                item->callbacks.getUnchecked (j)->modalStateFinished (item->returnValue);

            compToDelete.deleteAndZero();

            detail::ComponentHelpers::ModalComponentManagerChangeNotifier::getInstance().modalComponentManagerChanged();
        }
    }
}
1 Like

Do you see the same crash on macOS, or only on Windows?

I think the call to item->callbacks.getUnchecked is safe, because there doesn’t seem to be any way of removing callbacks from an item after they’ve been registered.

My only idea is that, if stack.size() >= 2 first time through the loop, and then the call to modalStateFinished or modalComponentManagerChanged causes more than one item to be removed, then the index passed to stack.getUnchecked next time through the loop will no longer be within the container.

I’m not sure how this could happen in practice, because it appears that only handleAsyncUpdate() can remove items from the stack - although, this function is only protected, so maybe it could be called reentrantly from a derived type.

I also note that Component is a friend of ModalComponentManager, so perhaps something in Component is accidentally breaking the invariants of the ModalComponentManager, although I don’t see anything like that from a cursory scan through juce_Component.cpp.

Maybe you could try the following:

  • Add a jassert (i < stack.size()) directly in the loop, before the stack.getUnchecked() call. This will fire if the index is not within the container. If this fires, then we can be farily certain about the cause of the crash.
  • Check your codebase to see whether you’ve derived from ModalComponentManager anywhere, and if so, whether handleAsyncUpdate is called directly on an instance of the derived type. You could also add a flag to ModalComponentManager that’s set only when handleAsyncUpdate is in progress, and assert if the flag is already set when entering that function. This should warn you about dangerous potentially-reentrant calls.

So far I have only seen this one Windows. I have not derived from ModalComponentManager.

Then only other thing I could think is that the ModalComponentManager gets deleted and then handleAsyncUpdate gets called, but that shouldn’t be possible either. It’s very confusing.