Bug 1440539 - Support time jitter in the JS Shell, and expose a function to enable it. r=luke
authorTom Ritter <tom@mozilla.com>
Fri, 02 Mar 2018 13:47:44 -0600
changeset 461472 a66754828b42ed065348a19b6e9336f95fa72b78
parent 461471 9804e9351510e50c48beffcc56b9b8eb170d98c0
child 461484 0add475b998c8e692db1ed12c698ba7c735c6515
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1440539
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 1440539 - Support time jitter in the JS Shell, and expose a function to enable it. r=luke This adds jittering to the already existing logic for time clamping. It also exposes a testing function allowing those interested to enable time clamping or time clamping and jittering. Neither (clamping nor jittering) is enabled by default. MozReview-Commit-ID: JcHCEwRQPch
js/src/builtin/TestingFunctions.cpp
js/src/jsdate.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -4935,16 +4935,43 @@ IsLegacyIterator(JSContext* cx, unsigned
     if (args.length() < 1)
         args.rval().setBoolean(false);
     else
         args.rval().setBoolean(IsPropertyIterator(args[0]));
     return true;
 }
 
 static bool
+SetTimeResolution(JSContext* cx, unsigned argc, Value* vp)
+{
+   CallArgs args = CallArgsFromVp(argc, vp);
+   RootedObject callee(cx, &args.callee());
+
+   if (!args.requireAtLeast(cx, "setTimeResolution", 2))
+        return false;
+
+   if (!args[0].isInt32()) {
+      ReportUsageErrorASCII(cx, callee, "First argument must be an Int32.");
+      return false;
+   }
+   int32_t resolution = args[0].toInt32();
+
+   if (!args[1].isBoolean()) {
+       ReportUsageErrorASCII(cx, callee, "Second argument must be a Boolean");
+       return false;
+   }
+   bool jitter = args[1].toBoolean();
+
+   JS::SetTimeResolutionUsec(resolution, jitter);
+
+   args.rval().setUndefined();
+   return true;
+}
+
+static bool
 EnableExpressionClosures(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JS::ContextOptionsRef(cx).setExpressionClosures(true);
     args.rval().setUndefined();
     return true;
 }
 
@@ -5700,16 +5727,21 @@ gc::ZealModeHelpText),
     JS_FN_HELP("isLegacyIterator", IsLegacyIterator, 1, 0,
 "isLegacyIterator(value)",
 "  Returns whether the value is considered is a legacy iterator.\n"),
 
     JS_FN_HELP("getTimeZone", GetTimeZone, 0, 0,
 "getTimeZone()",
 "  Get the current time zone.\n"),
 
+    JS_FN_HELP("setTimeResolution", SetTimeResolution, 2, 0,
+"setTimeResolution(resolution, jitter)",
+"  Enables time clamping and jittering. Specify a time resolution in\n"
+"  microseconds and whether or not to jitter\n"),
+
     JS_FN_HELP("enableExpressionClosures", EnableExpressionClosures, 0, 0,
 "enableExpressionClosures()",
 "  Enables the deprecated, non-standard expression closures.\n"),
 
     JS_FN_HELP("disableExpressionClosures", DisableExpressionClosures, 0, 0,
 "disableExpressionClosures()",
 "  Disables the deprecated, non-standard expression closures.\n"),
 
old mode 100644
new mode 100755
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -1305,18 +1305,47 @@ date_parse(JSContext* cx, unsigned argc,
 }
 
 static ClippedTime
 NowAsMillis()
 {
     double now = PRMJ_Now();
     if (sReduceMicrosecondTimePrecisionCallback)
         now = sReduceMicrosecondTimePrecisionCallback(now);
-    else if (sResolutionUsec)
-        now = floor(now / sResolutionUsec) * sResolutionUsec;
+    else if (sResolutionUsec) {
+        double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
+
+        if (sJitter) {
+            // Calculate a random midpoint for jittering. In the browser, we are adversarial:
+            // Web Content may try to calculate the midpoint themselves and use that to bypass
+            // it's security. In the JS Shell, we are not adversarial, we want to jitter the
+            // time to recreate the operating environment, but we do not concern ourselves
+            // with trying to prevent an attacker from calculating the midpoint themselves.
+            // So we use a very simple, very fast CRC with a hardcoded seed.
+
+            uint64_t midpoint = *((uint64_t*)&clamped);
+            midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
+            // MurmurHash3 internal component from
+            //   https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
+            midpoint ^= midpoint >> 33;
+            midpoint *= uint64_t{0xFF51AFD7ED558CCD};
+            midpoint ^= midpoint >> 33;
+            midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
+            midpoint ^= midpoint >> 33;
+            midpoint %= sResolutionUsec;
+
+            if (now > clamped + midpoint) { // We're jittering up to the next step
+                now = clamped + sResolutionUsec;
+            } else { // We're staying at the clamped value
+                now = clamped;
+            }
+        } else { //No jitter, only clamping
+            now = clamped;
+        }
+    }
 
     return TimeClip(now / PRMJ_USEC_PER_MSEC);
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);