diff --git a/js/public/Date.h b/js/public/Date.h index 6199f9eca5df..5adc71a7ffa4 100644 --- a/js/public/Date.h +++ b/js/public/Date.h @@ -6,10 +6,46 @@ #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) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 81be0cf34de2..ec3e4065cabb 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -61,6 +61,7 @@ #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" @@ -5237,7 +5238,7 @@ 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) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index f4f28762f5be..ea3bbdd4ccc4 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -50,9 +50,12 @@ 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; /* @@ -350,7 +353,7 @@ MakeDate(double day, double 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) @@ -566,14 +569,6 @@ RegionMatches(const char* s1, int s1off, const CharT* s2, int s2off, int 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) @@ -644,7 +639,8 @@ date_UTC(JSContext* cx, unsigned argc, Value* vp) } // 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; } @@ -777,7 +773,7 @@ DaysInMonth(int year, int month) */ template 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; @@ -873,19 +869,16 @@ ParseISODate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo 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 @@ -895,7 +888,7 @@ ParseISODate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo template 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; @@ -1157,19 +1150,19 @@ ParseDate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo) 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() @@ -1194,45 +1187,44 @@ date_parse(JSContext* cx, unsigned argc, Value* vp) 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(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 @@ -1695,7 +1687,7 @@ date_setTime_impl(JSContext* cx, CallArgs args) { Rooted dateObj(cx, &args.thisv().toObject().as()); if (args.length() == 0) { - dateObj->setUTCTime(GenericNaN(), args.rval()); + dateObj->setUTCTime(ClippedTime::NaN(), args.rval()); return true; } @@ -1760,7 +1752,7 @@ date_setMilliseconds_impl(JSContext* cx, CallArgs args) 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()); @@ -1790,7 +1782,7 @@ date_setUTCMilliseconds_impl(JSContext* cx, CallArgs args) 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()); @@ -1827,7 +1819,7 @@ date_setSeconds_impl(JSContext* cx, CallArgs args) 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()); @@ -1864,7 +1856,7 @@ date_setUTCSeconds_impl(JSContext* cx, CallArgs args) 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()); @@ -1906,7 +1898,7 @@ date_setMinutes_impl(JSContext* cx, CallArgs args) 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()); @@ -1948,7 +1940,7 @@ date_setUTCMinutes_impl(JSContext* cx, CallArgs args) 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()); @@ -1995,7 +1987,7 @@ date_setHours_impl(JSContext* cx, CallArgs args) 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()); @@ -2042,7 +2034,7 @@ date_setUTCHours_impl(JSContext* cx, CallArgs args) 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()); @@ -2074,7 +2066,7 @@ date_setDate_impl(JSContext* cx, CallArgs args) 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()); @@ -2106,7 +2098,7 @@ date_setUTCDate_impl(JSContext* cx, CallArgs args) 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()); @@ -2163,7 +2155,7 @@ date_setMonth_impl(JSContext* cx, CallArgs args) 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()); @@ -2200,7 +2192,7 @@ date_setUTCMonth_impl(JSContext* cx, CallArgs args) 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()); @@ -2258,7 +2250,7 @@ date_setFullYear_impl(JSContext* cx, CallArgs args) 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()); @@ -2300,7 +2292,7 @@ date_setUTCFullYear_impl(JSContext* cx, CallArgs args) 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()); @@ -2330,7 +2322,7 @@ date_setYear_impl(JSContext* cx, CallArgs args) /* Step 3. */ if (IsNaN(y)) { - dateObj->setUTCTime(GenericNaN(), args.rval()); + dateObj->setUTCTime(ClippedTime::NaN(), args.rval()); return true; } @@ -2374,7 +2366,7 @@ static const char * const months[] = 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)), @@ -2388,7 +2380,7 @@ print_gmt_string(char* buf, size_t size, double 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, @@ -2402,7 +2394,7 @@ print_iso_string(char* buf, size_t size, double 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, @@ -2558,7 +2550,7 @@ date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rv 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); @@ -2973,9 +2965,9 @@ static const JSFunctionSpec date_methods[] = { }; 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; @@ -2984,9 +2976,9 @@ NewDateObject(JSContext* cx, const CallArgs& args, double d) } 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 @@ -2994,7 +2986,7 @@ 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); @@ -3008,7 +3000,7 @@ 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; @@ -3018,17 +3010,16 @@ DateOneArgument(JSContext* cx, const CallArgs& args) 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()); @@ -3131,7 +3122,7 @@ js::DateConstructor(JSContext* cx, unsigned argc, Value* vp) static bool FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto) { - proto->as().setUTCTime(GenericNaN()); + proto->as().setUTCTime(ClippedTime::NaN()); /* * Date.prototype.toGMTString has the same initial value as @@ -3173,13 +3164,13 @@ const Class DateObject::class_ = { } }; -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().setUTCTime(msec_time); + obj->as().setUTCTime(t); return obj; } @@ -3188,8 +3179,8 @@ 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) diff --git a/js/src/jsdate.h b/js/src/jsdate.h index 29fcf8c88a56..29bde83bb237 100644 --- a/js/src/jsdate.h +++ b/js/src/jsdate.h @@ -13,9 +13,12 @@ #include "jstypes.h" +#include "js/Date.h" #include "js/RootingAPI.h" #include "js/TypeDecls.h" +#include "vm/DateTime.h" + namespace js { /* @@ -26,8 +29,8 @@ namespace js { * 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. diff --git a/js/src/vm/DateObject.h b/js/src/vm/DateObject.h index cfe1a545ddbb..e15b3c69ff4f 100644 --- a/js/src/vm/DateObject.h +++ b/js/src/vm/DateObject.h @@ -9,6 +9,7 @@ #include "jsobj.h" +#include "js/Date.h" #include "js/Value.h" namespace js { @@ -46,8 +47,8 @@ class DateObject : public NativeObject } // 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); diff --git a/js/src/vm/DateTime.h b/js/src/vm/DateTime.h index 4b00a6423439..4ec477b3ee61 100644 --- a/js/src/vm/DateTime.h +++ b/js/src/vm/DateTime.h @@ -39,19 +39,6 @@ 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 diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 785496a9e617..7e106834345a 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -40,6 +40,7 @@ #include "jswrapper.h" #include "builtin/MapObject.h" +#include "js/Date.h" #include "vm/SharedArrayObject.h" #include "vm/TypedArrayObject.h" #include "vm/WrapperObject.h" @@ -53,6 +54,7 @@ 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 @@ -1591,12 +1593,13 @@ JSStructuredCloneReader::startRead(MutableHandleValue vp) 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);