Bug 1430841 Refactor ReduceTimePrecision and add (failing) gtests r=bkelly
☠☠ backed out by be1b4e4d00ba ☠ ☠
authorTom Ritter <tom@mozilla.com>
Thu, 25 Jan 2018 13:29:37 -0600
changeset 401255 b2c9a5c4e97148f214c71bfe81cf648734102f1d
parent 401254 716157692a117b762f3cb439e059abc8382bf2d2
child 401256 bb8dcc2219b332e122f43089f6890d876c76427d
push id58781
push usernerli@mozilla.com
push dateMon, 29 Jan 2018 16:55:51 +0000
treeherderautoland@5c060f21f0b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1430841
milestone60.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 1430841 Refactor ReduceTimePrecision and add (failing) gtests r=bkelly This commit adds a gtest calling ReduceTimePrecision that illustrates several failing test cases from float fuzziness, as well as generating a ton of test cases at random that also fail. MozReview-Commit-ID: Epia5gm5Ahb
toolkit/components/resistfingerprinting/moz.build
toolkit/components/resistfingerprinting/nsRFPService.cpp
toolkit/components/resistfingerprinting/nsRFPService.h
toolkit/components/resistfingerprinting/tests/moz.build
toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+TEST_DIRS += ['tests']
+
 UNIFIED_SOURCES += [
     'nsRFPService.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 EXPORTS += [
     'nsRFPService.h',
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -108,89 +108,86 @@ nsRFPService::IsResistFingerprintingEnab
 /* static */
 bool
 nsRFPService::IsTimerPrecisionReductionEnabled()
 {
   return (sPrivacyTimerPrecisionReduction || IsResistFingerprintingEnabled()) &&
          TimerResolution() != 0;
 }
 
+/*
+  @param aTime timestamp in native units
+  @param aResolutionUS the precision, in microseconds, to clamp it to
+  @param aResolutionScaleCorrection the amount aResolutionScaleCorrection must be divided by to match the units of aResolutionUS
+*/
 /* static */
 double
-nsRFPService::ReduceTimePrecisionAsMSecs(double aTime)
+nsRFPService::ReduceTimePrecisionImpl(double aTime, double aResolutionUS, double aResolutionScaleCorrection)
 {
   if (!IsTimerPrecisionReductionEnabled()) {
     return aTime;
   }
-  const double resolutionMSec = TimerResolution() / 1000.0;
-  double ret = floor(aTime / resolutionMSec) * resolutionMSec;
+  if (aResolutionScaleCorrection != 1 &&
+      aResolutionScaleCorrection != 1000 &&
+      aResolutionScaleCorrection != 1000000) {
+    MOZ_ASSERT(false, "Only scale corrections of 1, 1000, and 1000000 are supported.");
+    return aTime;
+  }
+
+  double ret;
+  const double reducedResolution = aResolutionUS / aResolutionScaleCorrection;
+  if (aResolutionScaleCorrection >= 1000000 && aResolutionUS < 1000000) {
+    // The resolution is so small we need to use the reciprocal to avoid floating point error.
+    const double resolutionReciprocal = 1000000.0 / reducedResolution;
+    ret = floor(aTime * resolutionReciprocal) / resolutionReciprocal;
 #if defined(DEBUG)
   MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
-    ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
-      DBL_DIG-1, aTime, DBL_DIG-1, resolutionMSec, DBL_DIG-1, floor(aTime / resolutionMSec), DBL_DIG-1, ret));
+    ("Given: %.*f, Reciprocal Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
+      DBL_DIG-1, aTime, DBL_DIG-1, resolutionReciprocal, DBL_DIG-1, floor(aTime * resolutionReciprocal), DBL_DIG-1, ret));
 #endif
+  } else {
+    ret = floor(aTime / reducedResolution) * reducedResolution;
+#if defined(DEBUG)
+    MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
+      ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
+        DBL_DIG-1, aTime, DBL_DIG-1, reducedResolution, DBL_DIG-1, floor(aTime / reducedResolution), DBL_DIG-1, ret));
+#endif
+  }
   return ret;
 }
 
 /* static */
 double
 nsRFPService::ReduceTimePrecisionAsUSecs(double aTime)
 {
-  if (!IsTimerPrecisionReductionEnabled()) {
-    return aTime;
-  }
-  double resolutionUSec = TimerResolution();
-  double ret = floor(aTime / resolutionUSec) * resolutionUSec;
-#if defined(DEBUG)
-  double tmp_sResolutionUSec = resolutionUSec;
-  MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
-    ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
-      DBL_DIG-1, aTime, DBL_DIG-1, tmp_sResolutionUSec, DBL_DIG-1, floor(aTime / tmp_sResolutionUSec), DBL_DIG-1, ret));
-#endif
-  return ret;
+  return nsRFPService::ReduceTimePrecisionImpl(aTime, TimerResolution(), 1);
+}
+
+/* static */
+double
+nsRFPService::ReduceTimePrecisionAsMSecs(double aTime)
+{
+  return nsRFPService::ReduceTimePrecisionImpl(aTime, TimerResolution(), 1000);
+}
+
+/* static */
+double
+nsRFPService::ReduceTimePrecisionAsSecs(double aTime)
+{
+  return nsRFPService::ReduceTimePrecisionImpl(aTime, TimerResolution(), 1000000);
 }
 
 /* static */
 uint32_t
 nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality)
 {
   return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
 }
 
 /* static */
-double
-nsRFPService::ReduceTimePrecisionAsSecs(double aTime)
-{
-  if (!IsTimerPrecisionReductionEnabled()) {
-    return aTime;
-  }
-  double resolutionUSec = TimerResolution();
-  if (TimerResolution() < 1000000) {
-    // The resolution is smaller than one sec.  Use the reciprocal to avoid
-    // floating point error.
-    const double resolutionSecReciprocal = 1000000.0 / resolutionUSec;
-    double ret = floor(aTime * resolutionSecReciprocal) / resolutionSecReciprocal;
-#if defined(DEBUG)
-  MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
-    ("Given: %.*f, Reciprocal Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
-      DBL_DIG-1, aTime, DBL_DIG-1, resolutionSecReciprocal, DBL_DIG-1, floor(aTime * resolutionSecReciprocal), DBL_DIG-1, ret));
-#endif
-    return ret;
-  }
-  const double resolutionSec = resolutionUSec / 1000000.0;
-  double ret = floor(aTime / resolutionSec) * resolutionSec;
-#if defined(DEBUG)
-  MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
-    ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
-      DBL_DIG-1, aTime, DBL_DIG-1, resolutionSec, DBL_DIG-1, floor(aTime / resolutionSec), DBL_DIG-1, ret));
-#endif
-  return ret;
-}
-
-/* static */
 uint32_t
 nsRFPService::GetSpoofedTotalFrames(double aTime)
 {
   double time = ReduceTimePrecisionAsSecs(aTime);
 
   return NSToIntFloor(time * sVideoFramesPerSec);
 }
 
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -154,19 +154,22 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static nsRFPService* GetOrCreate();
   static bool IsResistFingerprintingEnabled();
   static bool IsTimerPrecisionReductionEnabled();
 
   // The following Reduce methods can be called off main thread.
+  static double ReduceTimePrecisionAsUSecs(double aTime);
   static double ReduceTimePrecisionAsMSecs(double aTime);
-  static double ReduceTimePrecisionAsUSecs(double aTime);
   static double ReduceTimePrecisionAsSecs(double aTime);
+  // Public only for testing purposes
+  static double ReduceTimePrecisionImpl(double aTime, double aResolutionUSec, double aTimeScaleCorrection);
+
 
   // This method calculates the video resolution (i.e. height x width) based
   // on the video quality (480p, 720p, etc).
   static uint32_t CalculateTargetVideoResolution(uint32_t aVideoQuality);
 
   // Methods for getting spoofed media statistics and the return value will
   // depend on the video resolution.
   static uint32_t GetSpoofedTotalFrames(double aTime);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/tests/moz.build
@@ -0,0 +1,5 @@
+UNIFIED_SOURCES += [
+    'test_reduceprecision.cpp',
+]
+
+FINAL_LIBRARY = "xul-gtest"
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * 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/. */
+
+#include <math.h>
+
+#include "gtest/gtest.h"
+#include "nsRFPService.h"
+
+using namespace mozilla;
+
+/*
+   Hello! Are you looking at this file because you got an error you don't understand?
+   Perhaps something that looks like the following?
+
+    toolkit/components/resistfingerprinting/tests/test_reduceprecision.cpp:15: Failure
+      Expected: reduced1
+        Which is: 2064.83
+      To be equal to: reduced2
+        Which is: 2064.83
+
+   "Gosh," you might say, "They sure look equal to me. What the heck is going on here?"
+
+   The answer lies beyond what you can see, in that which you cannot see. One must
+   journey into the depths, the hidden, that which the world fights its hardest to
+   conceal from us.
+
+   Specially: you need to look at more decimal places. Run the test with:
+   	  MOZ_LOG="nsResistFingerprinting:5"
+
+   And look for two successive lines similar to the below (the format will certainly
+   be different by the time you read this comment):
+      V/nsResistFingerprinting Given: 2064.83384599999999, Reciprocal Rounding with 50000.00000000000000, Intermediate: 103241692.00000000000000, Got: 2064.83383999999978
+      V/nsResistFingerprinting Given: 2064.83383999999978, Reciprocal Rounding with 50000.00000000000000, Intermediate: 103241691.00000000000000, Got: 2064.83381999999983
+
+   Look at the last two values:
+      Got: 2064.83383999999978
+      Got: 2064.83381999999983
+
+   They're supposed to be equal. They're not. But they both round to 2064.83.
+*/
+
+void process(double clock, double precision, double precisionUnits) {
+    double reduced1 = nsRFPService::ReduceTimePrecisionImpl(clock, precision, precisionUnits);
+	double reduced2 = nsRFPService::ReduceTimePrecisionImpl(reduced1, precision, precisionUnits);
+	ASSERT_EQ(reduced1, reduced2);
+}
+
+TEST(ResistFingerprinting, ReducePrecision_Assumptions) {
+	ASSERT_EQ(FLT_RADIX, 2);
+	ASSERT_EQ(DBL_MANT_DIG, 53);
+}
+
+TEST(ResistFingerprinting, ReducePrecision_Reciprocal) {
+	// This one has a rounding error in the Reciprocal case:
+	process(2064.8338460, 20, 1000000);
+	// These are just big values
+	process(1516305819, 20, 1000000);
+	process(69053.12, 20, 1000000);
+}
+
+TEST(ResistFingerprinting, ReducePrecision_KnownGood) {
+	process(2064.8338460, 20, 1000);
+	process(69027.62, 20, 1000);
+	process(69053.12, 20, 1000);
+}
+
+TEST(ResistFingerprinting, ReducePrecision_KnownBad) {
+	process(1054.842405, 20, 1000);
+	process(273.53038600000002, 20, 1000);
+	process(628.66686500000003, 20, 1000);
+	process(521.28919100000007, 20, 1000);
+}
+
+TEST(ResistFingerprinting, ReducePrecision_Edge) {
+	process(2611.14, 20, 1000);
+	process(2611.16, 20, 1000);
+	process(2612.16, 20, 1000);
+	process(2601.64, 20, 1000);
+	process(2595.16, 20, 1000);
+	process(2578.66, 20, 1000);
+}
+
+TEST(ResistFingerprinting, ReducePrecision_Expectations) {
+	double result;
+	result = nsRFPService::ReduceTimePrecisionImpl(2611.14, 20, 1000);
+	ASSERT_EQ(result, 2611.14);
+	result = nsRFPService::ReduceTimePrecisionImpl(2611.145, 20, 1000);
+	ASSERT_EQ(result, 2611.14);
+	result = nsRFPService::ReduceTimePrecisionImpl(2611.141, 20, 1000);
+	ASSERT_EQ(result, 2611.14);
+	result = nsRFPService::ReduceTimePrecisionImpl(2611.15999, 20, 1000);
+	ASSERT_EQ(result, 2611.14);
+	result = nsRFPService::ReduceTimePrecisionImpl(2611.15, 20, 1000);
+	ASSERT_EQ(result, 2611.14);
+	result = nsRFPService::ReduceTimePrecisionImpl(2611.13, 20, 1000);
+	ASSERT_EQ(result, 2611.12);
+}
+
+// Use an ugly but simple hack to turn an integer-based rand()
+// function to a double-based one
+#define RAND_DOUBLE (rand() * (rand() / (double)rand()))
+
+TEST(ResistFingerprinting, ReducePrecision_Aggressive) {
+	for (int i=0; i<10000; i++) {
+		// Test three different time magnitudes, with decimals.
+		// Note that we need separate variables for the different units, as scaling
+		// them after calculating them will erase effects of approximation
+		// A magnitude in the seconds since epoch range
+		double time1_s = fmod(RAND_DOUBLE, 1516305819);
+		double time1_ms = fmod(RAND_DOUBLE, 1516305819000);
+		double time1_us = fmod(RAND_DOUBLE, 1516305819000000);
+		// A magnitude in the 'couple of minutes worth of milliseconds' range
+		double time2_s = fmod(RAND_DOUBLE, (60 * 60 * 5));
+		double time2_ms = fmod(RAND_DOUBLE, (1000 * 60 * 60 * 5));
+		double time2_us = fmod(RAND_DOUBLE, (1000000 * 60 * 60 * 5));
+		// A magnitude in the small range
+		double time3_s = fmod(RAND_DOUBLE, 10);
+		double time3_ms = fmod(RAND_DOUBLE, 10000);
+		double time3_us = fmod(RAND_DOUBLE, 10000000);
+
+		// Test two precision magnitudes, no decimals
+		// A magnitude in the high milliseconds
+		double precision1 = rand() % 250000;
+		// a magnitude in the low microseconds
+		double precision2 = rand() % 200;
+
+		//printf("%.*f, %.*f, %.*f\n", DBL_DIG-1, time1, DBL_DIG-1, time2, DBL_DIG-1, time3);
+		process(time1_s, precision1, 1000000);
+		process(time1_s, precision2, 1000000);
+		process(time2_s, precision1, 1000000);
+		process(time2_s, precision2, 1000000);
+		process(time3_s, precision1, 1000000);
+		process(time3_s, precision2, 1000000);
+
+		process(time1_ms, precision1, 1000);
+		process(time1_ms, precision2, 1000);
+		process(time2_ms, precision1, 1000);
+		process(time2_ms, precision2, 1000);
+		process(time3_ms, precision1, 1000);
+		process(time3_ms, precision2, 1000);
+
+		process(time1_us, precision1, 1);
+		process(time1_us, precision2, 1);
+		process(time2_us, precision1, 1);
+		process(time2_us, precision2, 1);
+		process(time3_us, precision1, 1);
+		process(time3_us, precision2, 1);
+	}
+}