Date object needs to check for ES5 15.9.1.14 TimeClip limit.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Apr 2014 18:24:31 +0000 (18:24 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Apr 2014 18:24:31 +0000 (18:24 +0000)
<https://webkit.org/b/131248>

Reviewed by Mark Hahnenberg.

Source/JavaScriptCore:

The current Date object code does not adequately check for the ES5
15.9.1.14 TimeClip limit.  As a result, some calculations can underflow
/ overflow and produce unexpected results.

For example, we were getting an assertion failure in
WTF::equivalentYearForDST() due int underflows in this function, which
in turn were due to an int overflow in WTF::msToYear().

This patch adds the needed checks, and adds some assertions to ensure
that the used values are sane.

The changes have no noticeable impact on benchmark results.

* runtime/DateConstructor.cpp:
(JSC::callDate):
* runtime/JSDateMath.cpp:
(JSC::localTimeOffset):
(JSC::gregorianDateTimeToMS):
(JSC::msToGregorianDateTime):
(JSC::parseDateFromNullTerminatedCharacters):
(JSC::parseDate):
* runtime/JSDateMath.h:
- parseDateFromNullTerminatedCharacters() does not need to be public.
  Made it a static function.
* runtime/VM.cpp:
(JSC::VM::resetDateCache):
- Changed cachedDateStringValue to use std::numeric_limits<double>::quiet_NaN()
  to be consistent with other Date code.

Source/WTF:

* wtf/DateMath.cpp:
- Moved the definition of maxECMAScriptTime to the .h file so that we
  can use it in other files as well.
(WTF::msToYear):
- Removed a stale comment for parseDateFromNullTerminatedCharacters().
* wtf/DateMath.h:

LayoutTests:

* js/regress-131248-expected.txt: Added.
* js/regress-131248.html: Added.
* js/script-tests/regress-131248.js: Added.
(testDateFromSetDateAdjustement):
(testDateFromSetTimeWithMilliseconds):
(testDateFromString):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@166876 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
LayoutTests/ChangeLog
LayoutTests/js/regress-131248-expected.txt [new file with mode: 0644]
LayoutTests/js/regress-131248.html [new file with mode: 0644]
LayoutTests/js/script-tests/regress-131248.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/DateConstructor.cpp
Source/JavaScriptCore/runtime/JSDateMath.cpp
Source/JavaScriptCore/runtime/JSDateMath.h
Source/JavaScriptCore/runtime/VM.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/DateMath.cpp
Source/WTF/wtf/DateMath.h

index 7d07a83..ef94c9e 100644 (file)
@@ -1,3 +1,17 @@
+2014-04-04  Mark Lam  <mark.lam@apple.com>
+
+        Date object needs to check for ES5 15.9.1.14 TimeClip limit.
+        <https://webkit.org/b/131248>
+
+        Reviewed by Mark Hahnenberg.
+
+        * js/regress-131248-expected.txt: Added.
+        * js/regress-131248.html: Added.
+        * js/script-tests/regress-131248.js: Added.
+        (testDateFromSetDateAdjustement):
+        (testDateFromSetTimeWithMilliseconds):
+        (testDateFromString):
+
 2014-04-07  Sergio Villar Senin  <svillar@igalia.com>
 
         Unreviewed gardening for GTK.
diff --git a/LayoutTests/js/regress-131248-expected.txt b/LayoutTests/js/regress-131248-expected.txt
new file mode 100644 (file)
index 0000000..82bd4c3
--- /dev/null
@@ -0,0 +1,41 @@
+This test checks date values at the limits set by the ES5 15.9.1.14 TimeClip specification and ensures that we don't crash on any assertions.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+(new Date(1396547803766)).setDate(1396549003769) ==> NaN ms, Invalid Date
+(new Date(1396547803766)).setDate(100000000) ==> NaN ms, Invalid Date
+(new Date(1396547803766)).setDate(99983840) ==> NaN ms, Invalid Date
+(new Date(1396547803766)).setDate(99983839) ==> 8639999978203766 ms, Fri Sep 12 275760
+(new Date(1396547803766)).setDate(10000000) ==> 865396288603766 ms, Thu Apr 25 29393
+
+(new Date(0)).setTime(8640000000000001) ==> NaN ms, Invalid Date NaN ms
+(new Date(0)).setTime(8640000000000000) ==> 8640000000000000 ms, Sat, 13 Sep 275760 00:00:00 GMT 0 ms
+(new Date(0)).setTime(-8640000000000000) ==> -8640000000000000 ms, Tue, 20 Apr -271821 00:00:00 GMT 0 ms
+(new Date(0)).setTime(-8640000000000001) ==> NaN ms, Invalid Date NaN ms
+
+(new Date(13 Sep 275760 00:00:00 -0001) ==> NaN ms, Invalid Date NaN ms
+(new Date(13 Sep 275760 00:00:00 +0000) ==> 8640000000000000 ms, Sat, 13 Sep 275760 00:00:00 GMT 0 ms
+(new Date(13 Sep 275760 00:00:00 +0001) ==> 8639999996400000 ms, Fri, 12 Sep 275760 23:00:00 GMT 0 ms
+(new Date(20 Apr -271821 00:00:00 -0001) ==> -8639999996400000 ms, Tue, 20 Apr -271821 01:00:00 GMT 0 ms
+(new Date(20 Apr -271821 00:00:00 +0000) ==> -8640000000000000 ms, Tue, 20 Apr -271821 00:00:00 GMT 0 ms
+(new Date(20 Apr -271821 00:00:00 +0001) ==> NaN ms, Invalid Date NaN ms
+
+(new Date(19 Apr -271821 23:59:59) ==> NaN ms, Invalid Date NaN ms
+
+(new Date(275760-09-13T00:00:00.001) ==> NaN ms, Invalid Date NaN ms
+(new Date(275760-09-13T00:00:00.000) ==> 8640000000000000 ms, Sat, 13 Sep 275760 00:00:00 GMT 0 ms
+(new Date(-271821-04-20T00:00:00.0000) ==> -8640000000000000 ms, Tue, 20 Apr -271821 00:00:00 GMT 0 ms
+(new Date(-271821-04-19T23:59:59.999) ==> NaN ms, Invalid Date NaN ms
+
+(new Date(Sat, 13 Sep 275760 00:00:00 UTC-2) ==> NaN ms, Invalid Date NaN ms
+(new Date(Sat, 13 Sep 275760 00:00:00 UTC) ==> 8640000000000000 ms, Sat, 13 Sep 275760 00:00:00 GMT 0 ms
+(new Date(Sat, 13 Sep 275760 00:00:00 UTC+2) ==> 8639999992800000 ms, Fri, 12 Sep 275760 22:00:00 GMT 0 ms
+(new Date(Tue, 20 Apr -271821 00:00:00 UTC-2) ==> -8639999992800000 ms, Tue, 20 Apr -271821 02:00:00 GMT 0 ms
+(new Date(Tue, 20 Apr -271821 00:00:00 UTC) ==> -8640000000000000 ms, Tue, 20 Apr -271821 00:00:00 GMT 0 ms
+(new Date(Tue, 20 Apr -271821 00:00:00 UTC+2) ==> NaN ms, Invalid Date NaN ms
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/regress-131248.html b/LayoutTests/js/regress-131248.html
new file mode 100644 (file)
index 0000000..5dbd626
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script src="script-tests/regress-131248.js"></script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/script-tests/regress-131248.js b/LayoutTests/js/script-tests/regress-131248.js
new file mode 100644 (file)
index 0000000..98e3379
--- /dev/null
@@ -0,0 +1,57 @@
+description(
+"This test checks date values at the limits set by the ES5 15.9.1.14 TimeClip specification and ensures that we don't crash on any assertions."
+);
+
+function testDateFromSetDateAdjustement(initialMs, adjustMs) {
+    var date = new Date(initialMs);
+    debug("(new Date(" + initialMs + ")).setDate(" + adjustMs + ") ==> " + date.setDate(adjustMs) + " ms, " + date.toDateString());
+}
+
+testDateFromSetDateAdjustement(1396547803766, 1396549003769);
+testDateFromSetDateAdjustement(1396547803766, 100000000);
+testDateFromSetDateAdjustement(1396547803766, 99983839+1);
+testDateFromSetDateAdjustement(1396547803766, 99983839);
+testDateFromSetDateAdjustement(1396547803766, 10000000);
+debug("");
+
+function testDateFromSetTimeWithMilliseconds(ms) {
+    var date = new Date(0);
+    debug("(new Date(0)).setTime(" + ms + ") ==> " + date.setTime(ms) + " ms, " + date.toUTCString() + " " + date.getUTCMilliseconds() + " ms");
+}
+
+testDateFromSetTimeWithMilliseconds(8640000000000001);
+testDateFromSetTimeWithMilliseconds(8640000000000000);
+testDateFromSetTimeWithMilliseconds(-8640000000000000);
+testDateFromSetTimeWithMilliseconds(-8640000000000001);
+debug("");
+
+function testDateFromString(str) {
+    var date = new Date(str);
+    debug("(new Date(" + str + ") ==> " + date.getTime() + " ms, " + date.toUTCString() + " " + date.getUTCMilliseconds() + " ms");
+}
+
+testDateFromString("13 Sep 275760 00:00:00 -0001");
+testDateFromString("13 Sep 275760 00:00:00 +0000");
+testDateFromString("13 Sep 275760 00:00:00 +0001");
+testDateFromString("20 Apr -271821 00:00:00 -0001");
+testDateFromString("20 Apr -271821 00:00:00 +0000");
+testDateFromString("20 Apr -271821 00:00:00 +0001");
+debug("");
+
+testDateFromString("19 Apr -271821 23:59:59");
+debug("");
+
+testDateFromString("275760-09-13T00:00:00.001");
+testDateFromString("275760-09-13T00:00:00.000");
+testDateFromString("-271821-04-20T00:00:00.0000");
+testDateFromString("-271821-04-19T23:59:59.999");
+debug("");
+
+testDateFromString("Sat, 13 Sep 275760 00:00:00 UTC-2");
+testDateFromString("Sat, 13 Sep 275760 00:00:00 UTC");
+testDateFromString("Sat, 13 Sep 275760 00:00:00 UTC+2");
+testDateFromString("Tue, 20 Apr -271821 00:00:00 UTC-2");
+testDateFromString("Tue, 20 Apr -271821 00:00:00 UTC");
+testDateFromString("Tue, 20 Apr -271821 00:00:00 UTC+2");
+debug("");
+
index 73660bb..e777030 100644 (file)
@@ -1,3 +1,39 @@
+2014-04-04  Mark Lam  <mark.lam@apple.com>
+
+        Date object needs to check for ES5 15.9.1.14 TimeClip limit.
+        <https://webkit.org/b/131248>
+
+        Reviewed by Mark Hahnenberg.
+
+        The current Date object code does not adequately check for the ES5
+        15.9.1.14 TimeClip limit.  As a result, some calculations can underflow
+        / overflow and produce unexpected results.
+
+        For example, we were getting an assertion failure in
+        WTF::equivalentYearForDST() due int underflows in this function, which
+        in turn were due to an int overflow in WTF::msToYear().
+
+        This patch adds the needed checks, and adds some assertions to ensure
+        that the used values are sane.
+
+        The changes have no noticeable impact on benchmark results.
+
+        * runtime/DateConstructor.cpp:
+        (JSC::callDate):
+        * runtime/JSDateMath.cpp:
+        (JSC::localTimeOffset):
+        (JSC::gregorianDateTimeToMS):
+        (JSC::msToGregorianDateTime):
+        (JSC::parseDateFromNullTerminatedCharacters):
+        (JSC::parseDate):
+        * runtime/JSDateMath.h:
+        - parseDateFromNullTerminatedCharacters() does not need to be public.
+          Made it a static function.
+        * runtime/VM.cpp:
+        (JSC::VM::resetDateCache):
+        - Changed cachedDateStringValue to use std::numeric_limits<double>::quiet_NaN()
+          to be consistent with other Date code.
+
 2014-04-06  Csaba Osztrogon√°c  <ossy@webkit.org>
 
         Unreviewed speculative 32-bit buildfix after r166837.
index 18b9dcd..2bbdd39 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- *  Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
+ *  Copyright (C) 2004-2008, 2011, 2014 Apple Inc. All rights reserved.
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -190,7 +190,12 @@ static EncodedJSValue JSC_HOST_CALL callDate(ExecState* exec)
 {
     VM& vm = exec->vm();
     GregorianDateTime ts;
-    msToGregorianDateTime(vm, currentTimeMS(), false, ts);
+
+    double currentTime = timeClip(currentTimeMS());
+    if (std::isnan(currentTime))
+        return JSValue::encode(jsNaN());
+    
+    msToGregorianDateTime(vm, currentTime, false, ts);
     return JSValue::encode(jsNontrivialString(&vm, formatDateTime(ts, DateTimeFormatDateAndTime, false)));
 }
 
index b2afc16..16f1ecc 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- * Copyright (C) 2006, 2007, 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2007, 2012, 2014 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  * Copyright (C) 2007-2009 Torch Mobile, Inc.
  * Copyright (C) 2010 &yet, LLC. (nate@andyet.net)
@@ -138,6 +138,7 @@ static LocalTimeOffset localTimeOffset(VM& vm, double ms)
     double start = cache.start;
     double end = cache.end;
 
+    ASSERT(fabs(ms) <= WTF::maxECMAScriptTime);
     if (start <= ms) {
         // If the time fits in the cached interval, return the cached offset.
         if (ms <= end) return cache.offset;
@@ -145,7 +146,7 @@ static LocalTimeOffset localTimeOffset(VM& vm, double ms)
         // Compute a possible new interval end.
         double newEnd = end + cache.increment;
 
-        if (ms <= newEnd) {
+        if (ms <= newEnd && newEnd <= WTF::maxECMAScriptTime) {
             LocalTimeOffset endOffset = calculateLocalTimeOffset(newEnd);
             if (cache.offset == endOffset) {
                 // If the offset at the end of the new interval still matches
@@ -194,15 +195,19 @@ double gregorianDateTimeToMS(VM& vm, const GregorianDateTime& t, double milliSec
     double ms = timeToMS(t.hour(), t.minute(), t.second(), milliSeconds);
     double result = (day * WTF::msPerDay) + ms;
 
+    if (fabs(result) > WTF::maxECMAScriptTime)
+        return std::numeric_limits<double>::quiet_NaN();
+
     if (!inputIsUTC)
         result -= localTimeOffset(vm, result).offset;
 
-    return result;
+    return timeClip(result);
 }
 
 // input is UTC
 void msToGregorianDateTime(VM& vm, double ms, bool outputIsUTC, GregorianDateTime& tm)
 {
+    ASSERT(fabs(ms) <= WTF::maxECMAScriptTime);
     LocalTimeOffset localTime;
     if (!outputIsUTC) {
         localTime = localTimeOffset(vm, ms);
@@ -222,13 +227,13 @@ void msToGregorianDateTime(VM& vm, double ms, bool outputIsUTC, GregorianDateTim
     tm.setUtcOffset(localTime.offset / WTF::msPerSecond);
 }
 
-double parseDateFromNullTerminatedCharacters(VM& vm, const char* dateString)
+static double parseDateFromNullTerminatedCharacters(VM& vm, const char* dateString)
 {
     bool haveTZ;
     int offset;
     double ms = WTF::parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset);
-    if (std::isnan(ms))
-        return QNaN;
+    if (std::isnan(ms) || fabs(ms) > WTF::maxECMAScriptTime)
+        return std::numeric_limits<double>::quiet_NaN();
 
     // fall back to local timezone
     if (!haveTZ)
@@ -244,6 +249,7 @@ double parseDate(VM& vm, const String& date)
     double value = parseES5DateFromNullTerminatedCharacters(date.utf8().data());
     if (std::isnan(value))
         value = parseDateFromNullTerminatedCharacters(vm, date.utf8().data());
+    value = timeClip(value);
     vm.cachedDateString = date;
     vm.cachedDateStringValue = value;
     return value;
index 17ba4a2..61b490d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2007, 2014 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  * Copyright (C) 2010 Research In Motion Limited. All rights reserved.
  *
@@ -53,7 +53,6 @@ class VM;
 void msToGregorianDateTime(VM&, double, bool outputIsUTC, GregorianDateTime&);
 double gregorianDateTimeToMS(VM&, const GregorianDateTime&, double, bool inputIsUTC);
 double getUTCOffset(VM&);
-double parseDateFromNullTerminatedCharacters(VM&, const char* dateString);
 double parseDate(VM&, const WTF::String&);
 
 } // namespace JSC
index 228178d..9ff3f3b 100644 (file)
@@ -501,7 +501,7 @@ void VM::resetDateCache()
 {
     localTimeOffsetCache.reset();
     cachedDateString = String();
-    cachedDateStringValue = QNaN;
+    cachedDateStringValue = std::numeric_limits<double>::quiet_NaN();
     dateInstanceCache.reset();
 }
 
index 59bc5fd..032b27b 100644 (file)
@@ -1,3 +1,17 @@
+2014-04-04  Mark Lam  <mark.lam@apple.com>
+
+        Date object needs to check for ES5 15.9.1.14 TimeClip limit.
+        <https://webkit.org/b/131248>
+
+        Reviewed by Mark Hahnenberg.
+
+        * wtf/DateMath.cpp:
+        - Moved the definition of maxECMAScriptTime to the .h file so that we
+          can use it in other files as well.
+        (WTF::msToYear):
+        - Removed a stale comment for parseDateFromNullTerminatedCharacters().
+        * wtf/DateMath.h:
+
 2014-04-04  Mark Hahnenberg  <mhahnenberg@apple.com>
 
         Enhanced GC logging
index 6ee36d3..c81dddf 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2007, 2014 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  * Copyright (C) 2007-2009 Torch Mobile, Inc.
  * Copyright (C) 2010 &yet, LLC. (nate@andyet.net)
@@ -109,10 +109,6 @@ namespace WTF {
 /* Constants */
 
 static const double maxUnixTime = 2145859200.0; // 12/31/2037
-// ECMAScript asks not to support for a date of which total
-// millisecond value is larger than the following value.
-// See 15.9.1.14 of ECMA-262 5th edition.
-static const double maxECMAScriptTime = 8.64E15;
 
 // Day of year for the first day of each month, where index 0 is January, and day 0 is January 1.
 // First for non-leap years, then for leap years.
@@ -184,6 +180,7 @@ static void appendTwoDigitNumber(StringBuilder& builder, int number)
 
 int msToYear(double ms)
 {
+    ASSERT(fabs(ms) <= maxECMAScriptTime);
     int approxYear = static_cast<int>(floor(ms / (msPerDay * 365.2425)) + 1970);
     double msFromApproxYearTo1970 = msPerDay * daysFrom1970ToYear(approxYear);
     if (msFromApproxYearTo1970 > ms)
@@ -330,7 +327,7 @@ static inline int minimumYearForDST()
 }
 
 /*
- * Find an equivalent year for the one given, where equivalence is deterined by
+ * Find an equivalent year for the one given, where equivalence is determined by
  * the two years having the same leapness and the first day of the year, falling
  * on the same day of the week.
  *
@@ -796,7 +793,6 @@ double parseES5DateFromNullTerminatedCharacters(const char* dateString)
     return dateSeconds * msPerSecond;
 }
 
-// Odd case where 'exec' is allowed to be 0, to accomodate a caller in WebCore.
 double parseDateFromNullTerminatedCharacters(const char* dateString, bool& haveTZ, int& offset)
 {
     haveTZ = false;
index c8ae0d9..2b9e329 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2007, 2014 Apple Inc. All rights reserved.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  * Copyright (C) 2010 Research In Motion Limited. All rights reserved.
  *
 
 namespace WTF {
 
+// ECMAScript asks not to support for a date of which total
+// millisecond value is larger than the following value.
+// See 15.9.1.14 of ECMA-262 5th edition.
+static const double maxECMAScriptTime = 8.64E15;
+
 struct LocalTimeOffset {
     LocalTimeOffset()
         : isDST(false)