@@ -408,6 +408,91 @@ class StandalonePluginHolder : private AudioIODeviceCallback,
408408 Array<MidiDeviceInfo> lastMidiDevices;
409409
410410private:
411+ /* This class can be used to ensure that audio callbacks use buffers with a
412+ predictable maximum size.
413+
414+ On some platforms (such as iOS 10), the expected buffer size reported in
415+ audioDeviceAboutToStart may be smaller than the blocks passed to
416+ audioDeviceIOCallback. This can lead to out-of-bounds reads if the render
417+ callback depends on additional buffers which were initialised using the
418+ smaller size.
419+
420+ As a workaround, this class will ensure that the render callback will
421+ only ever be called with a block with a length less than or equal to the
422+ expected block size.
423+ */
424+ class CallbackMaxSizeEnforcer : public AudioIODeviceCallback
425+ {
426+ public:
427+ explicit CallbackMaxSizeEnforcer (AudioIODeviceCallback& callbackIn)
428+ : inner (callbackIn) {}
429+
430+ void audioDeviceAboutToStart (AudioIODevice* device) override
431+ {
432+ maximumSize = device->getCurrentBufferSizeSamples ();
433+ storedInputChannels .resize ((size_t ) device->getActiveInputChannels () .countNumberOfSetBits ());
434+ storedOutputChannels.resize ((size_t ) device->getActiveOutputChannels ().countNumberOfSetBits ());
435+
436+ inner.audioDeviceAboutToStart (device);
437+ }
438+
439+ void audioDeviceIOCallback (const float ** inputChannelData,
440+ int numInputChannels,
441+ float ** outputChannelData,
442+ int numOutputChannels,
443+ int numSamples) override
444+ {
445+ jassert ((int ) storedInputChannels.size () == numInputChannels);
446+ jassert ((int ) storedOutputChannels.size () == numOutputChannels);
447+ ignoreUnused (numInputChannels, numOutputChannels);
448+
449+ int position = 0 ;
450+
451+ while (position < numSamples)
452+ {
453+ const auto blockLength = jmin (maximumSize, numSamples - position);
454+
455+ initChannelPointers (inputChannelData, storedInputChannels, position);
456+ initChannelPointers (outputChannelData, storedOutputChannels, position);
457+
458+ inner.audioDeviceIOCallback (storedInputChannels.data (),
459+ (int ) storedInputChannels.size (),
460+ storedOutputChannels.data (),
461+ (int ) storedOutputChannels.size (),
462+ blockLength);
463+
464+ position += blockLength;
465+ }
466+ }
467+
468+ void audioDeviceStopped () override
469+ {
470+ inner.audioDeviceStopped ();
471+ }
472+
473+ private:
474+ struct GetChannelWithOffset
475+ {
476+ int offset;
477+
478+ template <typename Ptr>
479+ auto operator () (Ptr ptr) const noexcept -> Ptr { return ptr + offset; }
480+ };
481+
482+ template <typename Ptr, typename Vector>
483+ void initChannelPointers (Ptr&& source, Vector&& target, int offset)
484+ {
485+ std::transform (source, source + target.size (), target.begin (), GetChannelWithOffset { offset });
486+ }
487+
488+ AudioIODeviceCallback& inner;
489+ int maximumSize = 0 ;
490+ std::vector<const float *> storedInputChannels;
491+ std::vector<float *> storedOutputChannels;
492+ };
493+
494+ CallbackMaxSizeEnforcer maxSizeEnforcer { *this };
495+
411496 // ==============================================================================
412497 class SettingsComponent : public Component
413498 {
@@ -533,7 +618,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback,
533618 const String& preferredDefaultDeviceName,
534619 const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions)
535620 {
536- deviceManager.addAudioCallback (this );
621+ deviceManager.addAudioCallback (&maxSizeEnforcer );
537622 deviceManager.addMidiInputDeviceCallback ({}, &player);
538623
539624 reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions);
@@ -544,7 +629,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback,
544629 saveAudioDeviceState ();
545630
546631 deviceManager.removeMidiInputDeviceCallback ({}, &player);
547- deviceManager.removeAudioCallback (this );
632+ deviceManager.removeAudioCallback (&maxSizeEnforcer );
548633 }
549634
550635 void timerCallback () override
0 commit comments