Bug 1158399 - Expose the [[DateValue]] field in Date objects only through a ClippedTime class that enforces prior TimeClip-ing on the given value. r=evilpie, r=bz, r=dhylands, r=mt, r=froydnj, r=khuey, r=baku, r=smaug

This commit is contained in:
Jeff Walden 2015-05-01 19:12:52 -07:00
parent 715931e1f9
commit 4cafc6914a
18 changed files with 175 additions and 80 deletions

View File

@ -526,7 +526,7 @@ File::GetLastModifiedDate(ErrorResult& aRv)
return Date();
}
return Date(value);
return Date(JS::TimeClip(value));
}
int64_t

View File

@ -6,8 +6,9 @@
#include "mozilla/dom/Date.h"
#include "jsapi.h" // for JS_ObjectIsDate, JS_NewDateObjectMsec
#include "jsapi.h" // for JS_ObjectIsDate
#include "jsfriendapi.h" // for DateGetMsecSinceEpoch
#include "js/Date.h" // for JS::NewDateObject, JS::ClippedTime, JS::TimeClip
#include "js/RootingAPI.h" // for Rooted, MutableHandle
#include "js/Value.h" // for Value
#include "mozilla/FloatingPoint.h" // for IsNaN, UnspecifiedNaN
@ -15,30 +16,22 @@
namespace mozilla {
namespace dom {
Date::Date()
: mMsecSinceEpoch(UnspecifiedNaN<double>())
{
}
bool
Date::IsUndefined() const
{
return IsNaN(mMsecSinceEpoch);
}
bool
Date::SetTimeStamp(JSContext* aCx, JSObject* aObject)
{
JS::Rooted<JSObject*> obj(aCx, aObject);
MOZ_ASSERT(JS_ObjectIsDate(aCx, obj));
mMsecSinceEpoch = js::DateGetMsecSinceEpoch(aCx, obj);
double msecs = js::DateGetMsecSinceEpoch(aCx, obj);
JS::ClippedTime time = JS::TimeClip(msecs);
MOZ_ASSERT(NumbersAreIdentical(msecs, time.toDouble()));
mMsecSinceEpoch = time;
return true;
}
bool
Date::ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const
{
JSObject* obj = JS_NewDateObjectMsec(aCx, mMsecSinceEpoch);
JSObject* obj = JS::NewDateObject(aCx, mMsecSinceEpoch);
if (!obj) {
return false;
}

View File

@ -9,6 +9,7 @@
#ifndef mozilla_dom_Date_h
#define mozilla_dom_Date_h
#include "js/Date.h"
#include "js/TypeDecls.h"
namespace mozilla {
@ -17,21 +18,33 @@ namespace dom {
class Date
{
public:
// Not inlining much here to avoid the includes we'd need.
Date();
explicit Date(double aMilliseconds)
Date() {}
explicit Date(JS::ClippedTime aMilliseconds)
: mMsecSinceEpoch(aMilliseconds)
{}
bool IsUndefined() const;
double TimeStamp() const
bool IsUndefined() const
{
return !mMsecSinceEpoch.isValid();
}
JS::ClippedTime TimeStamp() const
{
return mMsecSinceEpoch;
}
void SetTimeStamp(double aMilliseconds)
// Returns an integer in the range [-8.64e15, +8.64e15] (-0 excluded), *or*
// returns NaN. DO NOT ASSUME THIS IS FINITE!
double ToDouble() const
{
return mMsecSinceEpoch.toDouble();
}
void SetTimeStamp(JS::ClippedTime aMilliseconds)
{
mMsecSinceEpoch = aMilliseconds;
}
// Can return false if CheckedUnwrap fails. This will NOT throw;
// callers should do it as needed.
bool SetTimeStamp(JSContext* aCx, JSObject* aObject);
@ -39,7 +52,7 @@ public:
bool ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const;
private:
double mMsecSinceEpoch;
JS::ClippedTime mMsecSinceEpoch;
};
} // namespace dom

View File

@ -4319,7 +4319,7 @@ nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
PRTime since = 0;
if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
since = PRTime(aOptions.mSince.Value().TimeStamp());
since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble());
}
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,

View File

@ -10,6 +10,7 @@
#include "js/Value.h"
#include "js/RootingAPI.h"
#include "jsapi.h"
#include "js/Date.h"
#include "mozilla/dom/FileModeBinding.h"
#include "nsDebug.h"
#include "nsIFileStreams.h"
@ -49,7 +50,7 @@ MetadataHelper::GetSuccessResult(JSContext* aCx,
if (mParams->LastModifiedRequested()) {
double msec = mParams->LastModified();
JSObject *date = JS_NewDateObjectMsec(aCx, msec);
JSObject *date = JS::NewDateObject(aCx, JS::TimeClip(msec));
NS_ENSURE_TRUE(date, NS_ERROR_OUT_OF_MEMORY);
JS::Rooted<JS::Value> dateRoot(aCx, JS::ObjectValue(*date));

View File

@ -1514,12 +1514,12 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
return false;
}
double date = JS::MakeDate(year, month - 1, day);
if (IsNaN(date)) {
JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
if (!time.isValid()) {
return false;
}
aResultValue = Decimal::fromDouble(date);
aResultValue = Decimal::fromDouble(time.toDouble());
return true;
}
case NS_FORM_INPUT_TIME:
@ -1762,7 +1762,8 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
return Nullable<Date>();
}
return Nullable<Date>(Date(JS::MakeDate(year, month - 1, day)));
JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
return Nullable<Date>(Date(time));
}
case NS_FORM_INPUT_TIME:
{
@ -1773,7 +1774,11 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
return Nullable<Date>();
}
return Nullable<Date>(Date(millisecond));
JS::ClippedTime time = JS::TimeClip(millisecond);
MOZ_ASSERT(time.toDouble() == millisecond,
"HTML times are restricted to the day after the epoch and "
"never clip");
return Nullable<Date>(Date(time));
}
}
@ -1795,7 +1800,7 @@ HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
return;
}
SetValue(Decimal::fromDouble(aDate.Value().TimeStamp()));
SetValue(Decimal::fromDouble(aDate.Value().TimeStamp().toDouble()));
}
NS_IMETHODIMP

View File

@ -20,6 +20,7 @@
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "js/Class.h"
#include "js/Date.h"
#include "js/StructuredClone.h"
#include "KeyPath.h"
#include "mozilla/Endian.h"
@ -749,8 +750,8 @@ public:
return false;
}
JS::Rooted<JSObject*> date(aCx,
JS_NewDateObjectMsec(aCx, aData.lastModifiedDate));
JS::ClippedTime time = JS::TimeClip(aData.lastModifiedDate);
JS::Rooted<JSObject*> date(aCx, JS::NewDateObject(aCx, time));
if (NS_WARN_IF(!date)) {
return false;
}

View File

@ -8,6 +8,7 @@
#include "Key.h"
#include <algorithm>
#include "js/Date.h"
#include "js/Value.h"
#include "jsfriendapi.h"
#include "mozilla/Endian.h"
@ -237,7 +238,11 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd,
}
else if (*aPos - aTypeOffset == eDate) {
double msec = static_cast<double>(DecodeNumber(aPos, aEnd));
JSObject* date = JS_NewDateObjectMsec(aCx, msec);
JS::ClippedTime time = JS::TimeClip(msec);
MOZ_ASSERT(msec == time.toDouble(),
"encoding from a Date object not containing an invalid date "
"means we should always have clipped values");
JSObject* date = JS::NewDateObject(aCx, time);
if (!date) {
IDB_WARNING("Failed to make date!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;

View File

@ -21,6 +21,7 @@
#include "mozilla/dom/Date.h"
#include "mozilla/dom/CryptoKey.h"
#include "mtransport/dtlsidentity.h"
#include "js/Date.h"
#include "js/StructuredClone.h"
#include "js/TypeDecls.h"
@ -54,7 +55,10 @@ public:
// WebIDL expires attribute. Note: JS dates are milliseconds since epoch;
// NSPR PRTime is in microseconds since the same epoch.
int64_t Expires() const { return mExpires / PR_USEC_PER_MSEC; }
JS::ClippedTime Expires() const
{
return JS::TimeClip(mExpires / PR_USEC_PER_MSEC);
}
// Accessors for use by PeerConnectionImpl.
RefPtr<DtlsIdentity> CreateDtlsIdentity() const;

View File

@ -34,7 +34,7 @@ TimeManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
void
TimeManager::Set(Date& aDate)
{
Set(aDate.TimeStamp());
Set(aDate.ToDouble());
}
void

View File

@ -3,51 +3,118 @@
* 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/. */
/* JavaScript date/time computation and creation functions. */
#ifndef js_Date_h
#define js_Date_h
/*
* Dates in JavaScript are defined by IEEE-754 double precision numbers from
* the set:
*
* { t : -8.64e15 t +8.64e15 } { NaN }
*
* The single NaN value represents any invalid-date value. All other values
* represent idealized durations in milliseconds since the UTC epoch. (Leap
* seconds are ignored; leap days are not.) +0 is the only zero in this set.
* The limit represented by 8.64e15 milliseconds is 100 million days either
* side of 00:00 January 1, 1970 UTC.
*
* Dates in the above set are represented by the |ClippedTime| class. The
* double type is a superset of the above set, so it *may* (but need not)
* represent a date. Use ECMAScript's |TimeClip| method to produce a date from
* a double.
*
* Date *objects* are simply wrappers around |TimeClip|'d numbers, with a bunch
* of accessor methods to the various aspects of the represented date.
*/
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"
#include "jstypes.h"
#include "js/Conversions.h"
#include "js/Value.h"
struct JSContext;
namespace JS {
class ClippedTime;
inline ClippedTime TimeClip(double time);
/*
* |ClippedTime| represents the limited subset of dates/times described above.
*
* An invalid date/time may be created through the |ClippedTime::invalid|
* method. Otherwise, a |ClippedTime| may be created using the |TimeClip|
* method.
*
* In typical use, the user might wish to manipulate a timestamp. The user
* performs a series of operations on it, but the final value might not be a
* date as defined above -- it could have overflowed, acquired a fractional
* component, &c. So as a *final* step, the user passes that value through
* |TimeClip| to produce a number restricted to JavaScript's date range.
*
* APIs that accept a JavaScript date value thus accept a |ClippedTime|, not a
* double. This ensures that date/time APIs will only ever receive acceptable
* JavaScript dates. This also forces users to perform any desired clipping,
* as only the user knows what behavior is desired when clipping occurs.
*/
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);
}
explicit ClippedTime(double time) : t(time) {}
friend ClippedTime TimeClip(double time);
public:
ClippedTime() : t(JS::GenericNaN()) {}
explicit ClippedTime(double time) : t(timeClip(time)) {}
// Create an invalid date.
ClippedTime() : t(mozilla::UnspecifiedNaN<double>()) {}
static ClippedTime NaN() { return ClippedTime(); }
// Create an invalid date/time, more explicitly; prefer this to the default
// constructor.
static ClippedTime invalid() { return ClippedTime(); }
double value() const { return t; }
double toDouble() const { return t; }
bool isValid() const { return !mozilla::IsNaN(t); }
};
// ES6 20.3.1.15.
//
// Clip a double to JavaScript's date range (or to an invalid date) using the
// ECMAScript TimeClip algorithm.
inline ClippedTime
TimeClip(double d)
TimeClip(double time)
{
return ClippedTime(d);
// Steps 1-2.
const double MaxTimeMagnitude = 8.64e15;
if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
return ClippedTime(mozilla::UnspecifiedNaN<double>());
// Step 3.
return ClippedTime(ToInteger(time) + (+0.0));
}
// 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.
// Produce a double Value from the given time. Because times may be NaN,
// prefer using this to manual canonicalization.
inline Value
TimeValue(ClippedTime time)
{
return DoubleValue(JS::CanonicalizeNaN(time.toDouble()));
}
// Create a new Date object whose [[DateValue]] internal slot contains the
// clipped |time|. (Users who must represent times outside that range must use
// another representation.)
extern JS_PUBLIC_API(JSObject*)
NewDateObject(JSContext* cx, ClippedTime time);
// Year is a year, month is 0-11, day is 1-based. The return value is a number
// of milliseconds since the epoch.
//
// Consistent with the MakeDate algorithm defined in ECMAScript, this value is
// *not* clipped! Use JS::TimeClip if you need a clipped date.
JS_PUBLIC_API(double)
MakeDate(double year, unsigned month, unsigned day);

View File

@ -5500,11 +5500,11 @@ JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min,
}
JS_PUBLIC_API(JSObject*)
JS_NewDateObjectMsec(JSContext* cx, double msec)
JS::NewDateObject(JSContext* cx, JS::ClippedTime time)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
return NewDateObjectMsec(cx, JS::TimeClip(msec));
return NewDateObjectMsec(cx, time);
}
JS_PUBLIC_API(bool)

View File

@ -4761,9 +4761,6 @@ SetForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue
extern JS_PUBLIC_API(JSObject*)
JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, int sec);
extern JS_PUBLIC_API(JSObject*)
JS_NewDateObjectMsec(JSContext* cx, double msec);
/*
* Infallible predicate to test whether obj is a date object.
*/

View File

@ -353,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)).value();
return ::MakeDate(MakeDay(year, month, day), 0);
}
JS_PUBLIC_API(double)
@ -640,7 +640,7 @@ date_UTC(JSContext* cx, unsigned argc, Value* vp)
// Step 16.
ClippedTime time = TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
args.rval().setDouble(time.value());
args.rval().set(TimeValue(time));
return true;
}
@ -878,7 +878,7 @@ ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* d
msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
*result = TimeClip(msec);
return NumbersAreIdentical(msec, result->value());
return NumbersAreIdentical(msec, result->toDouble());
#undef PEEK
#undef NEED
@ -1193,21 +1193,21 @@ date_parse(JSContext* cx, unsigned argc, Value* vp)
return true;
}
args.rval().setDouble(result.value());
args.rval().set(TimeValue(result));
return true;
}
static ClippedTime
NowAsMillis()
{
return ClippedTime(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
return TimeClip(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().value());
args.rval().set(TimeValue(NowAsMillis()));
return true;
}
@ -1217,14 +1217,14 @@ DateObject::setUTCTime(ClippedTime t)
for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
setReservedSlot(ind, UndefinedValue());
setFixedSlot(UTC_TIME_SLOT, DoubleValue(t.value()));
setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
}
void
DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp)
{
setUTCTime(t);
vp.setDouble(t.value());
vp.set(TimeValue(t));
}
void
@ -1687,7 +1687,7 @@ date_setTime_impl(JSContext* cx, CallArgs args)
{
Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
if (args.length() == 0) {
dateObj->setUTCTime(ClippedTime::NaN(), args.rval());
dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
return true;
}
@ -2322,7 +2322,7 @@ date_setYear_impl(JSContext* cx, CallArgs args)
/* Step 3. */
if (IsNaN(y)) {
dateObj->setUTCTime(ClippedTime::NaN(), args.rval());
dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
return true;
}
@ -2366,7 +2366,7 @@ static const char * const months[] =
static void
print_gmt_string(char* buf, size_t size, double utctime)
{
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
JS_snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
days[int(WeekDay(utctime))],
int(DateFromTime(utctime)),
@ -2380,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(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
JS_snprintf(buf, size, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
int(YearFromTime(utctime)),
int(MonthFromTime(utctime)) + 1,
@ -2394,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(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
JS_snprintf(buf, size, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
int(YearFromTime(utctime)),
int(MonthFromTime(utctime)) + 1,
@ -2550,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(NumbersAreIdentical(TimeClip(date).value(), date));
MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date));
double local = LocalTime(date, &cx->runtime()->dateTimeInfo);
@ -2990,7 +2990,7 @@ NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t)
static bool
ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t)
{
return date_format(cx, t.value(), FORMATSPEC_FULL, args.rval());
return date_format(cx, t.toDouble(), FORMATSPEC_FULL, args.rval());
}
static bool
@ -3023,7 +3023,7 @@ DateOneArgument(JSContext* cx, const CallArgs& args)
return false;
if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo))
t = ClippedTime::NaN();
t = ClippedTime::invalid();
} else {
double d;
if (!ToNumber(cx, args[0], &d))

View File

@ -43,7 +43,14 @@ class DateObject : public NativeObject
static const Class class_;
static const Class protoClass_;
inline const js::Value& UTCTime() const {
JS::ClippedTime clippedTime() const {
double t = getFixedSlot(UTC_TIME_SLOT).toDouble();
JS::ClippedTime clipped = JS::TimeClip(t);
MOZ_ASSERT(mozilla::NumbersAreIdentical(clipped.toDouble(), t));
return clipped;
}
const js::Value& UTCTime() const {
return getFixedSlot(UTC_TIME_SLOT);
}

View File

@ -28,6 +28,7 @@
#include "builtin/TypedObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
#include "js/Date.h"
#include "vm/Compression.h"
#include "vm/GeneratorObject.h"
#include "vm/Interpreter.h"
@ -1777,7 +1778,7 @@ CloneObject(JSContext* cx, HandleNativeObject selfHostedObject)
MOZ_ASSERT(source->isPermanentAtom());
clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
} else if (selfHostedObject->is<DateObject>()) {
clone = JS_NewDateObjectMsec(cx, selfHostedObject->as<DateObject>().UTCTime().toNumber());
clone = JS::NewDateObject(cx, selfHostedObject->as<DateObject>().clippedTime());
} else if (selfHostedObject->is<BooleanObject>()) {
clone = BooleanObject::create(cx, selfHostedObject->as<BooleanObject>().unbox());
} else if (selfHostedObject->is<NumberObject>()) {

View File

@ -1592,7 +1592,7 @@ JSStructuredCloneReader::startRead(MutableHandleValue vp)
if (!in.readDouble(&d) || !checkDouble(d))
return false;
JS::ClippedTime t = JS::TimeClip(d);
if (!NumbersAreIdentical(d, t.value())) {
if (!NumbersAreIdentical(d, t.toDouble())) {
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA, "date");
return false;

View File

@ -35,6 +35,7 @@
#include "mozilla/Services.h"
#include "nsIXPConnect.h"
#include "jsapi.h"
#include "js/Date.h"
#include "prenv.h"
#include "nsAppDirectoryServiceDefs.h"
@ -780,7 +781,7 @@ nsAppStartup::GetStartupInfo(JSContext* aCx, JS::MutableHandle<JS::Value> aRetva
if (stamp >= procTime) {
PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp)
/ PR_USEC_PER_MSEC;
JS::Rooted<JSObject*> date(aCx, JS_NewDateObjectMsec(aCx, prStamp));
JS::Rooted<JSObject*> date(aCx, JS::NewDateObject(aCx, JS::TimeClip(prStamp)));
JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE);
} else {
Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, ev);