Use-after-free in DelayedRenderer::processWrapped - crashes the DAW on dense MIDI (patch included)
-
Hi all,
I've been building a multi-mic drum kit, and for days it randomly crashed my DAW whenever I played fast, busy patterns (quick hi-hats, fast toms). It happened in both Ableton and Reaper, but never in the HISE standalone, which is what made it so hard to pin down. Windows reported heap corruption (
0xC0000374), and the denser the MIDI, the faster it died, sometimes seconds, sometimes 10 minutes.I spent past Sunday debugging it with Claude Code, mostly to read the crash dumps for me.
Things we ruled out first, in case it saves anyone time: my scripts (it still crashed with everything disabled), round-robin, choke scripts, voice limits, monoliths vs streamed samples, CPU affinity, deferCallbacks, and the number of samplers. It even crashed with a stripped-down project and an empty script, so it clearly wasn't my code.
We eventually ran it under Application Verifier (PageHeap), which turns the delayed heap corruption into an immediate access violation, and symbolicated the dump. Here's what Claude Code found, pasting its explanation:
The crash is in
hi_core/hi_core/MainControllerHelpers.cpp, inDelayedRenderer::processWrapped.When the host sends a buffer larger than the prepared block size (the "FL Studio violates the block size" workaround), the function chunks the buffer and calls itself recursively — passing its own member
delayedMidiBufferas themidiMessagesargument:processWrapped(chunk, delayedMidiBuffer); // passes the memberSo inside the recursive call,
midiMessagesisdelayedMidiBuffer. Then in the shortBuffer paths:MidiBuffer::Iterator it(midiMessages); // iterator into delayedMidiBuffer delayedMidiBuffer.clear(); // clears the buffer being iterated for (auto& e : shortBuffer) delayedMidiBuffer.addEvent(...); // and reallocs it while (it.getNextEvent(m, pos)) // dangling iterator -> use-after-freeThe
clear()and reallocatingaddEvent()invalidate the iterator, which is then dereferenced. It only appears in a host because the standalone uses a fixed block size, so that recursive path never runs.
The fix (give the recursion its own buffer so the two can't alias):
// MainControllerHelpers.h — next to delayedMidiBuffer: MidiBuffer chunkMidiBuffer; // MainControllerHelpers.cpp — in the oversized-buffer branch: chunkMidiBuffer.clear(); chunkMidiBuffer.addEvents(midiMessages, thisOffset, numThisTime, -thisOffset); ... processWrapped(chunk, chunkMidiBuffer); // was: delayedMidiBufferThere are two
shortBufferpaths that clear/regrowdelayedMidiBufferwhile iteratingmidiMessages, so this closes both.Result: my full kit used to crash within seconds or minutes. After the patch it ran +25 minutes in Reaper with PageHeap still on (so any remaining bad access would fault immediately) and +25 minutes in Ableton on the exact project that always crashed. No crash.
Minor side note it also spotted:
alloca(numChannels * sizeof(float*) * numChannels)in the same function allocatesnumChannels²— harmless, but looks like a typo.Hope it's useful information. Thanks for HISE!
Jon