diff --git a/js/src/builtin/AtomicsObject.cpp b/js/src/builtin/AtomicsObject.cpp index bb2f948d8a..a4ae0736d4 100644 --- a/js/src/builtin/AtomicsObject.cpp +++ b/js/src/builtin/AtomicsObject.cpp @@ -83,8 +83,7 @@ static bool ReportOutOfRange(JSContext* cx) { // Use JSMSG_BAD_INDEX here even if it is generic, since that is - // the message used by ToIntegerIndex for its initial range - // checking. + // the message used by NonStandardToIndex for its initial range checking. JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); return false; } @@ -114,7 +113,7 @@ static bool GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle view, uint32_t* offset) { uint64_t index; - if (!js::ToIntegerIndex(cx, v, &index)) + if (!NonStandardToIndex(cx, v, &index)) return false; if (index >= view->length()) return ReportOutOfRange(cx); diff --git a/js/src/jit-test/tests/asm.js/testBasic.js b/js/src/jit-test/tests/asm.js/testBasic.js index f47a837f3a..aa710eb219 100644 --- a/js/src/jit-test/tests/asm.js/testBasic.js +++ b/js/src/jit-test/tests/asm.js/testBasic.js @@ -69,8 +69,8 @@ assertEq(asmLink(asmCompile('glob', USE_ASM + 'var im=glob.Math.imul; function f var module = asmCompile('glob','i','b', USE_ASM + 'var i32=new glob.Int32Array(b); function f(){} return f'); assertAsmLinkAlwaysFail(module, null, null); -assertAsmLinkAlwaysFail(module, this, null, null); -assertAsmLinkAlwaysFail(module, this, null, null); +assertAsmLinkFail(module, this, null, null); +assertAsmLinkFail(module, this, null, null); assertAsmLinkAlwaysFail(module, this, null, new ArrayBuffer(1)); assertAsmLinkFail(module, this, null, new ArrayBuffer(4)); assertAsmLinkFail(module, this, null, new ArrayBuffer(100)); diff --git a/js/src/jit-test/tests/basic/bug1292858.js b/js/src/jit-test/tests/basic/bug1292858.js index 788eb96523..f905274e17 100644 --- a/js/src/jit-test/tests/basic/bug1292858.js +++ b/js/src/jit-test/tests/basic/bug1292858.js @@ -4,8 +4,8 @@ try { var buf = new Uint8ClampedArray(a); throw new Error("didn't throw"); } catch (e) { - assertEq(e instanceof TypeError, true, - "expected TypeError, instead threw: " + e); + assertEq(e instanceof RangeError, true, + "expected RangeError, instead threw: " + e); caughtInvalidArguments = true; } assertEq(caughtInvalidArguments, true); @@ -20,8 +20,8 @@ while (true) { assertEq(buf.length, 300); } catch (e) { assertEq(a, -1); - assertEq(e instanceof TypeError, true, - "expected TypeError, instead threw: " + e); + assertEq(e instanceof RangeError, true, + "expected RangeError, instead threw: " + e); caughtInvalidArguments = true; break; } @@ -38,8 +38,8 @@ while (true) { assertEq(buf.length, 0); } catch (e) { assertEq(a, -1); - assertEq(e instanceof TypeError, true, - "expected TypeError, instead threw: " + e); + assertEq(e instanceof RangeError, true, + "expected RangeError, instead threw: " + e); caughtInvalidArguments = true; break; } diff --git a/js/src/jit-test/tests/ion/bug1298354.js b/js/src/jit-test/tests/ion/bug1298354.js index 73502df5a7..b443031fe1 100644 --- a/js/src/jit-test/tests/ion/bug1298354.js +++ b/js/src/jit-test/tests/ion/bug1298354.js @@ -3,7 +3,7 @@ new Function(` while (true) { try { - var buf = new Uint8ClampedArray(a); + var buf = new Uint8ClampedArray(-1); } catch (e) { break; } diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 598cf7caf0..ee8a7137b7 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1725,8 +1725,9 @@ js::ToLengthClamped(JSContext*, HandleValue, uint32_t*, bool*); template bool js::ToLengthClamped(ExclusiveContext*, HandleValue, uint32_t*, bool*); +//Non-standard: Used by Atomics. bool -js::ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) +js::NonStandardToIndex(JSContext* cx, HandleValue v, uint64_t* index) { // Fast common case. if (v.isInt32()) { @@ -1776,7 +1777,7 @@ js::ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) // ES2017 draft 7.1.17 ToIndex bool -js::ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) +js::ToIndex(JSContext* cx, JS::HandleValue v, const unsigned errorNumber, uint64_t* index) { // Step 1. if (v.isUndefined()) { @@ -1794,7 +1795,7 @@ js::ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) // 2. Step eliminates < 0, +0 == -0 with SameValueZero. // 3/4. Limit to <= 2^53-1, so everything above should fail. if (integerIndex < 0 || integerIndex >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber); return false; } @@ -1803,6 +1804,12 @@ js::ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) return true; } +bool +js::ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) +{ + return ToIndex(cx, v, JSMSG_BAD_INDEX, index); +} + template bool js_strtod(ExclusiveContext* cx, const CharT* begin, const CharT* end, const CharT** dEnd, diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 7c96bfb652..a2759aa65f 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -292,8 +292,7 @@ ToInteger(JSContext* cx, HandleValue v, double* dp) template MOZ_MUST_USE bool ToLengthClamped(T* cx, HandleValue v, uint32_t* out, bool* overflow); -/* Convert and range check an index value as for DataView, SIMD, and Atomics - * operations, eg ES7 24.2.1.1, DataView's GetViewValue(): +/* Convert and range check an index value for Atomics * * 1. numericIndex = ToNumber(argument) (may throw TypeError) * 2. intIndex = ToInteger(numericIndex) @@ -310,7 +309,7 @@ MOZ_MUST_USE bool ToLengthClamped(T* cx, HandleValue v, uint32_t* out, bool* ove * * The returned index will always be in the range 0 <= *index <= 2^53. */ -MOZ_MUST_USE bool ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); +MOZ_MUST_USE bool NonStandardToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); /* ES2017 draft 7.1.17 ToIndex * @@ -319,6 +318,7 @@ MOZ_MUST_USE bool ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* ind * * The returned index will always be in the range 0 <= *index <= 2^53-1. */ +MOZ_MUST_USE bool ToIndex(JSContext* cx, JS::HandleValue v, const unsigned errorNumber, uint64_t* index); MOZ_MUST_USE bool ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); MOZ_MUST_USE inline bool diff --git a/js/src/tests/ecma_6/TypedArray/constructor-length-too-large.js b/js/src/tests/ecma_6/TypedArray/constructor-length-too-large.js new file mode 100644 index 0000000000..6b6226851b --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-length-too-large.js @@ -0,0 +1,30 @@ +// Test that all TypedArray constructor variants throw a RangeError when +// attempting to create a too large array. + +// The maximum typed array length is (currently) limited to +// `(INT32_MAX / BYTES_PER_ELEMENT) - 1`. + +const INT32_MAX = 2**31 - 1; + +// 22.2.4.2 TypedArray ( length ) +for (let TA of typedArrayConstructors) { + assertThrows(() => new TA(INT32_MAX), RangeError); + assertThrows(() => new TA(INT32_MAX >> Math.log2(TA.BYTES_PER_ELEMENT)), RangeError); +} + +// Test disabled because allocating a 2**30 Int8Array easily leads to OOMs. +// +// 22.2.4.3 TypedArray ( typedArray ) +// const largeInt8Array = new Int8Array(2**30); +// for (let TA of typedArrayConstructors.filter(c => c.BYTES_PER_ELEMENT > 1)) { +// assertThrows(() => new TA(largeInt8Array), RangeError); +// } + +// 22.2.4.4 TypedArray ( object ) +for (let TA of typedArrayConstructors) { + assertThrows(() => new TA({length: INT32_MAX}), RangeError); + assertThrows(() => new TA({length: INT32_MAX >> Math.log2(TA.BYTES_PER_ELEMENT)}), RangeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/js1_8_5/extensions/typedarray.js b/js/src/tests/js1_8_5/extensions/typedarray.js index 885f94551f..c0ac5a35ce 100644 --- a/js/src/tests/js1_8_5/extensions/typedarray.js +++ b/js/src/tests/js1_8_5/extensions/typedarray.js @@ -436,7 +436,7 @@ function test() a = new ArrayBuffer(0x10); checkThrows(() => new Uint32Array(buffer, 4, 0x3FFFFFFF)); - checkThrows(() => new Float32Array(null)); + check(() => new Float32Array(null).length === 0); a = new Uint8Array(0x100); b = Uint32Array.prototype.subarray.apply(a, [0, 0x100]); @@ -496,9 +496,9 @@ function test() check(() => (new Float32Array(Math.sqrt(4))).length == 2); check(() => (new Float32Array({ length: 10 })).length == 10); check(() => (new Float32Array({})).length == 0); - checkThrows(() => new Float32Array("3")); - checkThrows(() => new Float32Array(null)); - checkThrows(() => new Float32Array(undefined)); + check(() => new Float32Array("3").length === 3); + check(() => new Float32Array(null).length === 0); + check(() => new Float32Array(undefined).length === 0); // check that NaN conversions happen correctly with array conversions check(() => (new Int32Array([NaN])[0]) == 0); diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 06e31f208c..7def8c7e1d 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -286,6 +286,7 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) return false; } + // Step 3. RootedObject proto(cx); RootedObject newTarget(cx, &args.newTarget().toObject()); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index de7ce028eb..232fde02d3 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -675,7 +675,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject makeTypedArrayWithTemplate(JSContext* cx, TypedArrayObject* templateObj, int32_t len) { if (len < 0 || uint32_t(len) >= INT32_MAX / sizeof(NativeType)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return nullptr; } @@ -745,18 +745,16 @@ class TypedArrayObjectTemplate : public TypedArrayObject MOZ_ASSERT(args.isConstructing()); RootedObject newTarget(cx, &args.newTarget().toObject()); - /* () or (number) */ - uint32_t len = 0; - if (args.length() == 0 || ValueIsLength(args[0], &len)) - return fromLength(cx, len, newTarget); + /* () or (length) */ + if (args.length() == 0 || !args[0].isObject()) { + uint64_t len; + if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) + return nullptr; - /* (not an object) */ - if (!args[0].isObject()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); - return nullptr; + return fromLength(cx, len, newTarget); } - RootedObject dataObj(cx, &args.get(0).toObject()); + RootedObject dataObj(cx, &args[0].toObject()); /* * (typedArray) @@ -933,8 +931,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject MutableHandle buffer) { if (count >= INT32_MAX / unit) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEED_DIET, - "size and count"); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } uint32_t byteLength = count * unit; @@ -957,17 +954,22 @@ class TypedArrayObjectTemplate : public TypedArrayObject } static JSObject* - fromLength(JSContext* cx, uint32_t nelements, HandleObject newTarget = nullptr) + fromLength(JSContext* cx, uint64_t nelements, HandleObject newTarget = nullptr) { RootedObject proto(cx); if (!GetPrototypeForInstance(cx, newTarget, &proto)) return nullptr; + if (nelements > UINT32_MAX) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); + return nullptr; + } + Rooted buffer(cx); - if (!maybeCreateArrayBuffer(cx, nelements, BYTES_PER_ELEMENT, nullptr, &buffer)) + if (!maybeCreateArrayBuffer(cx, uint32_t(nelements), BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; - return makeInstance(cx, buffer, 0, nelements, proto); + return makeInstance(cx, buffer, 0, uint32_t(nelements), proto); } static bool