put_by_val_direct need to check the property is index or not for using putDirect / putDirectIndex
https://bugs.webkit.org/show_bug.cgi?id=140426

Reviewed by Darin Adler.

In the put_by_val_direct operation, we use JSObject::putDirect.
However, it only accepts non-index property. For index property, we need to use JSObject::putDirectIndex.
This patch checks toString-ed Identifier is index or not to choose putDirect / putDirectIndex.

* dfg/DFGOperations.cpp:
(JSC::DFG::putByVal):
(JSC::DFG::operationPutByValInternal):
* jit/JITOperations.cpp:
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::LLINT_SLOW_PATH_DECL):
* runtime/Identifier.h:
(JSC::isIndex):
(JSC::parseIndex):
* tests/stress/dfg-put-by-val-direct-with-edge-numbers.js: Added.
(lookupWithKey):
(toStringThrowsError.toString):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@182452 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 0994bb8..5ab5963 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,27 @@
+2015-04-06  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        put_by_val_direct need to check the property is index or not for using putDirect / putDirectIndex
+        https://bugs.webkit.org/show_bug.cgi?id=140426
+
+        Reviewed by Darin Adler.
+
+        In the put_by_val_direct operation, we use JSObject::putDirect.
+        However, it only accepts non-index property. For index property, we need to use JSObject::putDirectIndex.
+        This patch checks toString-ed Identifier is index or not to choose putDirect / putDirectIndex.
+
+        * dfg/DFGOperations.cpp:
+        (JSC::DFG::putByVal):
+        (JSC::DFG::operationPutByValInternal):
+        * jit/JITOperations.cpp:
+        * llint/LLIntSlowPaths.cpp:
+        (JSC::LLInt::LLINT_SLOW_PATH_DECL):
+        * runtime/Identifier.h:
+        (JSC::isIndex):
+        (JSC::parseIndex):
+        * tests/stress/dfg-put-by-val-direct-with-edge-numbers.js: Added.
+        (lookupWithKey):
+        (toStringThrowsError.toString):
+
 2015-04-06  Alberto Garcia  <berto@igalia.com>
 
         [GTK] Fix HPPA build
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp
index a0726c1..ff16903 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp
@@ -69,6 +69,7 @@
 {
     VM& vm = exec->vm();
     NativeCallFrameTracer tracer(&vm, exec);
+    ASSERT(isIndex(index));
     if (direct) {
         RELEASE_ASSERT(baseValue.isObject());
         asObject(baseValue)->putDirectIndex(exec, index, value, 0, strict ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
@@ -99,6 +100,8 @@
     JSValue value = JSValue::decode(encodedValue);
 
     if (LIKELY(property.isUInt32())) {
+        // Despite its name, JSValue::isUInt32 will return true only for positive boxed int32_t; all those values are valid array indices.
+        ASSERT(isIndex(property.asUInt32()));
         putByVal<strict, direct>(exec, baseValue, property.asUInt32(), value);
         return;
     }
@@ -106,7 +109,7 @@
     if (property.isDouble()) {
         double propertyAsDouble = property.asDouble();
         uint32_t propertyAsUInt32 = static_cast<uint32_t>(propertyAsDouble);
-        if (propertyAsDouble == propertyAsUInt32) {
+        if (propertyAsDouble == propertyAsUInt32 && isIndex(propertyAsUInt32)) {
             putByVal<strict, direct>(exec, baseValue, propertyAsUInt32, value);
             return;
         }
@@ -114,14 +117,18 @@
 
     // Don't put to an object if toString throws an exception.
     auto propertyName = property.toPropertyKey(exec);
-    if (!vm->exception()) {
-        PutPropertySlot slot(baseValue, strict);
-        if (direct) {
-            RELEASE_ASSERT(baseValue.isObject());
+    if (vm->exception())
+        return;
+
+    PutPropertySlot slot(baseValue, strict);
+    if (direct) {
+        RELEASE_ASSERT(baseValue.isObject());
+        if (Optional<uint32_t> index = parseIndex(propertyName))
+            asObject(baseValue)->putDirectIndex(exec, index.value(), value, 0, strict ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+        else
             asObject(baseValue)->putDirect(*vm, propertyName, value, slot);
-        } else
-            baseValue.put(exec, propertyName, value, slot);
-    }
+    } else
+        baseValue.put(exec, propertyName, value, slot);
 }
 
 template<typename ViewClass>
@@ -285,7 +292,7 @@
         } else if (property.isDouble()) {
             double propertyAsDouble = property.asDouble();
             uint32_t propertyAsUInt32 = static_cast<uint32_t>(propertyAsDouble);
-            if (propertyAsUInt32 == propertyAsDouble)
+            if (propertyAsUInt32 == propertyAsDouble && isIndex(propertyAsUInt32))
                 return getByVal(exec, base, propertyAsUInt32);
         } else if (property.isString()) {
             Structure& structure = *base->structure(vm);
diff --git a/Source/JavaScriptCore/jit/JITOperations.cpp b/Source/JavaScriptCore/jit/JITOperations.cpp
index 58b8636..5e90d48 100644
--- a/Source/JavaScriptCore/jit/JITOperations.cpp
+++ b/Source/JavaScriptCore/jit/JITOperations.cpp
@@ -53,6 +53,7 @@
 #include "JSWithScope.h"
 #include "LegacyProfiler.h"
 #include "ObjectConstructor.h"
+#include "PropertyName.h"
 #include "Repatch.h"
 #include "RepatchBuffer.h"
 #include "TestRunnerUtils.h"
@@ -497,16 +498,34 @@
 
 static void directPutByVal(CallFrame* callFrame, JSObject* baseObject, JSValue subscript, JSValue value)
 {
+    bool isStrictMode = callFrame->codeBlock()->isStrictMode();
     if (LIKELY(subscript.isUInt32())) {
-        uint32_t i = subscript.asUInt32();
-        baseObject->putDirectIndex(callFrame, i, value);
-    } else {
-        auto property = subscript.toPropertyKey(callFrame);
-        if (!callFrame->vm().exception()) { // Don't put to an object if toString threw an exception.
-            PutPropertySlot slot(baseObject, callFrame->codeBlock()->isStrictMode());
-            baseObject->putDirect(callFrame->vm(), property, value, slot);
+        // Despite its name, JSValue::isUInt32 will return true only for positive boxed int32_t; all those values are valid array indices.
+        ASSERT(isIndex(subscript.asUInt32()));
+        baseObject->putDirectIndex(callFrame, subscript.asUInt32(), value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+        return;
+    }
+
+    if (subscript.isDouble()) {
+        double subscriptAsDouble = subscript.asDouble();
+        uint32_t subscriptAsUInt32 = static_cast<uint32_t>(subscriptAsDouble);
+        if (subscriptAsDouble == subscriptAsUInt32 && isIndex(subscriptAsUInt32)) {
+            baseObject->putDirectIndex(callFrame, subscriptAsUInt32, value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+            return;
         }
     }
+
+    // Don't put to an object if toString threw an exception.
+    auto property = subscript.toPropertyKey(callFrame);
+    if (callFrame->vm().exception())
+        return;
+
+    if (Optional<uint32_t> index = parseIndex(property))
+        baseObject->putDirectIndex(callFrame, index.value(), value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+    else {
+        PutPropertySlot slot(baseObject, isStrictMode);
+        baseObject->putDirect(callFrame->vm(), property, value, slot);
+    }
 }
 void JIT_OPERATION operationPutByVal(ExecState* exec, EncodedJSValue encodedBaseValue, EncodedJSValue encodedSubscript, EncodedJSValue encodedValue)
 {
diff --git a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
index 279285a..27b98698 100644
--- a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
+++ b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
@@ -796,16 +796,34 @@
     JSValue value = LLINT_OP_C(3).jsValue();
     RELEASE_ASSERT(baseValue.isObject());
     JSObject* baseObject = asObject(baseValue);
+    bool isStrictMode = exec->codeBlock()->isStrictMode();
     if (LIKELY(subscript.isUInt32())) {
-        uint32_t i = subscript.asUInt32();
-        baseObject->putDirectIndex(exec, i, value);
-    } else {
-        auto property = subscript.toPropertyKey(exec);
-        if (!exec->vm().exception()) { // Don't put to an object if toString threw an exception.
-            PutPropertySlot slot(baseObject, exec->codeBlock()->isStrictMode());
-            baseObject->putDirect(exec->vm(), property, value, slot);
+        // Despite its name, JSValue::isUInt32 will return true only for positive boxed int32_t; all those values are valid array indices.
+        ASSERT(isIndex(subscript.asUInt32()));
+        baseObject->putDirectIndex(exec, subscript.asUInt32(), value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+        LLINT_END();
+    }
+
+    if (subscript.isDouble()) {
+        double subscriptAsDouble = subscript.asDouble();
+        uint32_t subscriptAsUInt32 = static_cast<uint32_t>(subscriptAsDouble);
+        if (subscriptAsDouble == subscriptAsUInt32 && isIndex(subscriptAsUInt32)) {
+            baseObject->putDirectIndex(exec, subscriptAsUInt32, value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+            LLINT_END();
         }
     }
+
+    // Don't put to an object if toString threw an exception.
+    auto property = subscript.toPropertyKey(exec);
+    if (exec->vm().exception())
+        LLINT_END();
+
+    if (Optional<uint32_t> index = parseIndex(property))
+        baseObject->putDirectIndex(exec, index.value(), value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
+    else {
+        PutPropertySlot slot(baseObject, isStrictMode);
+        baseObject->putDirect(exec->vm(), property, value, slot);
+    }
     LLINT_END();
 }
 
diff --git a/Source/JavaScriptCore/runtime/Identifier.h b/Source/JavaScriptCore/runtime/Identifier.h
index 507b755..f5e168f 100644
--- a/Source/JavaScriptCore/runtime/Identifier.h
+++ b/Source/JavaScriptCore/runtime/Identifier.h
@@ -32,6 +32,11 @@
 
 class ExecState;
 
+ALWAYS_INLINE bool isIndex(uint32_t index)
+{
+    return index != 0xFFFFFFFFU;
+}
+
 template <typename CharType>
 ALWAYS_INLINE Optional<uint32_t> parseIndex(const CharType* characters, unsigned length)
 {
@@ -67,7 +72,7 @@
         value = newValue;
     }
 
-    if (value == 0xFFFFFFFFU)
+    if (!isIndex(value))
         return Nullopt;
     return value;
 }
diff --git a/Source/JavaScriptCore/tests/stress/dfg-put-by-val-direct-with-edge-numbers.js b/Source/JavaScriptCore/tests/stress/dfg-put-by-val-direct-with-edge-numbers.js
new file mode 100644
index 0000000..ebf21e2
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/dfg-put-by-val-direct-with-edge-numbers.js
@@ -0,0 +1,104 @@
+// Test that a object accepts DFG PutByValueDirect operation with edge numbers.
+
+function lookupWithKey(key) {
+    var object = {
+        [key]: 42
+    };
+    return object[key];
+}
+noInline(lookupWithKey);
+
+for (var i = 0; i < 10000; ++i) {
+    [
+        // integers
+        -0x80000001,  // out of int32_t
+        -0x80000000,  // int32_t min
+        -1,           // negative
+        0,            // zero
+        1,            // positive
+        0x7fffffff,   // int32_t max
+        0x80000000,   // out of int32_t
+        0xfffffffd,   // less than array max in JSObject
+        0xfffffffe,   // array max in JSObject
+        0xffffffff,   // uint32_t max, not array index
+        0x100000000,  // out of uint32_t
+
+        // stringified integers
+        (-0x80000001).toString(),  // out of int32_t
+        (-0x80000000).toString(),  // int32_t min
+        (-1).toString(),           // negative
+        (0).toString(),            // zero
+        (1).toString(),            // positive
+        (0x7fffffff).toString(),   // int32_t max
+        (0x80000000).toString(),   // out of int32_t
+        (0xfffffffd).toString(),   // less than array max in JSObject
+        (0xfffffffe).toString(),   // array max in JSObject
+        (0xffffffff).toString(),   // (uint32_t max).toString()
+        (0x100000000).toString(),  // out of uint32_t
+
+        // doubles
+        Number.MIN_VALUE,
+        Number.MAX_VALUE,
+        Number.MIN_SAFE_INTEGER,
+        Number.MAX_SAFE_INTEGER,
+        Number.POSITIVE_INFINITY,
+        Number.NEGATIVE_INFINITY,
+        Number.NaN,
+        Number.EPSILON,
+        +0.0,
+        -0.0,
+        0.1,
+        -0.1,
+        4.2,
+        -4.2,
+        0x80000000 + 0.5,   // out of int32_t, double
+
+        // stringified doules
+        (Number.MIN_VALUE).toString(),
+        (Number.MAX_VALUE).toString(),
+        (Number.MIN_SAFE_INTEGER).toString(),
+        (Number.MAX_SAFE_INTEGER).toString(),
+        (Number.POSITIVE_INFINITY).toString(),
+        (Number.NEGATIVE_INFINITY).toString(),
+        "NaN",
+        (Number.EPSILON).toString(),
+        "+0.0",
+        "-0.0",
+        "0.1",
+        "-0.1",
+        "4.2",
+        "-4.2",
+        (0x80000000 + 0.5).toString()
+    ].forEach(function (key) {
+        var value = lookupWithKey(key);
+        if (value !== 42)
+            throw new Error('bad value: ' + value);
+    });
+}
+
+function lookupWithKey2(key) {
+    var object = {
+        [key]: 42
+    };
+    return object[key];
+}
+noInline(lookupWithKey2);
+
+var toStringThrowsError = {
+    toString: function () {
+        throw new Error('ng');
+    }
+};
+
+for (var i = 0; i < 10000; ++i) {
+    var error = null;
+    try {
+        lookupWithKey2(toStringThrowsError);
+    } catch (e) {
+        error = e;
+    }
+    if (!error)
+        throw new Error('not thrown');
+    if (String(error) !== 'Error: ng')
+        throw new Error('bad error: ' + String(error));
+}