Bug 1158399 - Ensure/assert that DateObject::setUTCTime never stores a non-TimeClip'd value in the reserved slot. r=evilpie
authorJeff Walden <jwalden@mit.edu>
Thu, 30 Apr 2015 20:03:30 -0700
changeset 243430 0055add82982188ea1744cafb9122ae75e1cca5e
parent 243429 d7a5e972e003b26a47476b46ca6366e05f830c39
child 243431 0c80b9af0593a336d074a45b0f036f24f281e865
push id28738
push usercbook@mozilla.com
push dateTue, 12 May 2015 14:11:31 +0000
treeherderautoland@bedce1b405a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie
bugs1158399
milestone40.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 1158399 - Ensure/assert that DateObject::setUTCTime never stores a non-TimeClip'd value in the reserved slot. r=evilpie
js/public/Date.h
js/src/jsapi.cpp
js/src/jsdate.cpp
js/src/jsdate.h
js/src/vm/DateObject.h
js/src/vm/DateTime.h
js/src/vm/StructuredClone.cpp
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -1,20 +1,56 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 js_Date_h
 #define js_Date_h
 
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MathAlgorithms.h"
+
 #include "jstypes.h"
 
+#include "js/Conversions.h"
+#include "js/Value.h"
+
 namespace JS {
 
+class ClippedTime
+{
+    double t;
+
+    /* ES5 15.9.1.14. */
+    double timeClip(double time) {
+        /* Steps 1-2. */
+        const double MaxTimeMagnitude = 8.64e15;
+        if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
+            return JS::GenericNaN();
+
+        /* Step 3. */
+        return JS::ToInteger(time) + (+0.0);
+    }
+
+  public:
+    ClippedTime() : t(JS::GenericNaN()) {}
+    explicit ClippedTime(double time) : t(timeClip(time)) {}
+
+    static ClippedTime NaN() { return ClippedTime(); }
+
+    double value() const { return t; }
+};
+
+inline ClippedTime
+TimeClip(double d)
+{
+    return ClippedTime(d);
+}
+
 // Year is a year, month is 0-11, day is 1-based.  The return value is
 // a number of milliseconds since the epoch.  Can return NaN.
 JS_PUBLIC_API(double)
 MakeDate(double year, unsigned month, unsigned day);
 
 // Takes an integer number of milliseconds since the epoch and returns the
 // year.  Can return NaN, and will do so if NaN is passed in.
 JS_PUBLIC_API(double)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -56,16 +56,17 @@
 #endif
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"  // for JS_BufferIsCompileableUnit
 #include "frontend/Parser.h" // for JS_BufferIsCompileableUnit
 #include "gc/Marking.h"
 #include "jit/JitCommon.h"
 #include "js/CharacterEncoding.h"
 #include "js/Conversions.h"
+#include "js/Date.h"
 #include "js/Proxy.h"
 #include "js/SliceBudget.h"
 #include "js/StructuredClone.h"
 #if ENABLE_INTL_API
 #include "unicode/uclean.h"
 #include "unicode/utypes.h"
 #endif // ENABLE_INTL_API
 #include "vm/DateObject.h"
@@ -5232,17 +5233,17 @@ JS_NewDateObject(JSContext* cx, int year
     return NewDateObject(cx, year, mon, mday, hour, min, sec);
 }
 
 JS_PUBLIC_API(JSObject*)
 JS_NewDateObjectMsec(JSContext* cx, double msec)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    return NewDateObjectMsec(cx, msec);
+    return NewDateObjectMsec(cx, JS::TimeClip(msec));
 }
 
 JS_PUBLIC_API(bool)
 JS_ObjectIsDate(JSContext* cx, HandleObject obj)
 {
     assertSameCompartment(cx, obj);
     return ObjectClassIs(obj, ESClass_Date, cx);
 }
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -45,19 +45,22 @@
 
 #include "jsobjinlines.h"
 
 using namespace js;
 
 using mozilla::ArrayLength;
 using mozilla::IsFinite;
 using mozilla::IsNaN;
+using mozilla::NumbersAreIdentical;
 
 using JS::AutoCheckCannotGC;
+using JS::ClippedTime;
 using JS::GenericNaN;
+using JS::TimeClip;
 using JS::ToInteger;
 
 /*
  * The JS 'Date' object is patterned after the Java 'Date' object.
  * Here is a script:
  *
  *    today = new Date();
  *
@@ -345,17 +348,17 @@ MakeDate(double day, double time)
 
     /* Step 2. */
     return day * msPerDay + time;
 }
 
 JS_PUBLIC_API(double)
 JS::MakeDate(double year, unsigned month, unsigned day)
 {
-    return TimeClip(::MakeDate(MakeDay(year, month, day), 0));
+    return TimeClip(::MakeDate(MakeDay(year, month, day), 0)).value();
 }
 
 JS_PUBLIC_API(double)
 JS::YearFromTime(double time)
 {
     return ::YearFromTime(time);
 }
 
@@ -561,24 +564,16 @@ RegionMatches(const char* s1, int s1off,
         s1off++;
         s2off++;
         count--;
     }
 
     return count == 0;
 }
 
-/* find UTC time from given date... no 1900 correction! */
-static double
-date_msecFromDate(double year, double mon, double mday, double hour,
-                  double min, double sec, double msec)
-{
-    return MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, msec));
-}
-
 /* ES6 20.3.3.4. */
 static bool
 date_UTC(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1-2.
     double y;
@@ -639,17 +634,18 @@ date_UTC(JSContext* cx, unsigned argc, V
     double yr = y;
     if (!IsNaN(y)) {
         double yint = ToInteger(y);
         if (0 <= yint && yint <= 99)
             yr = 1900 + yint;
     }
 
     // Step 16.
-    args.rval().setDouble(TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli))));
+    ClippedTime time = TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
+    args.rval().setDouble(time.value());
     return true;
 }
 
 /*
  * Read and convert decimal digits from s[*i] into *result
  * while *i < limit.
  *
  * Succeed if any digits are converted. Advance *i only
@@ -772,17 +768,17 @@ DaysInMonth(int year, int month)
  *   hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
  *   mm   = two digits of minute (00 through 59)
  *   ss   = two digits of second (00 through 59)
  *   s    = one or more digits representing a decimal fraction of a second
  *   TZD  = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
  */
 template <typename CharT>
 static bool
-ParseISODate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo)
+ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo)
 {
     size_t i = 0;
     int tzMul = 1;
     int dateMul = 1;
     size_t year = 1970;
     size_t month = 1;
     size_t day = 1;
     size_t hour = 0;
@@ -868,39 +864,36 @@ ParseISODate(const CharT* s, size_t leng
         return false;
     }
 
     if (i != length)
         return false;
 
     month -= 1; /* convert month to 0-based */
 
-    double msec = date_msecFromDate(dateMul * double(year), month, day,
-                                    hour, min, sec, frac * 1000.0);
+    double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
+                           MakeTime(hour, min, sec, frac * 1000.0));
 
     if (isLocalTime)
         msec = UTC(msec, dtInfo);
     else
         msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
 
-    if (msec < -8.64e15 || msec > 8.64e15)
-        return false;
-
-    *result = msec;
-    return true;
+    *result = TimeClip(msec);
+    return NumbersAreIdentical(msec, result->value());
 
 #undef PEEK
 #undef NEED
 #undef DONE_UNLESS
 #undef NEED_NDIGITS
 }
 
 template <typename CharT>
 static bool
-ParseDate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo)
+ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo)
 {
     if (ParseISODate(s, length, result, dtInfo))
         return true;
 
     if (length == 0)
         return false;
 
     int year = -1;
@@ -1152,29 +1145,29 @@ ParseDate(const CharT* s, size_t length,
     mon -= 1; /* convert month to 0-based */
     if (sec < 0)
         sec = 0;
     if (min < 0)
         min = 0;
     if (hour < 0)
         hour = 0;
 
-    double msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
+    double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
 
     if (tzOffset == -1) /* no time zone specified, have to use local */
         msec = UTC(msec, dtInfo);
     else
         msec += tzOffset * msPerMinute;
 
-    *result = msec;
+    *result = TimeClip(msec);
     return true;
 }
 
 static bool
-ParseDate(JSLinearString* s, double* result, DateTimeInfo* dtInfo)
+ParseDate(JSLinearString* s, ClippedTime* result, DateTimeInfo* dtInfo)
 {
     AutoCheckCannotGC nogc;
     return s->hasLatin1Chars()
            ? ParseDate(s->latin1Chars(nogc), s->length(), result, dtInfo)
            : ParseDate(s->twoByteChars(nogc), s->length(), result, dtInfo);
 }
 
 static bool
@@ -1189,55 +1182,54 @@ date_parse(JSContext* cx, unsigned argc,
     JSString* str = ToString<CanGC>(cx, args[0]);
     if (!str)
         return false;
 
     JSLinearString* linearStr = str->ensureLinear(cx);
     if (!linearStr)
         return false;
 
-    double result;
+    ClippedTime result;
     if (!ParseDate(linearStr, &result, &cx->runtime()->dateTimeInfo)) {
         args.rval().setNaN();
         return true;
     }
 
-    result = TimeClip(result);
-    args.rval().setNumber(result);
+    args.rval().setDouble(result.value());
     return true;
 }
 
-static inline double
+static ClippedTime
 NowAsMillis()
 {
-    return (double) (PRMJ_Now() / PRMJ_USEC_PER_MSEC);
+    return ClippedTime(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setDouble(NowAsMillis());
+    args.rval().setDouble(NowAsMillis().value());
     return true;
 }
 
 void
-DateObject::setUTCTime(double t)
+DateObject::setUTCTime(ClippedTime t)
 {
     for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
         setReservedSlot(ind, UndefinedValue());
 
-    setFixedSlot(UTC_TIME_SLOT, DoubleValue(t));
+    setFixedSlot(UTC_TIME_SLOT, DoubleValue(t.value()));
 }
 
 void
-DateObject::setUTCTime(double t, MutableHandleValue vp)
+DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp)
 {
     setUTCTime(t);
-    vp.setDouble(t);
+    vp.setDouble(t.value());
 }
 
 void
 DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo)
 {
     /* Check if the cache is already populated. */
     if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
         getReservedSlot(TZA_SLOT).toDouble() == dtInfo->localTZA())
@@ -1690,17 +1682,17 @@ date_getTimezoneOffset(JSContext* cx, un
     return CallNonGenericMethod<IsDate, DateObject::getTimezoneOffset_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 date_setTime_impl(JSContext* cx, CallArgs args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
     if (args.length() == 0) {
-        dateObj->setUTCTime(GenericNaN(), args.rval());
+        dateObj->setUTCTime(ClippedTime::NaN(), args.rval());
         return true;
     }
 
     double result;
     if (!ToNumber(cx, args[0], &result))
         return false;
 
     dateObj->setUTCTime(TimeClip(result), args.rval());
@@ -1755,17 +1747,17 @@ date_setMilliseconds_impl(JSContext* cx,
 
     /* Step 2. */
     double milli;
     if (!ToNumber(cx, args.get(0), &milli))
         return false;
     double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
 
     /* Step 3. */
-    double u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo));
 
     /* Steps 4-5. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 static bool
 date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp)
@@ -1785,17 +1777,17 @@ date_setUTCMilliseconds_impl(JSContext* 
 
     /* Step 2. */
     double milli;
     if (!ToNumber(cx, args.get(0), &milli))
         return false;
     double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
 
     /* Step 3. */
-    double v = TimeClip(MakeDate(Day(t), time));
+    ClippedTime v = TimeClip(MakeDate(Day(t), time));
 
     /* Steps 4-5. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 static bool
 date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp)
@@ -1822,17 +1814,17 @@ date_setSeconds_impl(JSContext* cx, Call
     double milli;
     if (!GetMsecsOrDefault(cx, args, 1, t, &milli))
         return false;
 
     /* Step 4. */
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
 
     /* Step 5. */
-    double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
 
     /* Steps 6-7. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.31. */
 static bool
@@ -1859,17 +1851,17 @@ date_setUTCSeconds_impl(JSContext* cx, C
     double milli;
     if (!GetMsecsOrDefault(cx, args, 1, t, &milli))
         return false;
 
     /* Step 4. */
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
 
     /* Step 5. */
-    double v = TimeClip(date);
+    ClippedTime v = TimeClip(date);
 
     /* Steps 6-7. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.32. */
 static bool
@@ -1901,17 +1893,17 @@ date_setMinutes_impl(JSContext* cx, Call
     double milli;
     if (!GetMsecsOrDefault(cx, args, 2, t, &milli))
         return false;
 
     /* Step 5. */
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
 
     /* Step 6. */
-    double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.33. */
 static bool
@@ -1943,17 +1935,17 @@ date_setUTCMinutes_impl(JSContext* cx, C
     double milli;
     if (!GetMsecsOrDefault(cx, args, 2, t, &milli))
         return false;
 
     /* Step 5. */
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
 
     /* Step 6. */
-    double v = TimeClip(date);
+    ClippedTime v = TimeClip(date);
 
     /* Steps 7-8. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.34. */
 static bool
@@ -1990,17 +1982,17 @@ date_setHours_impl(JSContext* cx, CallAr
     double milli;
     if (!GetMsecsOrDefault(cx, args, 3, t, &milli))
         return false;
 
     /* Step 6. */
     double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
 
     /* Step 6. */
-    double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.35. */
 static bool
@@ -2037,17 +2029,17 @@ date_setUTCHours_impl(JSContext* cx, Cal
     double milli;
     if (!GetMsecsOrDefault(cx, args, 3, t, &milli))
         return false;
 
     /* Step 6. */
     double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
 
     /* Step 7. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 8-9. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.36. */
 static bool
@@ -2069,17 +2061,17 @@ date_setDate_impl(JSContext* cx, CallArg
     double date;
     if (!ToNumber(cx, args.get(0), &date))
         return false;
 
     /* Step 3. */
     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
 
     /* Step 4. */
-    double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
 
     /* Steps 5-6. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.37. */
 static bool
@@ -2101,17 +2093,17 @@ date_setUTCDate_impl(JSContext* cx, Call
     double date;
     if (!ToNumber(cx, args.get(0), &date))
         return false;
 
     /* Step 3. */
     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
 
     /* Step 4. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 5-6. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 static bool
 date_setUTCDate(JSContext* cx, unsigned argc, Value* vp)
@@ -2158,17 +2150,17 @@ date_setMonth_impl(JSContext* cx, CallAr
     double date;
     if (!GetDateOrDefault(cx, args, 1, t, &date))
         return false;
 
     /* Step 4. */
     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
 
     /* Step 5. */
-    double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
 
     /* Steps 6-7. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 static bool
 date_setMonth(JSContext* cx, unsigned argc, Value* vp)
@@ -2195,17 +2187,17 @@ date_setUTCMonth_impl(JSContext* cx, Cal
     double date;
     if (!GetDateOrDefault(cx, args, 1, t, &date))
         return false;
 
     /* Step 4. */
     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
 
     /* Step 5. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 6-7. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 static bool
 date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp)
@@ -2253,17 +2245,17 @@ date_setFullYear_impl(JSContext* cx, Cal
     double date;
     if (!GetDateOrDefault(cx, args, 2, t, &date))
         return false;
 
     /* Step 5. */
     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
 
     /* Step 6. */
-    double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 static bool
 date_setFullYear(JSContext* cx, unsigned argc, Value* vp)
@@ -2295,17 +2287,17 @@ date_setUTCFullYear_impl(JSContext* cx, 
     double date;
     if (!GetDateOrDefault(cx, args, 2, t, &date))
         return false;
 
     /* Step 5. */
     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
 
     /* Step 6. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 7-8. */
     dateObj->setUTCTime(v, args.rval());
     return true;
 }
 
 static bool
 date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp)
@@ -2325,17 +2317,17 @@ date_setYear_impl(JSContext* cx, CallArg
 
     /* Step 2. */
     double y;
     if (!ToNumber(cx, args.get(0), &y))
         return false;
 
     /* Step 3. */
     if (IsNaN(y)) {
-        dateObj->setUTCTime(GenericNaN(), args.rval());
+        dateObj->setUTCTime(ClippedTime::NaN(), args.rval());
         return true;
     }
 
     /* Step 4. */
     double yint = ToInteger(y);
     if (0 <= yint && yint <= 99)
         yint += 1900;
 
@@ -2369,45 +2361,45 @@ static const char * const months[] =
 };
 
 
 // Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
 // requires a PRMJTime... which only has 16-bit years.  Sub-ECMA.
 static void
 print_gmt_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(TimeClip(utctime) == utctime);
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
     JS_snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
                 days[int(WeekDay(utctime))],
                 int(DateFromTime(utctime)),
                 months[int(MonthFromTime(utctime))],
                 int(YearFromTime(utctime)),
                 int(HourFromTime(utctime)),
                 int(MinFromTime(utctime)),
                 int(SecFromTime(utctime)));
 }
 
 static void
 print_iso_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(TimeClip(utctime) == utctime);
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
     JS_snprintf(buf, size, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
                 int(YearFromTime(utctime)),
                 int(MonthFromTime(utctime)) + 1,
                 int(DateFromTime(utctime)),
                 int(HourFromTime(utctime)),
                 int(MinFromTime(utctime)),
                 int(SecFromTime(utctime)),
                 int(msFromTime(utctime)));
 }
 
 static void
 print_iso_extended_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(TimeClip(utctime) == utctime);
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
     JS_snprintf(buf, size, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
                 int(YearFromTime(utctime)),
                 int(MonthFromTime(utctime)) + 1,
                 int(DateFromTime(utctime)),
                 int(HourFromTime(utctime)),
                 int(MinFromTime(utctime)),
                 int(SecFromTime(utctime)),
                 int(msFromTime(utctime)));
@@ -2553,17 +2545,17 @@ date_format(JSContext* cx, double date, 
     char tzbuf[100];
     bool usetz;
     size_t i, tzlen;
     PRMJTime split;
 
     if (!IsFinite(date)) {
         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
     } else {
-        MOZ_ASSERT(TimeClip(date) == date);
+        MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).value(), date));
 
         double local = LocalTime(date, &cx->runtime()->dateTimeInfo);
 
         /* offset from GMT in minutes.  The offset includes daylight savings,
            if it applies. */
         int minutes = (int) floor(AdjustTime(date, &cx->runtime()->dateTimeInfo) / msPerMinute);
 
         /* map 510 minutes to 0830 hours */
@@ -2968,72 +2960,71 @@ static const JSFunctionSpec date_methods
     JS_FN(js_toSource_str,       date_toSource,           0,0),
 #endif
     JS_FN(js_toString_str,       date_toString,           0,0),
     JS_FN(js_valueOf_str,        date_valueOf,            0,0),
     JS_FS_END
 };
 
 static bool
-NewDateObject(JSContext* cx, const CallArgs& args, double d)
+NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
-    JSObject* obj = NewDateObjectMsec(cx, d);
+    JSObject* obj = NewDateObjectMsec(cx, t);
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
-ToDateString(JSContext* cx, const CallArgs& args, double d)
+ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
-    return date_format(cx, d, FORMATSPEC_FULL, args.rval());
+    return date_format(cx, t.value(), FORMATSPEC_FULL, args.rval());
 }
 
 static bool
 DateNoArguments(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() == 0);
 
-    double now = NowAsMillis();
+    ClippedTime now = NowAsMillis();
 
     if (args.isConstructing())
         return NewDateObject(cx, args, now);
 
     return ToDateString(cx, args, now);
 }
 
 static bool
 DateOneArgument(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() == 1);
 
     if (args.isConstructing()) {
-        double d;
+        ClippedTime t;
 
         if (!ToPrimitive(cx, args[0]))
             return false;
 
         if (args[0].isString()) {
             JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
             if (!linearStr)
                 return false;
 
-            if (!ParseDate(linearStr, &d, &cx->runtime()->dateTimeInfo))
-                d = GenericNaN();
-            else
-                d = TimeClip(d);
+            if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo))
+                t = ClippedTime::NaN();
         } else {
+            double d;
             if (!ToNumber(cx, args[0], &d))
                 return false;
-            d = TimeClip(d);
+            t = TimeClip(d);
         }
 
-        return NewDateObject(cx, args, d);
+        return NewDateObject(cx, args, t);
     }
 
     return ToDateString(cx, args, NowAsMillis());
 }
 
 static bool
 DateMultipleArguments(JSContext* cx, const CallArgs& args)
 {
@@ -3126,17 +3117,17 @@ js::DateConstructor(JSContext* cx, unsig
         return DateOneArgument(cx, args);
 
     return DateMultipleArguments(cx, args);
 }
 
 static bool
 FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto)
 {
-    proto->as<DateObject>().setUTCTime(GenericNaN());
+    proto->as<DateObject>().setUTCTime(ClippedTime::NaN());
 
     /*
      * Date.prototype.toGMTString has the same initial value as
      * Date.prototype.toUTCString.
      */
     RootedValue toUTCStringFun(cx);
     RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
     RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
@@ -3168,33 +3159,33 @@ const Class DateObject::class_ = {
         date_static_methods,
         nullptr,
         date_methods,
         nullptr,
         FinishDateClassInit
     }
 };
 
-JS_FRIEND_API(JSObject*)
-js::NewDateObjectMsec(JSContext* cx, double msec_time)
+JSObject*
+js::NewDateObjectMsec(JSContext* cx, ClippedTime t)
 {
     JSObject* obj = NewBuiltinClassInstance(cx, &DateObject::class_);
     if (!obj)
         return nullptr;
-    obj->as<DateObject>().setUTCTime(msec_time);
+    obj->as<DateObject>().setUTCTime(t);
     return obj;
 }
 
 JS_FRIEND_API(JSObject*)
 js::NewDateObject(JSContext* cx, int year, int mon, int mday,
                   int hour, int min, int sec)
 {
     MOZ_ASSERT(mon < 12);
-    double msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
-    return NewDateObjectMsec(cx, UTC(msec_time, &cx->runtime()->dateTimeInfo));
+    double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
+    return NewDateObjectMsec(cx, TimeClip(UTC(msec_time, &cx->runtime()->dateTimeInfo)));
 }
 
 JS_FRIEND_API(bool)
 js::DateIsValid(JSContext* cx, JSObject* objArg)
 {
     RootedObject obj(cx, objArg);
     if (!ObjectClassIs(obj, ESClass_Date, cx))
         return false;
--- a/js/src/jsdate.h
+++ b/js/src/jsdate.h
@@ -8,31 +8,34 @@
  * JS Date class interface.
  */
 
 #ifndef jsdate_h
 #define jsdate_h
 
 #include "jstypes.h"
 
+#include "js/Date.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 
+#include "vm/DateTime.h"
+
 namespace js {
 
 /*
  * These functions provide a C interface to the date/time object
  */
 
 /*
  * Construct a new Date Object from a time value given in milliseconds UTC
  * since the epoch.
  */
-extern JS_FRIEND_API(JSObject*)
-NewDateObjectMsec(JSContext* cx, double msec_time);
+extern JSObject*
+NewDateObjectMsec(JSContext* cx, JS::ClippedTime t);
 
 /*
  * Construct a new Date Object from an exploded local time value.
  *
  * Assert that mon < 12 to help catch off-by-one user errors, which are common
  * due to the 0-based month numbering copied into JS from Java (java.util.Date
  * in 1995).
  */
--- a/js/src/vm/DateObject.h
+++ b/js/src/vm/DateObject.h
@@ -4,16 +4,17 @@
  * 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 vm_DateObject_h_
 #define vm_DateObject_h_
 
 #include "jsobj.h"
 
+#include "js/Date.h"
 #include "js/Value.h"
 
 namespace js {
 
 class DateTimeInfo;
 
 class DateObject : public NativeObject
 {
@@ -41,18 +42,18 @@ class DateObject : public NativeObject
   public:
     static const Class class_;
 
     inline const js::Value& UTCTime() const {
         return getFixedSlot(UTC_TIME_SLOT);
     }
 
     // Set UTC time to a given time and invalidate cached local time.
-    void setUTCTime(double t);
-    void setUTCTime(double t, MutableHandleValue vp);
+    void setUTCTime(JS::ClippedTime t);
+    void setUTCTime(JS::ClippedTime t, MutableHandleValue vp);
 
     inline double cachedLocalTime(DateTimeInfo* dtInfo);
 
     // Cache the local time, year, month, and so forth of the object.
     // If UTC time is not finite (e.g., NaN), the local time
     // slots will be set to the UTC time without conversion.
     void fillLocalTimeSlots(DateTimeInfo* dtInfo);
 
--- a/js/src/vm/DateTime.h
+++ b/js/src/vm/DateTime.h
@@ -34,29 +34,16 @@ const double msPerDay = msPerHour * Hour
  * so that they can be used in constexpr scenarios; if you need constants that
  * trigger floating point semantics, you'll have to manually cast to get it.
  */
 const unsigned SecondsPerHour = 60 * 60;
 const unsigned SecondsPerDay = SecondsPerHour * 24;
 
 const double StartOfTime = -8.64e15;
 const double EndOfTime = 8.64e15;
-const double MaxTimeMagnitude = 8.64e15;
-
-/* ES5 15.9.1.14. */
-inline double
-TimeClip(double time)
-{
-    /* Steps 1-2. */
-    if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
-        return JS::GenericNaN();
-
-    /* Step 3. */
-    return JS::ToInteger(time + (+0.0));
-}
 
 /*
  * Stores date/time information, particularly concerning the current local
  * time zone, and implements a small cache for daylight saving time offset
  * computation.
  *
  * The basic idea is premised upon this fact: the DST offset never changes more
  * than once in any thirty-day period.  If we know the offset at t_0 is o_0,
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -35,29 +35,31 @@
 #include <algorithm>
 
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsdate.h"
 #include "jswrapper.h"
 
 #include "builtin/MapObject.h"
+#include "js/Date.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 
 #include "jscntxtinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 
 using mozilla::BitwiseCast;
 using mozilla::IsNaN;
 using mozilla::LittleEndian;
 using mozilla::NativeEndian;
+using mozilla::NumbersAreIdentical;
 using JS::CanonicalizeNaN;
 
 // When you make updates here, make sure you consider whether you need to bump the
 // value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h.  You will
 // likely need to increment the version if anything at all changes in the serialization
 // format.
 //
 // Note that SCTAG_END_OF_KEYS is written into the serialized form and should have
@@ -1586,22 +1588,23 @@ JSStructuredCloneReader::startRead(Mutab
             return false;
         break;
       }
 
       case SCTAG_DATE_OBJECT: {
         double d;
         if (!in.readDouble(&d) || !checkDouble(d))
             return false;
-        if (!IsNaN(d) && d != TimeClip(d)) {
+        JS::ClippedTime t = JS::TimeClip(d);
+        if (!NumbersAreIdentical(d, t.value())) {
             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
                                  JSMSG_SC_BAD_SERIALIZED_DATA, "date");
             return false;
         }
-        JSObject* obj = NewDateObjectMsec(context(), d);
+        JSObject* obj = NewDateObjectMsec(context(), t);
         if (!obj)
             return false;
         vp.setObject(*obj);
         break;
       }
 
       case SCTAG_REGEXP_OBJECT: {
         RegExpFlag flags = RegExpFlag(data);