diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index 47902f2f0b..a2bfa8a0bb 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -408,6 +408,91 @@ public: Array lastMidiDevices; private: + /* This class can be used to ensure that audio callbacks use buffers with a + predictable maximum size. + + On some platforms (such as iOS 10), the expected buffer size reported in + audioDeviceAboutToStart may be smaller than the blocks passed to + audioDeviceIOCallback. This can lead to out-of-bounds reads if the render + callback depends on additional buffers which were initialised using the + smaller size. + + As a workaround, this class will ensure that the render callback will + only ever be called with a block with a length less than or equal to the + expected block size. + */ + class CallbackMaxSizeEnforcer : public AudioIODeviceCallback + { + public: + explicit CallbackMaxSizeEnforcer (AudioIODeviceCallback& callbackIn) + : inner (callbackIn) {} + + void audioDeviceAboutToStart (AudioIODevice* device) override + { + maximumSize = device->getCurrentBufferSizeSamples(); + storedInputChannels .resize ((size_t) device->getActiveInputChannels() .countNumberOfSetBits()); + storedOutputChannels.resize ((size_t) device->getActiveOutputChannels().countNumberOfSetBits()); + + inner.audioDeviceAboutToStart (device); + } + + void audioDeviceIOCallback (const float** inputChannelData, + int numInputChannels, + float** outputChannelData, + int numOutputChannels, + int numSamples) override + { + jassert ((int) storedInputChannels.size() == numInputChannels); + jassert ((int) storedOutputChannels.size() == numOutputChannels); + ignoreUnused (numInputChannels, numOutputChannels); + + int position = 0; + + while (position < numSamples) + { + const auto blockLength = jmin (maximumSize, numSamples - position); + + initChannelPointers (inputChannelData, storedInputChannels, position); + initChannelPointers (outputChannelData, storedOutputChannels, position); + + inner.audioDeviceIOCallback (storedInputChannels.data(), + (int) storedInputChannels.size(), + storedOutputChannels.data(), + (int) storedOutputChannels.size(), + blockLength); + + position += blockLength; + } + } + + void audioDeviceStopped() override + { + inner.audioDeviceStopped(); + } + + private: + struct GetChannelWithOffset + { + int offset; + + template + auto operator() (Ptr ptr) const noexcept -> Ptr { return ptr + offset; } + }; + + template + void initChannelPointers (Ptr&& source, Vector&& target, int offset) + { + std::transform (source, source + target.size(), target.begin(), GetChannelWithOffset { offset }); + } + + AudioIODeviceCallback& inner; + int maximumSize = 0; + std::vector storedInputChannels; + std::vector storedOutputChannels; + }; + + CallbackMaxSizeEnforcer maxSizeEnforcer { *this }; + //============================================================================== class SettingsComponent : public Component { @@ -533,7 +618,7 @@ private: const String& preferredDefaultDeviceName, const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions) { - deviceManager.addAudioCallback (this); + deviceManager.addAudioCallback (&maxSizeEnforcer); deviceManager.addMidiInputDeviceCallback ({}, &player); reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions); @@ -544,7 +629,7 @@ private: saveAudioDeviceState(); deviceManager.removeMidiInputDeviceCallback ({}, &player); - deviceManager.removeAudioCallback (this); + deviceManager.removeAudioCallback (&maxSizeEnforcer); } void timerCallback() override