Bug 825247 - Fix unsafe use of JS::Value::toNumber in nsHTMLInputElement. r=bz a=bajaj
authorMounir Lamouri <mounir.lamouri@gmail.com>
Sat, 12 Jan 2013 01:43:44 +0000
changeset 127074 4f74542c367866b25827a772b6555855472505d4
parent 127073 a58d7d553e5e70254b7215d9a37562bf421ee884
child 127075 9496f016ef1d856e082e786c5f5989444cd8e818
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, bajaj
bugs825247
milestone20.0a2
Bug 825247 - Fix unsafe use of JS::Value::toNumber in nsHTMLInputElement. r=bz a=bajaj
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/test/forms/test_valueasdate_attribute.html
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -1071,57 +1071,65 @@ nsHTMLInputElement::ConvertStringToNumbe
     case NS_FORM_INPUT_NUMBER:
       {
         nsresult ec;
         aResultValue = PromiseFlatString(aValue).ToDouble(&ec);
         if (NS_FAILED(ec)) {
           return false;
         }
 
-        break;
+        return true;
       }
     case NS_FORM_INPUT_DATE:
       {
         JSContext* ctx = nsContentUtils::GetContextFromDocument(OwnerDoc());
         if (!ctx) {
           return false;
         }
 
         uint32_t year, month, day;
         if (!GetValueAsDate(aValue, year, month, day)) {
           return false;
         }
 
         JSObject* date = JS_NewDateObjectMsec(ctx, 0);
+        if (!date) {
+          JS_ClearPendingException(ctx);
+          return false;
+        }
+
         jsval rval;
         jsval fullYear[3];
         fullYear[0].setInt32(year);
         fullYear[1].setInt32(month-1);
         fullYear[2].setInt32(day);
         if (!JS::Call(ctx, date, "setUTCFullYear", 3, fullYear, &rval)) {
+          JS_ClearPendingException(ctx);
           return false;
         }
 
         jsval timestamp;
         if (!JS::Call(ctx, date, "getTime", 0, nullptr, &timestamp)) {
+          JS_ClearPendingException(ctx);
           return false;
         }
 
-        if (!timestamp.isNumber()) {
+        if (!timestamp.isNumber() || MOZ_DOUBLE_IS_NaN(timestamp.toNumber())) {
           return false;
         }
 
         aResultValue = timestamp.toNumber();
+        return true;
       }
-      break;
     default:
       return false;
   }
 
-  return true;
+  MOZ_NOT_REACHED();
+  return false;
 }
 
 double
 nsHTMLInputElement::GetValueAsDouble() const
 {
   double doubleValue;
   nsAutoString stringValue;
 
@@ -1237,23 +1245,32 @@ nsHTMLInputElement::ConvertNumberToStrin
           return false;
         }
 
         // The specs require |aValue| to be truncated.
         aValue = floor(aValue);
 
         JSObject* date = JS_NewDateObjectMsec(ctx, aValue);
         if (!date) {
+          JS_ClearPendingException(ctx);
           return false;
         }
 
         jsval year, month, day;
         if (!JS::Call(ctx, date, "getUTCFullYear", 0, nullptr, &year) ||
             !JS::Call(ctx, date, "getUTCMonth", 0, nullptr, &month) ||
             !JS::Call(ctx, date, "getUTCDate", 0, nullptr, &day)) {
+          JS_ClearPendingException(ctx);
+          return false;
+        }
+
+        if (!year.isNumber() || !month.isNumber() || !day.isNumber() ||
+            MOZ_DOUBLE_IS_NaN(year.toNumber()) ||
+            MOZ_DOUBLE_IS_NaN(month.toNumber()) ||
+            MOZ_DOUBLE_IS_NaN(day.toNumber())) {
           return false;
         }
 
         aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year.toNumber(),
                                    month.toNumber() + 1, day.toNumber());
 
 	return true;
       }
@@ -1275,22 +1292,29 @@ nsHTMLInputElement::GetValueAsDate(JSCon
   nsAutoString value;
   GetValueInternal(value);
   if (!GetValueAsDate(value, year, month, day)) {
     aDate->setNull();
     return NS_OK;
   }
 
   JSObject* date = JS_NewDateObjectMsec(aCtx, 0);
+  if (!date) {
+    JS_ClearPendingException(aCtx);
+    aDate->setNull();
+    return NS_OK;
+  }
+
   jsval rval;
   jsval fullYear[3];
   fullYear[0].setInt32(year);
   fullYear[1].setInt32(month-1);
   fullYear[2].setInt32(day);
   if(!JS::Call(aCtx, date, "setUTCFullYear", 3, fullYear, &rval)) {
+    JS_ClearPendingException(aCtx);
     aDate->setNull();
     return NS_OK;
   }
 
   aDate->setObjectOrNull(date);
   return NS_OK;
 }
 
@@ -1303,18 +1327,19 @@ nsHTMLInputElement::SetValueAsDate(JSCon
 
   if (!aDate.isObject() || !JS_ObjectIsDate(aCtx, &aDate.toObject())) {
     SetValue(EmptyString());
     return NS_OK;
   }
 
   JSObject& date = aDate.toObject();
   jsval timestamp;
-  bool ret = JS::Call(aCtx, &date, "getTime", 0, nullptr, &timestamp);
-  if (!ret || !timestamp.isNumber() || MOZ_DOUBLE_IS_NaN(timestamp.toNumber())) {
+  if (!JS::Call(aCtx, &date, "getTime", 0, nullptr, &timestamp) ||
+      !timestamp.isNumber() || MOZ_DOUBLE_IS_NaN(timestamp.toNumber())) {
+    JS_ClearPendingException(aCtx);
     SetValue(EmptyString());
     return NS_OK;
   }
 
   SetValue(timestamp.toNumber());
   return NS_OK;
 }
 
--- a/content/html/content/test/forms/test_valueasdate_attribute.html
+++ b/content/html/content/test/forms/test_valueasdate_attribute.html
@@ -171,36 +171,74 @@ function checkSet()
     [ 1298851200010,     "2011-02-28" ],
     [ -1,                "1969-12-31" ],
     [ -86400000,         "1969-12-31" ],
     [ 86400000,          "1970-01-02" ],
     // Negative years, this is out of range for the input element,
     // the corresponding date string is the empty string
     [ -62135596800001,   "" ],
     // Invalid dates.
+    // We set the value to something different than the empty string because
+    // NaN should set the value to the empty string.
+    [ 86400000,          "1970-01-02" ],
     [ NaN,               "" ],
   ];
 
   element.type = "date";
   for (data of testData) {
     element.valueAsDate = new Date(data[0]);
     is(element.value, data[1], "valueAsDate should set the value to "
                                 + data[1]);
   }
 
   element.valueAsDate = null;
   is(element.value, "", "valueAsDate should set the value to the empty string");
 
 }
 
+function checkWithBustedPrototype()
+{
+  var element = document.createElement('input');
+  element.type = 'date';
+
+  var witnessDate = new Date();
+
+  Date.prototype.getUTCFullYear = function() { return {}; };
+  Date.prototype.getUTCMonth = function() { return {}; };
+  Date.prototype.getUTCDate = function() { return {}; };
+  Date.prototype.getTime = function() { return {}; };
+  Date.prototype.setUTCFullYear = function(y,m,d) { };
+
+  element.valueAsDate = new Date();
+
+  todo_isnot(element.valueAsDate, null, ".valueAsDate should not return null");
+  // TODO: check the Date object value (UTCFullYear, UTCMonth and UTCDate)
+  // when .valueAsDate will stop returning null.
+
+  // Same test as above but using NaN instead of {}.
+
+  Date.prototype.getUTCFullYear = function() { return NaN; };
+  Date.prototype.getUTCMonth = function() { return NaN; };
+  Date.prototype.getUTCDate = function() { return NaN; };
+  Date.prototype.getTime = function() { return NaN; };
+  Date.prototype.setUTCFullYear = function(y,m,d) { };
+
+  element.valueAsDate = new Date();
+
+  todo_isnot(element.valueAsDate, null, ".valueAsDate should not return null");
+  // TODO: check the Date object value (UTCFullYear, UTCMonth and UTCDate)
+  // when .valueAsDate will stop returning null.
+}
+
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
 checkAvailability();
 checkGet();
 checkSet();
+checkWithBustedPrototype();
 
 SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>