Crash Safari with the Web Audio API

Back in the fall of 2016, I was working on adding the equalizer window to Winamp2-js. Unfortuneatly, wireing up the BiquadFilterNodes nessesary to get the correct behavior triggered a bug in Safari, both desktop and mobile, causing Safari to crash hard.

Screenshot of Safari crashing

The most interesting part of the crash report was:

Thread 11 Crashed:: com.apple.audio.IOThread.client
0 com.apple.WebCore 0x00007fff9f9dbb35 WTF::CrashOnOverflow::crash() + 5
1 com.apple.WebCore 0x00007fff9f9dbb29 WTF::CrashOnOverflow::overflowed() + 9
2 com.apple.WebCore 0x00007fff9fa84b77 WebCore::BiquadProcessor::process(WebCore::AudioBus const*, WebCore::AudioBus*, unsigned long) + 215

To be clear, I have no idea what exactly that means, but it looks intresting. I reported the bug in their bug tracker and figured I'd wait until I heard back to invest more energy in the feature. As months passed, and it became obvious that this bug was not a priority for the Safari team, I occasionally would revist the bug, trying to either narrow down what was causing it, or find some way to work around it.

Finally, in April of this year, 2017, I figured out that it was related to the order in which the audio nodes were being connected, and was able to come up with a workaround for Winamp2-js.

This also allowed me to create a much more minimal script that triggered the bug, and I thought I would share that today:

<script>
var context = new (window.AudioContext || window.webkitAudioContext)();

function start() {
var source = context.createOscillator();
var chanMerge = context.createChannelMerger(2);
var filter = context.createBiquadFilter();

source.connect(chanMerge, 0, 0);
source.connect(chanMerge, 0, 1);
// Connecting the channel merger directly to
// the biquad filter seems to be the problem.
chanMerge.connect(filter);
filter.connect(context.destination);

source.start(0);
source.stop(2); // This triggers the crash.
}
</script>
<p>Safari will crash on/before the end of the tone</p>
<button onclick="start()">Play then crash</button>

Open this page in Safari, click the button, wait two seconds and watch it crash.