Bug 795227 - ParallelArray should check length range like Array (r=dvander)
authorShu-yu Guo <shu@rfrn.org>
Tue, 02 Oct 2012 11:33:46 -0700
changeset 108923 a6991bc0778d9cfe3a081f39627bd290fed71c49
parent 108922 f1869aa32cba1c71b04827c8420c3b3423f65b0d
child 108924 b8e4333af38a36a76c1d018c446734c3ba3f1121
push id15758
push usershu@rfrn.org
push dateTue, 02 Oct 2012 19:01:13 +0000
treeherdermozilla-inbound@a6991bc0778d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander
bugs795227
milestone18.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 795227 - ParallelArray should check length range like Array (r=dvander)
js/src/builtin/ParallelArray.cpp
js/src/jit-test/tests/parallelarray/comprehension-throws.js
js/src/jit-test/tests/parallelarray/constructor-throws.js
js/src/jit-test/tests/parallelarray/filter-throws.js
js/src/jit-test/tests/parallelarray/overflow-throws.js
js/src/jit-test/tests/parallelarray/scatter-throws.js
--- a/js/src/builtin/ParallelArray.cpp
+++ b/js/src/builtin/ParallelArray.cpp
@@ -40,16 +40,36 @@ ReportMoreArgsNeeded(JSContext *cx, cons
 
 static bool
 ReportBadArg(JSContext *cx, const char *s = "")
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_BAD_ARG, s);
     return false;
 }
 
+static bool
+ReportBadLength(JSContext *cx)
+{
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ARRAY_LENGTH);
+    return false;
+}
+
+static bool
+ReportBadLengthOrArg(JSContext *cx, HandleValue v, const char *s = "")
+{
+    return v.isNumber() ? ReportBadLength(cx) : ReportBadArg(cx, s);
+}
+
+static bool
+ReportBadPartition(JSContext *cx)
+{
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_BAD_PARTITION);
+    return false;
+}
+
 bool
 ParallelArrayObject::IndexInfo::isInitialized() const
 {
     return (dimensions.length() > 0 &&
             indices.capacity() >= dimensions.length() &&
             partialProducts.length() == dimensions.length());
 }
 
@@ -84,32 +104,94 @@ CloseDelimiters(const IndexInfo &iv, Str
 
         if (!sb.append('>'))
             return false;
     } while (d-- > 0);
 
     return true;
 }
 
+// A version of ToUint32 that reports if the input value is malformed: either
+// it is given to us as a negative integer or it overflows.
+static bool
+ToUint32(JSContext *cx, const Value &v, uint32_t *out, bool *malformed)
+{
+    AssertArgumentsAreSane(cx, v);
+    {
+        js::SkipRoot skip(cx, &v);
+        js::MaybeCheckStackRoots(cx);
+    }
+
+    *malformed = false;
+
+    if (v.isInt32()) {
+        int32_t i = v.toInt32();
+        if (i < 0) {
+            *malformed = true;
+            return true;
+        }
+        *out = static_cast<uint32_t>(i);
+        return true;
+    }
+
+    double d;
+    if (v.isDouble()) {
+        d = v.toDouble();
+    } else {
+        if (!ToNumberSlow(cx, v, &d))
+            return false;
+    }
+
+    *out = ToUint32(d);
+
+    if (!MOZ_DOUBLE_IS_FINITE(d) || d != static_cast<double>(*out)) {
+        *malformed = true;
+        return true;
+    }
+
+    return true;
+}
+
+static bool
+GetLength(JSContext *cx, HandleObject obj, uint32_t *length)
+{
+    // If obj's length cannot overflow, just use GetLengthProperty.
+    if (obj->isArray() || obj->isArguments())
+        return GetLengthProperty(cx, obj, length);
+
+    // Otherwise check that we don't overflow uint32.
+    RootedValue value(cx);
+    if (!JSObject::getProperty(cx, obj, obj, cx->names().length, &value))
+        return false;
+
+    bool malformed;
+    if (!ToUint32(cx, value, length, &malformed))
+        return false;
+    if (malformed)
+        return ReportBadLengthOrArg(cx, value);
+
+    return true;
+}
+
 // Check if obj is a parallel array, and if so, cast to pa and initialize
 // the IndexInfo accordingly.
 //
 // This function is designed to be used in conjunction with
 // GetElementFromArrayLikeObject; see below.
 static bool
 MaybeGetParallelArrayObjectAndLength(JSContext *cx, HandleObject obj,
                                      MutableHandle<ParallelArrayObject *> pa,
                                      IndexInfo *iv, uint32_t *length)
 {
     if (ParallelArrayObject::is(obj)) {
         pa.set(ParallelArrayObject::as(obj));
         if (!pa->isOneDimensional() && !iv->initialize(cx, pa, 1))
             return false;
         *length = pa->outermostDimension();
-    } else if (!GetLengthProperty(cx, obj, length)) {
+    } else if (!GetLength(cx, obj, length)) {
         return false;
     }
 
     return true;
 }
 
 // Store the i-th element of the array-like object obj into vp.
 //
@@ -233,35 +315,42 @@ NewDenseArrayWithType(JSContext *cx, uin
         return NULL;
 
     return *buffer.address();
 }
 
 // Copy an array like object obj into an IndexVector, indices, using
 // ToUint32.
 static inline bool
-ArrayLikeToIndexVector(JSContext *cx, HandleObject obj, IndexVector &indices)
+ArrayLikeToIndexVector(JSContext *cx, HandleObject obj, IndexVector &indices,
+                       bool *malformed)
 {
     IndexInfo iv(cx);
     RootedParallelArrayObject pa(cx);
     uint32_t length;
 
+    *malformed = false;
+
     if (!MaybeGetParallelArrayObjectAndLength(cx, obj, &pa, &iv, &length))
         return false;
 
     if (!indices.resize(length))
         return false;
 
     RootedValue elem(cx);
+    bool malformed_;
     for (uint32_t i = 0; i < length; i++) {
         if (!GetElementFromArrayLikeObject(cx, obj, pa, iv, i, &elem) ||
-            !ToUint32(cx, elem, &indices[i]))
+            !ToUint32(cx, elem, &indices[i], &malformed_))
         {
             return false;
         }
+
+        if (malformed_)
+            *malformed = true;
     }
 
     return true;
 }
 
 static inline bool
 IdIsInBoundsIndex(JSContext *cx, HandleObject obj, HandleId id)
 {
@@ -509,23 +598,29 @@ ParallelArrayObject::SequentialMode::sca
 
     // Iterate over the scatter vector, but not more than the length of the
     // source array.
     RootedValue elem(cx);
     RootedValue telem(cx);
     RootedValue targetElem(cx);
     for (uint32_t i = 0; i < Min(targetsLength, source->outermostDimension()); i++) {
         uint32_t targetIndex;
+        bool malformed;
 
         if (!GetElementFromArrayLikeObject(cx, targets, targetsPA, tiv, i, &telem) ||
-            !ToUint32(cx, telem, &targetIndex))
+            !ToUint32(cx, telem, &targetIndex, &malformed))
         {
             return ExecutionFailed;
         }
 
+        if (malformed) {
+            ReportBadArg(cx, ".prototype.scatter");
+            return ExecutionFailed;
+        }
+
         if (targetIndex >= length) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_SCATTER_BOUNDS);
             return ExecutionFailed;
         }
 
         if (!source->getParallelArrayElement(cx, i, &iv, &elem))
             return ExecutionFailed;
 
@@ -1104,17 +1199,17 @@ ParallelArrayObject::construct(JSContext
     if (args.length() == 1) {
         RootedObject source(cx, NonNullObject(cx, args[0]));
         if (!source)
             return false;
 
         // When using an array value we can only make one dimensional arrays.
         IndexVector dims(cx);
         uint32_t length;
-        if (!dims.resize(1) || !GetLengthProperty(cx, source, &length))
+        if (!dims.resize(1) || !GetLength(cx, source, &length))
             return false;
         dims[0] = length;
 
         RootedObject buffer(cx, NewDenseCopiedArrayWithType(cx, length, source));
         if (!buffer)
             return false;
 
         return create(cx, buffer, 0, dims, args.rval());
@@ -1123,42 +1218,58 @@ ParallelArrayObject::construct(JSContext
     // Second case: initialize using a length/dimensions vector and kernel.
     //
     // If the length is an integer, we build a 1-dimensional parallel
     // array using the kernel.
     //
     // If the length is an array-like object of sizes, the i-th value in the
     // dimension array is the size of the i-th dimension.
     IndexInfo iv(cx);
+    bool malformed;
     if (args[0].isObject()) {
         RootedObject dimObj(cx, &(args[0].toObject()));
-        if (!ArrayLikeToIndexVector(cx, dimObj, iv.dimensions))
+        if (!ArrayLikeToIndexVector(cx, dimObj, iv.dimensions, &malformed))
             return false;
+        if (malformed)
+            return ReportBadLength(cx);
     } else {
-        if (!iv.dimensions.resize(1) || !ToUint32(cx, args[0], &iv.dimensions[0]))
+        if (!iv.dimensions.resize(1))
+            return false;
+
+        if (!ToUint32(cx, args[0], &iv.dimensions[0], &malformed))
             return false;
+        if (malformed) {
+            RootedValue arg0(cx, args[0]);
+            return ReportBadLengthOrArg(cx, arg0);
+        }
     }
 
     // If the first argument wasn't a array-like or had no length, assume
     // empty parallel array, i.e. with shape being [0].
     if (iv.dimensions.length() == 0 && !iv.dimensions.append(0))
         return false;
 
     // Initialize with every dimension packed.
     if (!iv.initialize(iv.dimensions.length()))
         return false;
 
+    // We checked that each individual dimension does not overflow; now check
+    // that the scalar length does not overflow.
+    uint32_t length = iv.scalarLengthOfDimensions();
+    double d = iv.dimensions[0];
+    for (uint32_t i = 1; i < iv.dimensions.length(); i++)
+        d *= iv.dimensions[i];
+    if (d != static_cast<double>(length))
+        return ReportBadLength(cx);
+
     // Extract second argument, the elemental function.
     RootedObject elementalFun(cx, ValueToCallable(cx, &args[1]));
     if (!elementalFun)
         return false;
 
-    // How long the flattened array will be.
-    uint32_t length = iv.scalarLengthOfDimensions();
-
     // Create backing store.
     RootedObject buffer(cx, NewDenseArrayWithType(cx, length));
     if (!buffer)
         return false;
 
 #ifdef DEBUG
     if (args.length() > 2) {
         DebugOptions options;
@@ -1322,18 +1433,23 @@ ParallelArrayObject::scatter(JSContext *
         if (!conflictFun)
             return false;
     }
 
     // The length of the result array is optional and defaults to the length
     // of the source array.
     uint32_t resultLength;
     if (args.length() >= 4) {
-        if (!ToUint32(cx, args[3], &resultLength))
+        bool malformed;
+        if (!ToUint32(cx, args[3], &resultLength, &malformed))
             return false;
+        if (malformed) {
+            RootedValue arg3(cx, args[3]);
+            return ReportBadLengthOrArg(cx, arg3, ".prototype.scatter");
+        }
     } else {
         resultLength = outer;
     }
 
     // Create a destination buffer. Fail if we can't maintain denseness.
     RootedObject buffer(cx, NewDenseArrayWithType(cx, resultLength));
     if (!buffer)
         return false;
@@ -1422,27 +1538,28 @@ ParallelArrayObject::flatten(JSContext *
 
 bool
 ParallelArrayObject::partition(JSContext *cx, CallArgs args)
 {
     if (args.length() < 1)
         return ReportMoreArgsNeeded(cx, "ParallelArray.prototype.partition", "0", "s");
 
     uint32_t newDimension;
-    if (!ToUint32(cx, args[0], &newDimension))
+    bool malformed;
+    if (!ToUint32(cx, args[0], &newDimension, &malformed))
         return false;
+    if (malformed)
+        return ReportBadPartition(cx);
 
     RootedParallelArrayObject obj(cx, as(&args.thisv().toObject()));
 
     // Throw if the outer dimension is not divisible by the new dimension.
     uint32_t outer = obj->outermostDimension();
-    if (newDimension == 0 || outer % newDimension) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_BAD_PARTITION);
-        return false;
-    }
+    if (newDimension == 0 || outer % newDimension)
+        return ReportBadPartition(cx);
 
     IndexVector dims(cx);
     if (!obj->getDimensions(cx, dims))
         return false;
 
     // Set the new outermost dimension to be the quotient of the old outermost
     // dimension and the new dimension.
     if (!dims.insert(dims.begin(), outer / newDimension))
@@ -1464,23 +1581,31 @@ ParallelArrayObject::get(JSContext *cx, 
     RootedParallelArrayObject obj(cx, as(&args.thisv().toObject()));
     RootedObject indicesObj(cx, NonNullObject(cx, args[0]));
     if (!indicesObj)
         return false;
 
     IndexInfo iv(cx);
     if (!iv.initialize(cx, obj, 0))
         return false;
-    if (!ArrayLikeToIndexVector(cx, indicesObj, iv.indices))
+
+    bool malformed;
+    if (!ArrayLikeToIndexVector(cx, indicesObj, iv.indices, &malformed))
         return false;
 
     // Throw if the shape of the index vector is wrong.
     if (iv.indices.length() == 0 || iv.indices.length() > iv.dimensions.length())
         return ReportBadArg(cx, ".prototype.get");
 
+    // Don't throw on overflow, just return undefined.
+    if (malformed) {
+        args.rval().setUndefined();
+        return true;
+    }
+
     return obj->getParallelArrayElement(cx, iv, args.rval());
 }
 
 bool
 ParallelArrayObject::dimensionsGetter(JSContext *cx, CallArgs args)
 {
     RootedObject dimArray(cx, as(&args.thisv().toObject())->dimensionArray());
     RootedObject copy(cx, NewDenseCopiedArray(cx, dimArray->getDenseArrayInitializedLength(),
--- a/js/src/jit-test/tests/parallelarray/comprehension-throws.js
+++ b/js/src/jit-test/tests/parallelarray/comprehension-throws.js
@@ -1,13 +1,25 @@
 load(libdir + "asserts.js");
 
 function buildComprehension() {
   // Throws if elemental fun not callable
   assertThrowsInstanceOf(function () {
     var p = new ParallelArray([2,2], undefined);
   }, TypeError);
   assertThrowsInstanceOf(function () {
+    var p = new ParallelArray(2, /x/);
+  }, TypeError);
+  assertThrowsInstanceOf(function () {
     var p = new ParallelArray(/x/, /x/);
   }, TypeError);
+  assertThrowsInstanceOf(function () {
+    new ParallelArray([0xffffffff + 1], function() { return 0; });
+  }, RangeError);
+  assertThrowsInstanceOf(function () {
+    new ParallelArray(0xffffffff + 1, function() { return 0; });
+  }, RangeError);
+  assertThrowsInstanceOf(function () {
+    new ParallelArray([0xfffff, 0xffff], function() { return 0; });
+  }, RangeError);
 }
 
 buildComprehension();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/constructor-throws.js
@@ -0,0 +1,9 @@
+load(libdir + "asserts.js");
+
+function testThrows() {
+  assertThrowsInstanceOf(function () {
+    new ParallelArray({ length: 0xffffffff + 1 });
+  }, RangeError);
+}
+
+testThrows();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/filter-throws.js
@@ -0,0 +1,11 @@
+load(libdir + "asserts.js");
+
+function testFilterThrows() {
+  var p = new ParallelArray([1,2,3,4,5]);
+
+  assertThrowsInstanceOf(function () {
+    p.filter({ length: 0xffffffff + 1 });
+  }, RangeError);
+}
+
+testFilterThrows();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/overflow-throws.js
@@ -0,0 +1,12 @@
+load(libdir + "asserts.js");
+
+function testOverflow() {
+  assertThrowsInstanceOf(function () {
+    new ParallelArray([0xffffffff + 1], function() { return 0; });
+  }, RangeError);
+  assertThrowsInstanceOf(function () {
+    new ParallelArray({ length: 0xffffffff + 1 });
+  }, RangeError);
+}
+
+testOverflow();
--- a/js/src/jit-test/tests/parallelarray/scatter-throws.js
+++ b/js/src/jit-test/tests/parallelarray/scatter-throws.js
@@ -1,16 +1,30 @@
 load(libdir + "asserts.js");
 
 function testScatterThrows() {
+  var p = new ParallelArray([1,2,3,4,5]);
+
   // Throw on conflict with no resolution function
   assertThrowsInstanceOf(function () {
-    var p = new ParallelArray([1,2,3,4,5]);
     var r = p.scatter([0,1,0,3,4]);
   }, Error);
   // Throw on out of bounds
   assertThrowsInstanceOf(function () {
-    var p = new ParallelArray([1,2,3,4,5]);
     var r = p.scatter([0,1,0,3,11]);
   }, Error);
+
+  assertThrowsInstanceOf(function () {
+    p.scatter([-1,1,0,3,4], 9, function (a,b) { return a+b; }, 10);
+  }, TypeError);
+  assertThrowsInstanceOf(function () {
+    p.scatter([0,1,0,3,4], 9, function (a,b) { return a+b; }, -1);
+  }, RangeError);
+  assertThrowsInstanceOf(function () {
+    p.scatter([0,1,0,3,4], 9, function (a,b) { return a+b; }, 0xffffffff + 1);
+  }, RangeError);
+  assertThrowsInstanceOf(function () {
+    p.scatter({ length: 0xffffffff + 1 }, 9, function (a,b) { return a+b; }, 10);
+  }, RangeError);
+
 }
 
 testScatterThrows();