Memory allocation deep inside VST3PluginInstance#processBlock

Hey JUCE friends,

I recently had a go at running my plugin host app under the new Realtime Sanitizer. This sanitizer is set to come out as part of Clang 20, but I went ahead and compiled it from source to try it out: GitHub - realtime-sanitizer/rtsan: Central hub for RealtimeSanitizer and its associated tooling

It flagged to me an allocation that’s inside of VST3PluginInstance#processBlock.

Here’s the full stack trace:

==40309==ERROR: RealtimeSanitizer: unsafe-library-call
Intercepted call to real-time unsafe function `malloc` in real-time context!
    #0 0x0001045dcaec in malloc rtsan_interceptors.cpp:442
    #1 0x000196e47b54 in _malloc_type_malloc_outlined+0x60 (libsystem_malloc.dylib:arm64+0x1cb54)
    #2 0x000196fdd368 in operator new(unsigned long)+0x30 (libc++abi.dylib:arm64+0x17368)
    #3 0x000300016b84 in void* std::__1::__libcpp_operator_new[abi:ne180100]<unsigned long>(unsigned long) new:303
    #4 0x000300016aec in std::__1::__libcpp_allocate[abi:ne180100](unsigned long, unsigned long) new:327
    #5 0x00030002fd14 in std::__1::allocator<float*>::allocate[abi:ne180100](unsigned long) allocator.h:125
    #6 0x00030002fbf0 in std::__1::__allocation_result<std::__1::allocator_traits<std::__1::allocator<float*>>::pointer> std::__1::__allocate_at_least[abi:ne180100]<std::__1::allocator<float*>>(std::__1::allocator<float*>&, unsigned long) allocate_at_least.h:55
    #7 0x00030002fb30 in std::__1::__split_buffer<float*, std::__1::allocator<float*>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<float*>&) __split_buffer:343
    #8 0x00030002f8ec in std::__1::__split_buffer<float*, std::__1::allocator<float*>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<float*>&) __split_buffer:339
    #9 0x0003000427d0 in float** std::__1::vector<float*, std::__1::allocator<float*>>::__push_back_slow_path<float*>(float*&&) vector:1451
    #10 0x000300042670 in std::__1::vector<float*, std::__1::allocator<float*>>::push_back[abi:ne180100](float*&&) vector:1479
    #11 0x0003001600cc in void juce::HostBufferMapper::associateBufferTo<float>(Steinberg::Vst::AudioBusBuffers&, std::__1::vector<float*, std::__1::allocator<float*>>&, juce::AudioBuffer<float>&, juce::ChannelMapping const&, int) const juce_VST3Common.h:1164
    #12 0x00030015ff70 in Steinberg::Vst::AudioBusBuffers* juce::HostBufferMapper::getVst3LayoutForJuceBuffer<float>(juce::AudioBuffer<float>&) juce_VST3Common.h:1136
    #13 0x00030015e80c in void juce::VST3PluginInstance::associateWith<float>(Steinberg::Vst::ProcessData&, juce::AudioBuffer<float>&) juce_VST3PluginFormat.cpp:3574
    #14 0x00030015e350 in void juce::VST3PluginInstance::processAudio<float>(juce::AudioBuffer<float>&, juce::MidiBuffer&, Steinberg::Vst::SymbolicSampleSizes, bool) juce_VST3PluginFormat.cpp:2775
    #15 0x000300152034 in juce::VST3PluginInstance::processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) juce_VST3PluginFormat.cpp:2698

Given processBlock functions are always expected to be called from an audio/realtime thread, I find this quite surprising!

Could the JUCE team consider fixing this as a bug?

Thanks a lot,
Moe

3 Likes

Re-reading my own stack trace quickly, the culprit is:

    #11 0x0003001600cc in void juce::HostBufferMapper::associateBufferTo<float>(Steinberg::Vst::AudioBusBuffers&, std::__1::vector<float*, std::__1::allocator<float*>>&, juce::AudioBuffer<float>&, juce::ChannelMapping const&, int) const juce_VST3Common.h:1164

Which calls push_back on a vector.

It would be worth checking which other code paths this potentially affects.

I assume this has to be done on realtime thread for a legitimate reason, in which case sizing the vector in question to some sensible upper bound will likely mitigate this allocation. If it can be done elsewhere, I would expect this vector to be sized in a prepareToPlay or similar callback.

Thanks for reporting! There’s now a fix for this issue on the develop branch:

3 Likes