Bug 1418195 - Baldr: use stricter checking for [EnforceRange] types (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Tue, 21 Nov 2017 09:50:18 -0600
changeset 393093 5a91b80c925e6564fe0fbb968fda984b53ffc1e5
parent 393092 1e9296f9dd46862c8ecb9a7723ade2b0dc00a8d7
child 393094 65d596a31eb90905b4f7b97cd16cb23089da594d
push id32954
push usershindli@mozilla.com
push dateWed, 22 Nov 2017 21:30:30 +0000
treeherdermozilla-central@960f50c2e0a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1418195
milestone59.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 1418195 - Baldr: use stricter checking for [EnforceRange] types (r=bbouvier) MozReview-Commit-ID: BfXf1VVjyI5
js/src/jit-test/tests/wasm/regress/proxy-has-trap-table.js
js/src/jit-test/tests/wasm/spec/jsapi.js
js/src/wasm/WasmJS.cpp
--- a/js/src/jit-test/tests/wasm/regress/proxy-has-trap-table.js
+++ b/js/src/jit-test/tests/wasm/regress/proxy-has-trap-table.js
@@ -1,10 +1,11 @@
 assertErrorMessage(() => {
     var desc = {
         element: "anyfunc",
+        initial: 1
     };
     var proxy = new Proxy({}, {
         has: true
     });
     Object.setPrototypeOf(desc, proxy);
     let table = new WebAssembly.Table(desc);
 }, TypeError, /proxy handler's has trap/);
--- a/js/src/jit-test/tests/wasm/spec/jsapi.js
+++ b/js/src/jit-test/tests/wasm/spec/jsapi.js
@@ -413,16 +413,20 @@ test(() => {
 test(() => {
     const memoryDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Memory');
     assert_equals(Memory, memoryDesc.value);
     assert_equals(Memory.length, 1);
     assert_equals(Memory.name, "Memory");
     assertThrows(() => Memory(), TypeError);
     assertThrows(() => new Memory(1), TypeError);
     assertThrows(() => new Memory({initial:{valueOf() { throw new Error("here")}}}), Error);
+    assertThrows(() => new Memory({}), RangeError);
+    assertThrows(() => new Memory({initial:NaN}), RangeError);
+    assertThrows(() => new Memory({initial:undefined}), RangeError);
+    assertThrows(() => new Memory({initial:"abc"}), RangeError);
     assertThrows(() => new Memory({initial:-1}), RangeError);
     assertThrows(() => new Memory({initial:Math.pow(2,32)}), RangeError);
     assertThrows(() => new Memory({initial:1, maximum: Math.pow(2,32)/Math.pow(2,14) }), RangeError);
     assertThrows(() => new Memory({initial:2, maximum:1 }), RangeError);
     assertThrows(() => new Memory({maximum: -1 }), RangeError);
     assert_equals(new Memory({initial:1}) instanceof Memory, true);
     assert_equals(new Memory({initial:1.5}).buffer.byteLength, WasmPage);
 }, "'WebAssembly.Memory' constructor function");
@@ -475,16 +479,20 @@ test(() => {
 }, "'WebAssembly.Memory.prototype.grow' data property");
 
 test(() => {
     const memGrowDesc = Object.getOwnPropertyDescriptor(memoryProto, 'grow');
     const memGrow = memGrowDesc.value;
     assert_equals(memGrow.length, 1);
     assertThrows(() => memGrow.call(), TypeError);
     assertThrows(() => memGrow.call({}), TypeError);
+    assertThrows(() => memGrow.call(mem1), RangeError);
+    assertThrows(() => memGrow.call(mem1, NaN), RangeError);
+    assertThrows(() => memGrow.call(mem1, undefined), RangeError);
+    assertThrows(() => memGrow.call(mem1, "abc"), RangeError);
     assertThrows(() => memGrow.call(mem1, -1), RangeError);
     assertThrows(() => memGrow.call(mem1, Math.pow(2,32)), RangeError);
     var mem = new Memory({initial:1, maximum:2});
     var buf = mem.buffer;
     assert_equals(buf.byteLength, WasmPage);
     assert_equals(mem.grow(0), 1);
     assert_equals(buf !== mem.buffer, true);
     assert_equals(buf.byteLength, 0);
@@ -514,16 +522,20 @@ test(() => {
     assert_equals(Table.length, 1);
     assert_equals(Table.name, "Table");
     assertThrows(() => Table(), TypeError);
     assertThrows(() => new Table(1), TypeError);
     assertThrows(() => new Table({initial:1, element:1}), TypeError);
     assertThrows(() => new Table({initial:1, element:"any"}), TypeError);
     assertThrows(() => new Table({initial:1, element:{valueOf() { return "anyfunc" }}}), TypeError);
     assertThrows(() => new Table({initial:{valueOf() { throw new Error("here")}}, element:"anyfunc"}), Error);
+    assertThrows(() => new Table({element:"anyfunc"}), RangeError);
+    assertThrows(() => new Table({initial:NaN, element:"anyfunc"}), RangeError);
+    assertThrows(() => new Table({initial:undefined, element:"anyfunc"}), RangeError);
+    assertThrows(() => new Table({initial:"abc", element:"anyfunc"}), RangeError);
     assertThrows(() => new Table({initial:-1, element:"anyfunc"}), RangeError);
     assertThrows(() => new Table({initial:Math.pow(2,32), element:"anyfunc"}), RangeError);
     assertThrows(() => new Table({initial:2, maximum:1, element:"anyfunc"}), RangeError);
     assertThrows(() => new Table({initial:2, maximum:Math.pow(2,32), element:"anyfunc"}), RangeError);
     assert_equals(new Table({initial:1, element:"anyfunc"}) instanceof Table, true);
     assert_equals(new Table({initial:1.5, element:"anyfunc"}) instanceof Table, true);
     assert_equals(new Table({initial:1, maximum:1.5, element:"anyfunc"}) instanceof Table, true);
     assert_equals(new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}) instanceof Table, true);
@@ -581,16 +593,20 @@ test(() => {
     const getDesc = Object.getOwnPropertyDescriptor(tableProto, 'get');
     const get = getDesc.value;
     assert_equals(get.length, 1);
     assertThrows(() => get.call(), TypeError);
     assertThrows(() => get.call({}), TypeError);
     assert_equals(get.call(tbl1, 0), null);
     assert_equals(get.call(tbl1, 1), null);
     assert_equals(get.call(tbl1, 1.5), null);
+    assertThrows(() => get.call(tbl1), RangeError);
+    assertThrows(() => get.call(tbl1, NaN), RangeError);
+    assertThrows(() => get.call(tbl1, undefined), RangeError);
+    assertThrows(() => get.call(tbl1, "abc"), RangeError);
     assertThrows(() => get.call(tbl1, 2), RangeError);
     assertThrows(() => get.call(tbl1, 2.5), RangeError);
     assertThrows(() => get.call(tbl1, -1), RangeError);
     assertThrows(() => get.call(tbl1, Math.pow(2,33)), RangeError);
     assertThrows(() => get.call(tbl1, {valueOf() { throw new Error("hi") }}), Error);
 }, "'WebAssembly.Table.prototype.get' method");
 
 test(() => {
@@ -602,16 +618,19 @@ test(() => {
 
 test(() => {
     const setDesc = Object.getOwnPropertyDescriptor(tableProto, 'set');
     const set = setDesc.value;
     assert_equals(set.length, 2);
     assertThrows(() => set.call(), TypeError);
     assertThrows(() => set.call({}), TypeError);
     assertThrows(() => set.call(tbl1, 0), TypeError);
+    assertThrows(() => set.call(tbl1, NaN, null), RangeError);
+    assertThrows(() => set.call(tbl1, undefined, null), RangeError);
+    assertThrows(() => set.call(tbl1, "abc", null), RangeError);
     assertThrows(() => set.call(tbl1, 2, null), RangeError);
     assertThrows(() => set.call(tbl1, -1, null), RangeError);
     assertThrows(() => set.call(tbl1, Math.pow(2,33), null), RangeError);
     assertThrows(() => set.call(tbl1, 0, undefined), TypeError);
     assertThrows(() => set.call(tbl1, 0, {}), TypeError);
     assertThrows(() => set.call(tbl1, 0, function() {}), TypeError);
     assertThrows(() => set.call(tbl1, 0, Math.sin), TypeError);
     assertThrows(() => set.call(tbl1, {valueOf() { throw Error("hai") }}, null), Error);
@@ -627,16 +646,20 @@ test(() => {
 }, "'WebAssembly.Table.prototype.grow' data property");
 
 test(() => {
     const tblGrowDesc = Object.getOwnPropertyDescriptor(tableProto, 'grow');
     const tblGrow = tblGrowDesc.value;
     assert_equals(tblGrow.length, 1);
     assertThrows(() => tblGrow.call(), TypeError);
     assertThrows(() => tblGrow.call({}), TypeError);
+    assertThrows(() => tblGrow.call(tbl1), RangeError);
+    assertThrows(() => tblGrow.call(tbl1, NaN), RangeError);
+    assertThrows(() => tblGrow.call(tbl1, undefined), RangeError);
+    assertThrows(() => tblGrow.call(tbl1, "abc"), RangeError);
     assertThrows(() => tblGrow.call(tbl1, -1), RangeError);
     assertThrows(() => tblGrow.call(tbl1, Math.pow(2,32)), RangeError);
     var tbl = new Table({element:"anyfunc", initial:1, maximum:2});
     assert_equals(tbl.length, 1);
     assert_equals(tbl.grow(0), 1);
     assert_equals(tbl.length, 1);
     assert_equals(tbl.grow(1), 1);
     assert_equals(tbl.length, 2);
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -279,48 +279,66 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
         return false;
 
     return module->instantiate(cx, funcs, table, memory, globals, nullptr, instanceObj);
 }
 
 // ============================================================================
 // Common functions
 
+// '[EnforceRange] unsigned long' types are coerced with
+//    ConvertToInt(v, 32, 'unsigned')
+// defined in Web IDL Section 3.2.4.9.
 static bool
-ToNonWrappingUint32(JSContext* cx, HandleValue v, uint32_t max, const char* kind, const char* noun,
-                    uint32_t* u32)
+EnforceRangeU32(JSContext* cx, HandleValue v, uint32_t max, const char* kind, const char* noun,
+                uint32_t* u32)
 {
-    double dbl;
-    if (!ToInteger(cx, v, &dbl))
+    // Step 4.
+    double x;
+    if (!ToNumber(cx, v, &x))
         return false;
 
-    if (dbl < 0 || dbl > max) {
+    // Step 5.
+    if (mozilla::IsNegativeZero(x))
+        x = 0.0;
+
+    // Step 6.1.
+    if (!mozilla::IsFinite(x)) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32, kind, noun);
         return false;
     }
 
-    *u32 = uint32_t(dbl);
-    MOZ_ASSERT(double(*u32) == dbl);
+    // Step 6.2.
+    x = JS::ToInteger(x);
+
+    // Step 6.3, allowing caller to supply a more restrictive uint32_t max.
+    if (x < 0 || x > double(max)) {
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32, kind, noun);
+        return false;
+    }
+
+    *u32 = uint32_t(x);
+    MOZ_ASSERT(double(*u32) == x);
     return true;
 }
 
 static bool
 GetLimits(JSContext* cx, HandleObject obj, uint32_t maxInitial, uint32_t maxMaximum,
           const char* kind, Limits* limits)
 {
     JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
     if (!initialAtom)
         return false;
     RootedId initialId(cx, AtomToId(initialAtom));
 
     RootedValue initialVal(cx);
     if (!GetProperty(cx, obj, obj, initialId, &initialVal))
         return false;
 
-    if (!ToNonWrappingUint32(cx, initialVal, maxInitial, kind, "initial size", &limits->initial))
+    if (!EnforceRangeU32(cx, initialVal, maxInitial, kind, "initial size", &limits->initial))
         return false;
 
     JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
     if (!maximumAtom)
         return false;
     RootedId maximumId(cx, AtomToId(maximumAtom));
 
     bool found;
@@ -328,17 +346,17 @@ GetLimits(JSContext* cx, HandleObject ob
         return false;
 
     if (found) {
         RootedValue maxVal(cx);
         if (!GetProperty(cx, obj, obj, maximumId, &maxVal))
             return false;
 
         limits->maximum.emplace();
-        if (!ToNonWrappingUint32(cx, maxVal, maxMaximum, kind, "maximum size", limits->maximum.ptr()))
+        if (!EnforceRangeU32(cx, maxVal, maxMaximum, kind, "maximum size", limits->maximum.ptr()))
             return false;
 
         if (limits->initial > *limits->maximum) {
             JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32,
                                      kind, "maximum size");
             return false;
         }
     }
@@ -1280,17 +1298,17 @@ const JSPropertySpec WasmMemoryObject::p
 };
 
 /* static */ bool
 WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args)
 {
     RootedWasmMemoryObject memory(cx, &args.thisv().toObject().as<WasmMemoryObject>());
 
     uint32_t delta;
-    if (!ToNonWrappingUint32(cx, args.get(0), UINT32_MAX, "Memory", "grow delta", &delta))
+    if (!EnforceRangeU32(cx, args.get(0), UINT32_MAX, "Memory", "grow delta", &delta))
         return false;
 
     uint32_t ret = grow(memory, delta, cx);
 
     if (ret == uint32_t(-1)) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "memory");
         return false;
     }
@@ -1581,17 +1599,17 @@ const JSPropertySpec WasmTableObject::pr
 
 /* static */ bool
 WasmTableObject::getImpl(JSContext* cx, const CallArgs& args)
 {
     RootedWasmTableObject tableObj(cx, &args.thisv().toObject().as<WasmTableObject>());
     const Table& table = tableObj->table();
 
     uint32_t index;
-    if (!ToNonWrappingUint32(cx, args.get(0), table.length() - 1, "Table", "get index", &index))
+    if (!EnforceRangeU32(cx, args.get(0), table.length() - 1, "Table", "get index", &index))
         return false;
 
     ExternalTableElem& elem = table.externalArray()[index];
     if (!elem.code) {
         args.rval().setNull();
         return true;
     }
 
@@ -1620,17 +1638,17 @@ WasmTableObject::setImpl(JSContext* cx, 
 {
     RootedWasmTableObject tableObj(cx, &args.thisv().toObject().as<WasmTableObject>());
     Table& table = tableObj->table();
 
     if (!args.requireAtLeast(cx, "set", 2))
         return false;
 
     uint32_t index;
-    if (!ToNonWrappingUint32(cx, args.get(0), table.length() - 1, "Table", "set index", &index))
+    if (!EnforceRangeU32(cx, args.get(0), table.length() - 1, "Table", "set index", &index))
         return false;
 
     RootedFunction value(cx);
     if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
         return false;
     }
 
@@ -1666,17 +1684,17 @@ WasmTableObject::set(JSContext* cx, unsi
 }
 
 /* static */ bool
 WasmTableObject::growImpl(JSContext* cx, const CallArgs& args)
 {
     RootedWasmTableObject table(cx, &args.thisv().toObject().as<WasmTableObject>());
 
     uint32_t delta;
-    if (!ToNonWrappingUint32(cx, args.get(0), UINT32_MAX, "Table", "grow delta", &delta))
+    if (!EnforceRangeU32(cx, args.get(0), UINT32_MAX, "Table", "grow delta", &delta))
         return false;
 
     uint32_t ret = table->table().grow(delta, cx);
 
     if (ret == uint32_t(-1)) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "table");
         return false;
     }