%TypedArray%.prototype.indexOf is coercing non-integers or non-floats to numbers...
authorkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Jul 2016 20:58:52 +0000 (20:58 +0000)
committerkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Jul 2016 20:58:52 +0000 (20:58 +0000)
https://bugs.webkit.org/show_bug.cgi?id=159400

Reviewed by Geoffrey Garen.

This patch fixes coercion of non-numbers in indexOf/lastIndexOf.
Additionally, this patch fixes an issue with includes where it
would not check that the buffer remained non-neutered after
calling the toInteger() function. Lastly, some extra release
asserts have been added in some places to inform us of any issues
in the future.

Additionally, this patch changes bool toNativeFromDouble to
Optional<Type> toNativeFromDoubleWithoutCoercion. This makes it a
little clearer what the function does and also removes the return
argument. The only behavior change is that the function no longer
coerces non-numbers into numbers. That behavior was unused (maybe
unintended), however.

* runtime/JSGenericTypedArrayView.h:
(JSC::JSGenericTypedArrayView::toAdaptorNativeFromValueWithoutCoercion):
(JSC::JSGenericTypedArrayView::sort):
(JSC::JSGenericTypedArrayView::toAdaptorNativeFromValue): Deleted.
* runtime/JSGenericTypedArrayViewPrototypeFunctions.h:
(JSC::genericTypedArrayViewProtoFuncCopyWithin):
(JSC::genericTypedArrayViewProtoFuncIncludes):
(JSC::genericTypedArrayViewProtoFuncIndexOf):
(JSC::genericTypedArrayViewProtoFuncLastIndexOf):
* runtime/ToNativeFromValue.h:
(JSC::toNativeFromValueWithoutCoercion):
(JSC::toNativeFromValue): Deleted.
* runtime/TypedArrayAdaptors.h:
(JSC::IntegralTypedArrayAdaptor::toNativeFromInt32WithoutCoercion):
(JSC::IntegralTypedArrayAdaptor::toNativeFromUint32WithoutCoercion):
(JSC::IntegralTypedArrayAdaptor::toNativeFromDoubleWithoutCoercion):
(JSC::FloatTypedArrayAdaptor::toNativeFromInt32WithoutCoercion):
(JSC::FloatTypedArrayAdaptor::toNativeFromDoubleWithoutCoercion):
(JSC::Uint8ClampedAdaptor::toNativeFromInt32WithoutCoercion):
(JSC::Uint8ClampedAdaptor::toNativeFromDoubleWithoutCoercion):
(JSC::IntegralTypedArrayAdaptor::toNativeFromInt32): Deleted.
(JSC::IntegralTypedArrayAdaptor::toNativeFromUint32): Deleted.
(JSC::IntegralTypedArrayAdaptor::toNativeFromDouble): Deleted.
(JSC::FloatTypedArrayAdaptor::toNativeFromInt32): Deleted.
(JSC::FloatTypedArrayAdaptor::toNativeFromDouble): Deleted.
(JSC::Uint8ClampedAdaptor::toNativeFromInt32): Deleted.
(JSC::Uint8ClampedAdaptor::toNativeFromDouble): Deleted.
* tests/stress/resources/typedarray-test-helper-functions.js:
* tests/stress/typedarray-functions-with-neutered.js:
(callWithArgs):
* tests/stress/typedarray-includes.js: Added.
* tests/stress/typedarray-indexOf.js:
* tests/stress/typedarray-lastIndexOf.js:

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

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/JSGenericTypedArrayView.h
Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeFunctions.h
Source/JavaScriptCore/runtime/ToNativeFromValue.h
Source/JavaScriptCore/runtime/TypedArrayAdaptors.h
Source/JavaScriptCore/tests/stress/resources/typedarray-test-helper-functions.js
Source/JavaScriptCore/tests/stress/typedarray-functions-with-neutered.js
Source/JavaScriptCore/tests/stress/typedarray-includes.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/typedarray-indexOf.js
Source/JavaScriptCore/tests/stress/typedarray-lastIndexOf.js

index 1f7a7de..ab1b242 100644 (file)
@@ -1,3 +1,58 @@
+2016-07-15  Keith Miller  <keith_miller@apple.com>
+
+        %TypedArray%.prototype.indexOf is coercing non-integers or non-floats to numbers wrongly
+        https://bugs.webkit.org/show_bug.cgi?id=159400
+
+        Reviewed by Geoffrey Garen.
+
+        This patch fixes coercion of non-numbers in indexOf/lastIndexOf.
+        Additionally, this patch fixes an issue with includes where it
+        would not check that the buffer remained non-neutered after
+        calling the toInteger() function. Lastly, some extra release
+        asserts have been added in some places to inform us of any issues
+        in the future.
+
+        Additionally, this patch changes bool toNativeFromDouble to
+        Optional<Type> toNativeFromDoubleWithoutCoercion. This makes it a
+        little clearer what the function does and also removes the return
+        argument. The only behavior change is that the function no longer
+        coerces non-numbers into numbers. That behavior was unused (maybe
+        unintended), however.
+
+        * runtime/JSGenericTypedArrayView.h:
+        (JSC::JSGenericTypedArrayView::toAdaptorNativeFromValueWithoutCoercion):
+        (JSC::JSGenericTypedArrayView::sort):
+        (JSC::JSGenericTypedArrayView::toAdaptorNativeFromValue): Deleted.
+        * runtime/JSGenericTypedArrayViewPrototypeFunctions.h:
+        (JSC::genericTypedArrayViewProtoFuncCopyWithin):
+        (JSC::genericTypedArrayViewProtoFuncIncludes):
+        (JSC::genericTypedArrayViewProtoFuncIndexOf):
+        (JSC::genericTypedArrayViewProtoFuncLastIndexOf):
+        * runtime/ToNativeFromValue.h:
+        (JSC::toNativeFromValueWithoutCoercion):
+        (JSC::toNativeFromValue): Deleted.
+        * runtime/TypedArrayAdaptors.h:
+        (JSC::IntegralTypedArrayAdaptor::toNativeFromInt32WithoutCoercion):
+        (JSC::IntegralTypedArrayAdaptor::toNativeFromUint32WithoutCoercion):
+        (JSC::IntegralTypedArrayAdaptor::toNativeFromDoubleWithoutCoercion):
+        (JSC::FloatTypedArrayAdaptor::toNativeFromInt32WithoutCoercion):
+        (JSC::FloatTypedArrayAdaptor::toNativeFromDoubleWithoutCoercion):
+        (JSC::Uint8ClampedAdaptor::toNativeFromInt32WithoutCoercion):
+        (JSC::Uint8ClampedAdaptor::toNativeFromDoubleWithoutCoercion):
+        (JSC::IntegralTypedArrayAdaptor::toNativeFromInt32): Deleted.
+        (JSC::IntegralTypedArrayAdaptor::toNativeFromUint32): Deleted.
+        (JSC::IntegralTypedArrayAdaptor::toNativeFromDouble): Deleted.
+        (JSC::FloatTypedArrayAdaptor::toNativeFromInt32): Deleted.
+        (JSC::FloatTypedArrayAdaptor::toNativeFromDouble): Deleted.
+        (JSC::Uint8ClampedAdaptor::toNativeFromInt32): Deleted.
+        (JSC::Uint8ClampedAdaptor::toNativeFromDouble): Deleted.
+        * tests/stress/resources/typedarray-test-helper-functions.js:
+        * tests/stress/typedarray-functions-with-neutered.js:
+        (callWithArgs):
+        * tests/stress/typedarray-includes.js: Added.
+        * tests/stress/typedarray-indexOf.js:
+        * tests/stress/typedarray-lastIndexOf.js:
+
 2016-07-15  Csaba Osztrogon√°c  <ossy@webkit.org>
 
         Add new functions to ARMAssembler after r202214
index 74f73d0..b06f554 100644 (file)
@@ -186,10 +186,11 @@ public:
 
     static ElementType toAdaptorNativeFromValue(ExecState* exec, JSValue jsValue) { return toNativeFromValue<Adaptor>(exec, jsValue); }
 
-    static bool toAdaptorNativeFromValue(ExecState* exec, JSValue jsValue, ElementType& result) { return toNativeFromValue<Adaptor>(exec, jsValue, result); }
+    static Optional<ElementType> toAdaptorNativeFromValueWithoutCoercion(JSValue jsValue) { return toNativeFromValueWithoutCoercion<Adaptor>(jsValue); }
 
     void sort()
     {
+        RELEASE_ASSERT(!isNeutered());
         switch (Adaptor::typeValue) {
         case TypeFloat32:
             sortFloat<int32_t>();
index 3128e14..a88677b 100644 (file)
@@ -154,14 +154,14 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncCopyWithin(ExecState*
     if (vm.exception())
         return encodedJSValue();
 
-    if (thisObject->isNeutered())
-        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
-
     if (final < from)
         return JSValue::encode(exec->thisValue());
 
     long count = std::min(length - std::max(to, from), final - from);
 
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
+
     typename ViewClass::ElementType* array = thisObject->typedVector();
     memmove(array + to, array + from, count * thisObject->elementSize);
 
@@ -171,6 +171,7 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncCopyWithin(ExecState*
 template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIncludes(ExecState* exec)
 {
+    VM& vm = exec->vm();
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
@@ -183,26 +184,28 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIncludes(ExecState* e
     JSValue valueToFind = exec->argument(0);
 
     unsigned index = argumentClampedIndexFromStartOrEnd(exec, 1, length);
+    if (vm.exception())
+        return JSValue::encode(jsUndefined());
 
-    if (!valueToFind.isNumber())
-        return JSValue::encode(jsBoolean(false));
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     typename ViewClass::ElementType* array = thisObject->typedVector();
-    typename ViewClass::ElementType target;
-    if (!ViewClass::toAdaptorNativeFromValue(exec, valueToFind, target))
+    auto targetOption = ViewClass::toAdaptorNativeFromValueWithoutCoercion(valueToFind);
+    if (!targetOption)
         return JSValue::encode(jsBoolean(false));
 
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
+    ASSERT(!vm.exception());
+    RELEASE_ASSERT(!thisObject->isNeutered());
 
-    if (std::isnan(static_cast<double>(target))) {
+    if (std::isnan(static_cast<double>(*targetOption))) {
         for (; index < length; ++index) {
             if (std::isnan(static_cast<double>(array[index])))
                 return JSValue::encode(jsBoolean(true));
         }
     } else {
         for (; index < length; ++index) {
-            if (array[index] == target)
+            if (array[index] == targetOption)
                 return JSValue::encode(jsBoolean(true));
         }
     }
@@ -233,12 +236,14 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIndexOf(ExecState* ex
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     typename ViewClass::ElementType* array = thisObject->typedVector();
-    typename ViewClass::ElementType target = ViewClass::toAdaptorNativeFromValue(exec, valueToFind);
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
+    auto targetOption = ViewClass::toAdaptorNativeFromValueWithoutCoercion(valueToFind);
+    if (!targetOption)
+        return JSValue::encode(jsNumber(-1));
+    ASSERT(!vm.exception());
+    RELEASE_ASSERT(!thisObject->isNeutered());
 
     for (; index < length; ++index) {
-        if (array[index] == target)
+        if (array[index] == targetOption)
             return JSValue::encode(jsNumber(index));
     }
 
@@ -287,6 +292,7 @@ template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncLastIndexOf(ExecState* exec)
 {
     // 22.2.3.16
+    VM& vm = exec->vm();
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
@@ -311,16 +317,22 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncLastIndexOf(ExecState
             index = static_cast<unsigned>(fromDouble);
     }
 
+    if (vm.exception())
+        return JSValue::encode(JSValue());
+
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
+    auto targetOption = ViewClass::toAdaptorNativeFromValueWithoutCoercion(valueToFind);
+    if (!targetOption)
+        return JSValue::encode(jsNumber(-1));
+
     typename ViewClass::ElementType* array = thisObject->typedVector();
-    typename ViewClass::ElementType target = ViewClass::toAdaptorNativeFromValue(exec, valueToFind);
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
+    ASSERT(!vm.exception());
+    RELEASE_ASSERT(!thisObject->isNeutered());
 
     for (; index >= 0; --index) {
-        if (array[index] == target)
+        if (array[index] == targetOption)
             return JSValue::encode(jsNumber(index));
     }
 
index b9b9ad7..72fa812 100644 (file)
@@ -49,13 +49,13 @@ typename Adaptor::Type toNativeFromValue(ExecState* exec, JSValue value)
 }
 
 template<typename Adaptor>
-bool toNativeFromValue(ExecState* exec, JSValue value, typename Adaptor::Type& result)
+Optional<typename Adaptor::Type> toNativeFromValueWithoutCoercion(JSValue value)
 {
+    if (!value.isNumber())
+        return Nullopt;
     if (value.isInt32())
-        return Adaptor::toNativeFromInt32(value.asInt32(), result);
-    if (value.isNumber())
-        return Adaptor::toNativeFromDouble(value.asDouble(), result);
-    return Adaptor::toNativeFromDouble(value.toNumber(exec), result);
+        return Adaptor::toNativeFromInt32WithoutCoercion(value.asInt32());
+    return Adaptor::toNativeFromDoubleWithoutCoercion(value.asDouble());
 }
 
 } // namespace JSC
index ac8e0d6..be22a63 100644 (file)
@@ -79,36 +79,31 @@ struct IntegralTypedArrayAdaptor {
         return OtherAdaptor::toNativeFromInt32(value);
     }
 
-    static bool toNativeFromInt32(int32_t value, Type& result)
+    static Optional<Type> toNativeFromInt32WithoutCoercion(int32_t value)
     {
         if ((value >= 0 && static_cast<uint32_t>(value) > static_cast<uint32_t>(maxValue)) || value < static_cast<int32_t>(minValue))
-            return false;
-
-        result = static_cast<Type>(value);
-
-        return true;
+            return Nullopt;
+        return static_cast<Type>(value);
     }
 
-    static bool toNativeFromUint32(uint32_t value, Type& result)
+    static Optional<Type> toNativeFromUint32WithoutCoercion(uint32_t value)
     {
         if (value > static_cast<uint32_t>(maxValue))
-            return false;
-
-        result = static_cast<Type>(value);
+            return Nullopt;
 
-        return true;
+        return static_cast<Type>(value);
     }
 
-    static bool toNativeFromDouble(double value, Type& result)
+    static Optional<Type> toNativeFromDoubleWithoutCoercion(double value)
     {
         Type integer = static_cast<Type>(value);
         if (static_cast<double>(integer) != value)
-            return false;
+            return Nullopt;
 
         if (value < 0)
-            return toNativeFromInt32(static_cast<int32_t>(value), result);
+            return toNativeFromInt32WithoutCoercion(static_cast<int32_t>(value));
         
-        return toNativeFromUint32(static_cast<uint32_t>(value), result);
+        return toNativeFromUint32WithoutCoercion(static_cast<uint32_t>(value));
     }
 };
 
@@ -154,29 +149,25 @@ struct FloatTypedArrayAdaptor {
         return OtherAdaptor::toNativeFromDouble(value);
     }
 
-    static bool toNativeFromInt32(int32_t value, Type& result)
+    static Optional<Type> toNativeFromInt32WithoutCoercion(int32_t value)
     {
-        result = static_cast<Type>(value);
-        return true;
+        return static_cast<Type>(value);
     }
 
-    static Type toNativeFromDouble(double value, Type& result)
+    static Optional<Type> toNativeFromDoubleWithoutCoercion(double value)
     {
-        if (std::isnan(value) || std::isinf(value)) {
-            result = static_cast<Type>(value);
-            return true;
-        }
+        if (std::isnan(value) || std::isinf(value))
+            return static_cast<Type>(value);
 
         Type valueResult = static_cast<Type>(value);
 
         if (static_cast<double>(valueResult) != value)
-            return false;
+            return Nullopt;
 
         if (value < minValue || value > maxValue)
-            return false;
+            return Nullopt;
 
-        result = valueResult;
-        return true;
+        return valueResult;
     }
 };
 
@@ -264,24 +255,21 @@ struct Uint8ClampedAdaptor {
         return OtherAdaptor::toNativeFromInt32(value);
     }
     
-    static bool toNativeFromInt32(int32_t value, Type& result)
+    static Optional<Type> toNativeFromInt32WithoutCoercion(int32_t value)
     {
         if (value > maxValue || value < minValue)
-            return false;
+            return Nullopt;
 
-        result = static_cast<Type>(value);
-
-        return true;
+        return static_cast<Type>(value);
     }
 
-    static bool toNativeFromDouble(double value, uint8_t& result)
+    static Optional<Type> toNativeFromDoubleWithoutCoercion(double value)
     {
         uint8_t integer = static_cast<uint8_t>(value);
         if (static_cast<double>(integer) != value)
-            return false;
+            return Nullopt;
 
-        result = integer;
-        return true;
+        return integer;
     }
 
 private:
index 7cd174b..c915b26 100644 (file)
@@ -4,6 +4,8 @@ var typedArrays = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16A
 
 var signedArrays = [Int8Array, Int16Array, Int32Array, Float32Array, Float64Array];
 
+var intArrays = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array];
+
 var floatArrays = [Float32Array, Float64Array];
 
 function forEachTypedArray(constructors, testFunction /* , initialValues */ ) {
index e39fb41..0e66ec9 100644 (file)
@@ -83,6 +83,7 @@ prototypeFunctions = [
     { func:proto.findIndex, args:["func"] },
     { func:proto.forEach, args:["func"] },
     { func:proto.indexOf, args:["na", "prim"] },
+    { func:proto.includes, args:["na", "prim"] },
     { func:proto.join, args:["prim"] },
     { func:proto.lastIndexOf, args:["na", "prim"] },
     { func:proto.map, args:["func"] },
@@ -105,7 +106,7 @@ function defaultForArg(arg, argNum)
     return argNum;
 }
 
-function callWithArgs(func, array, args) {
+function callWithArgs(func, array, args, argNum) {
     let failed = true;
     try {
         func.call(array, ...args);
@@ -115,7 +116,7 @@ function callWithArgs(func, array, args) {
         failed = false;
     }
     if (failed)
-        throw new Error([func, args]);
+        throw new Error([func, argNum]);
 }
 
 
@@ -135,13 +136,13 @@ function checkArgumentsForType(func, args, constructor) {
                 transferArrayBuffer(array.buffer);
                 return func === array.every ? 1 : 0;
             };
-            callWithArgs(func, array, callArgs);
+            callWithArgs(func, array, callArgs, argNum);
         } else if (arg === "prim") {
             callArgs[argNum] = { [Symbol.toPrimitive]() {
                 transferArrayBuffer(array.buffer);
                 return argNum;
             } };
-            callWithArgs(func, array, callArgs);
+            callWithArgs(func, array, callArgs, argNum);
         } else if (arg === "array") {
             callArgs[argNum] = new Array(4);
             callArgs[argNum].fill(2);
@@ -150,7 +151,7 @@ function checkArgumentsForType(func, args, constructor) {
                 return 1;
             } };
             Object.defineProperty(callArgs[argNum], 1, desc);
-            callWithArgs(func, array, callArgs);
+            callWithArgs(func, array, callArgs, argNum);
         } else
             throw new Error(arg);
     }
diff --git a/Source/JavaScriptCore/tests/stress/typedarray-includes.js b/Source/JavaScriptCore/tests/stress/typedarray-includes.js
new file mode 100644 (file)
index 0000000..d6c03af
--- /dev/null
@@ -0,0 +1,10 @@
+load("resources/typedarray-test-helper-functions.js");
+
+for (constructor of typedArrays) {
+    let a = new constructor(10);
+    passed = true;
+    result = a.includes({ valueOf() { passed = false; return 1; } });
+    shouldBeTrue("passed");
+    shouldBeFalse("result");
+}
+finishJSTest();
index 19273c8..7dd5f00 100644 (file)
@@ -28,4 +28,40 @@ shouldBeTrue("testPrototypeFunction('indexOf', '(2, 0)', array, 0)");
 shouldBeTrue("testPrototypeFunction('indexOf', '(2, -1)', array, 3)");
 shouldBeTrue("testPrototypeFunction('indexOf', '(2, -2)', array, 3)");
 debug("");
+
+debug("Check object coersion");
+for (constructor of typedArrays) {
+    a = new constructor([0,2,3]);
+    passed = true;
+
+    shouldBe("a.indexOf({ valueOf() { passed = false; return 1; }})", "-1");
+    shouldBeTrue("passed");
+    shouldBe("a.indexOf(3, {valueOf: () => -1})", "2");
+
+    // test we don't coerce non-native values
+    shouldBe("a.indexOf(\"abc\")", "-1");
+    shouldBe("a.indexOf(null)", "-1");
+    shouldBe("a.indexOf(undefined)", "-1");
+    shouldBe("a.indexOf({1: ''})", "-1");
+    shouldBe("a.indexOf(\"\")", "-1");
+}
+
+
+for (constructor of intArrays) {
+    a = new constructor([0,2,3]);
+
+    shouldBe("a.indexOf(2.0)", "1");
+    shouldBe("a.indexOf(2.5)", "-1");
+}
+
+for (constructor of floatArrays) {
+    a = new constructor([0,2.0,3.6, NaN, Infinity]);
+
+    shouldBe("a.indexOf(2.0)", "1");
+    shouldBe("a.indexOf(2.5)", "-1");
+    shouldBe("a.indexOf(3.600001)", "-1");
+    shouldBe("a.indexOf(NaN)", "-1");
+    shouldBe("a.indexOf(Infinity)", "4");
+}
+
 finishJSTest();
index a386e45..c740043 100644 (file)
@@ -25,4 +25,40 @@ shouldBeTrue("testPrototypeFunction('lastIndexOf', '(2, 0)', array, 0)");
 shouldBeTrue("testPrototypeFunction('lastIndexOf', '(2, -1)', array, 3)");
 shouldBeTrue("testPrototypeFunction('lastIndexOf', '(2, -2)', array, 0)");
 debug("");
+
+debug("Check object coersion");
+for (constructor of typedArrays) {
+    a = new constructor([0,2,3]);
+    passed = true;
+
+    shouldBe("a.lastIndexOf({ valueOf() { passed = false; return 1; }})", "-1");
+    shouldBeTrue("passed");
+    shouldBe("a.lastIndexOf(3, {valueOf: () => 3})", "2");
+
+    // test we don't coerce non-native values
+    shouldBe("a.lastIndexOf(\"abc\")", "-1");
+    shouldBe("a.lastIndexOf(null)", "-1");
+    shouldBe("a.lastIndexOf(undefined)", "-1");
+    shouldBe("a.lastIndexOf({1: ''})", "-1");
+    shouldBe("a.lastIndexOf(\"\")", "-1");
+}
+
+
+for (constructor of intArrays) {
+    a = new constructor([0,2,3]);
+
+    shouldBe("a.lastIndexOf(2.0)", "1");
+    shouldBe("a.lastIndexOf(2.5)", "-1");
+}
+
+for (constructor of floatArrays) {
+    a = new constructor([0,2.0,3.6, NaN, Infinity]);
+
+    shouldBe("a.lastIndexOf(2.0)", "1");
+    shouldBe("a.lastIndexOf(2.5)", "-1");
+    shouldBe("a.lastIndexOf(3.600001)", "-1");
+    shouldBe("a.lastIndexOf(NaN)", "-1");
+    shouldBe("a.lastIndexOf(Infinity)", "4");
+}
+
 finishJSTest();