Bug 867174 - Part 2: Protect against invalid sample rates a bit harder; r=padenot
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 06 May 2013 11:34:03 -0400
changeset 131032 f51b726ef31d
parent 130979 b842d26dd5f0
child 131033 5ff22928369b
push id24649
push userryanvm@gmail.com
push date2013-05-08 02:10 +0000
treeherdermozilla-central@b980d32c366f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs867174
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 867174 - Part 2: Protect against invalid sample rates a bit harder; r=padenot
content/media/webaudio/AudioBufferSourceNode.cpp
content/media/webaudio/WebAudioUtils.h
content/media/webaudio/test/Makefile.in
content/media/webaudio/test/test_bug867174.html
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -292,19 +292,26 @@ public:
   TrackTicks GetPosition(AudioNodeStream* aStream)
   {
     if (aStream->GetCurrentPosition() < mStart) {
       return aStream->GetCurrentPosition();
     }
     return mStart + mPosition;
   }
 
-  int32_t ComputeFinalOutSampleRate() const
+  uint32_t ComputeFinalOutSampleRate()
   {
-    return static_cast<uint32_t>(IdealAudioRate() / (mPlaybackRate * mDopplerShift));
+    if (mPlaybackRate <= 0 || mPlaybackRate != mPlaybackRate) {
+      mPlaybackRate = 1.0f;
+    }
+    if (mDopplerShift <= 0 || mDopplerShift != mDopplerShift) {
+      mDopplerShift = 1.0f;
+    }
+    return WebAudioUtils::TruncateFloatToInt<uint32_t>(IdealAudioRate() /
+                                                       (mPlaybackRate * mDopplerShift));
   }
 
   bool ShouldResample() const
   {
     return !(mPlaybackRate == 1.0 &&
              mDopplerShift == 1.0 &&
              mSampleRate == IdealAudioRate());
   }
@@ -312,19 +319,21 @@ public:
   void UpdateSampleRateIfNeeded(AudioNodeStream* aStream, uint32_t aChannels)
   {
     if (mPlaybackRateTimeline.HasSimpleValue()) {
       mPlaybackRate = mPlaybackRateTimeline.GetValue();
     } else {
       mPlaybackRate = mPlaybackRateTimeline.GetValueAtTime(aStream->GetCurrentPosition());
     }
 
-    // Make sure the playback rate if something our resampler can work with.
-    if (mPlaybackRate <= 0.0 || mPlaybackRate >= 1024) {
+    // Make sure the playback rate and the doppler shift are something
+    // our resampler can work with.
+    if (ComputeFinalOutSampleRate() == 0) {
       mPlaybackRate = 1.0;
+      mDopplerShift = 1.0;
     }
 
     uint32_t currentOutSampleRate, currentInSampleRate;
     if (ShouldResample()) {
       SpeexResamplerState* resampler = Resampler(aChannels);
       speex_resampler_get_rate(resampler, &currentInSampleRate, &currentOutSampleRate);
       uint32_t finalSampleRate = ComputeFinalOutSampleRate();
       if (currentOutSampleRate != finalSampleRate) {
--- a/content/media/webaudio/WebAudioUtils.h
+++ b/content/media/webaudio/WebAudioUtils.h
@@ -3,16 +3,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WebAudioUtils_h_
 #define WebAudioUtils_h_
 
 #include <cmath>
+#include <limits>
+#include "mozilla/TypeTraits.h"
+#include "mozilla/Assertions.h"
 #include "AudioParamTimeline.h"
 #include "MediaSegment.h"
 
 namespace mozilla {
 
 class AudioNodeStream;
 
 namespace dom {
@@ -90,15 +93,109 @@ struct WebAudioUtils {
 
   /**
    * Convert a stream position into the time coordinate of the destination
    * stream.
    */
   static double StreamPositionToDestinationTime(TrackTicks aSourcePosition,
                                                 AudioNodeStream* aSource,
                                                 AudioNodeStream* aDestination);
+
+  /**
+   * Converts a floating point value to an integral type in a safe and
+   * platform agnostic way.  The following program demonstrates the kinds
+   * of ways things can go wrong depending on the CPU architecture you're
+   * compiling for:
+   *
+   * #include <stdio.h>
+   * volatile float r;
+   * int main()
+   * {
+   *   unsigned int q;
+   *   r = 1e100;
+   *   q = r;
+   *   printf("%f %d\n", r, q);
+   *   r = -1e100;
+   *   q = r;
+   *   printf("%f %d\n", r, q);
+   *   r = 1e15;
+   *   q = r;
+   *   printf("%f %x\n", r, q);
+   *   r = 0/0.;
+   *   q = r;
+   *   printf("%f %d\n", r, q);
+   * }
+   *
+   * This program, when compiled for unsigned int, generates the following
+   * results depending on the architecture:
+   *
+   * x86 and x86-64
+   * ---
+   *  inf 0
+   *  -inf 0
+   *  999999995904.000000 -727384064 d4a50000
+   *  nan 0
+   *
+   * ARM
+   * ---
+   *  inf -1
+   *  -inf 0
+   *  999999995904.000000 -1
+   *  nan 0
+   *
+   * When compiled for int, this program generates the following results:
+   *
+   * x86 and x86-64
+   * ---
+   *  inf -2147483648
+   *  -inf -2147483648
+   *  999999995904.000000 -2147483648
+   *  nan -2147483648
+   *
+   * ARM
+   * ---
+   *  inf 2147483647
+   *  -inf -2147483648
+   *  999999995904.000000 2147483647
+   *  nan 0
+   *
+   * Note that the caller is responsible to make sure that the value
+   * passed to this function is not a NaN.  This function will abort if
+   * it sees a NaN.
+   */
+  template <typename IntType, typename FloatType>
+  static IntType TruncateFloatToInt(FloatType f)
+  {
+    using namespace std;
+
+    MOZ_STATIC_ASSERT((mozilla::IsIntegral<IntType>::value == true),
+                      "IntType must be an integral type");
+    MOZ_STATIC_ASSERT((mozilla::IsFloatingPoint<FloatType>::value == true),
+                      "FloatType must be a floating point type");
+
+    if (f != f) {
+      // It is the responsibility of the caller to deal with NaN values.
+      // If we ever get to this point, we have a serious bug to fix.
+      NS_RUNTIMEABORT("We should never see a NaN here");
+    }
+
+    if (f > FloatType(numeric_limits<IntType>::max())) {
+      // If the floating point value is outside of the range of maximum
+      // integral value for this type, just clamp to the maximum value.
+      return numeric_limits<IntType>::max();
+    }
+
+    if (f < FloatType(numeric_limits<IntType>::min())) {
+      // If the floating point value is outside of the range of minimum
+      // integral value for this type, just clamp to the minimum value.
+      return numeric_limits<IntType>::min();
+    }
+
+    // Otherwise, this conversion must be well defined.
+    return IntType(f);
+  }
 };
 
 }
 }
 
 #endif
 
--- a/content/media/webaudio/test/Makefile.in
+++ b/content/media/webaudio/test/Makefile.in
@@ -15,16 +15,17 @@ MOCHITEST_FILES := \
   test_bug808374.html \
   test_bug827541.html \
   test_bug839753.html \
   test_bug845960.html \
   test_bug856771.html \
   test_bug866570.html \
   test_bug866737.html \
   test_bug867089.html \
+  test_bug867174.html \
   test_bug867203.html \
   test_analyserNode.html \
   test_AudioBuffer.html \
   test_AudioContext.html \
   test_AudioListener.html \
   test_AudioParam.html \
   test_audioParamExponentialRamp.html \
   test_audioParamLinearRamp.html \
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_bug867174.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Crashtest for bug 867174</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SpecialPowers.setBoolPref("media.webaudio.enabled", true);
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  var ctx = new AudioContext();
+
+  var source = ctx.createBufferSource();
+  var buffer = ctx.createBuffer(2, 2048, 8000);
+  source.playbackRate.setTargetValueAtTime(0, 2, 3);
+  var sp = ctx.createScriptProcessor();
+  source.connect(sp);
+  sp.connect(ctx.destination);
+  source.start(0);
+
+  sp.onaudioprocess = function(e) {
+    // Now set the buffer
+    source.buffer = buffer;
+
+    ok(true, "We did not crash.");
+    sp.onaudioprocess = null;
+    SpecialPowers.clearUserPref("media.webaudio.enabled");
+    SimpleTest.finish();
+  };
+});
+
+
+</script>
+</pre>
+</body>
+</html>