Title: [198592] trunk
Revision
198592
Author
[email protected]
Date
2016-03-23 14:03:02 -0700 (Wed, 23 Mar 2016)

Log Message

_javascript_Core ArrayPrototype::join shouldn't cache butterfly when it makes effectful calls
https://bugs.webkit.org/show_bug.cgi?id=155776

Reviewed by Saam Barati.

Source/_javascript_Core:

Array.join ends up calling toString, possibly on some object.  Since these calls
could be effectful and could change the array itself, we can't hold the butterfly
pointer while making effectful calls.  Changed the code to fall back to the general
case when an effectful toString() call might be made.

* runtime/ArrayPrototype.cpp:
(JSC::join):
* runtime/JSStringJoiner.h:
(JSC::JSStringJoiner::appendWithoutSideEffects): New helper that doesn't make effectful
toString() calls.
(JSC::JSStringJoiner::append): Built upon appendWithoutSideEffects.

LayoutTests:

New test.

* js/regress-155776-expected.txt: Added.
* js/regress-155776.html: Added.
* js/script-tests/regress-155776.js: Added.
(fillBigArrayViaToString):
(Function.prototype.toString):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (198591 => 198592)


--- trunk/LayoutTests/ChangeLog	2016-03-23 20:58:40 UTC (rev 198591)
+++ trunk/LayoutTests/ChangeLog	2016-03-23 21:03:02 UTC (rev 198592)
@@ -1,3 +1,18 @@
+2016-03-23  Michael Saboff  <[email protected]>
+
+        _javascript_Core ArrayPrototype::join shouldn't cache butterfly when it makes effectful calls
+        https://bugs.webkit.org/show_bug.cgi?id=155776
+
+        Reviewed by Saam Barati.
+
+        New test.
+
+        * js/regress-155776-expected.txt: Added.
+        * js/regress-155776.html: Added.
+        * js/script-tests/regress-155776.js: Added.
+        (fillBigArrayViaToString):
+        (Function.prototype.toString):
+
 2016-03-23  Daniel Bates  <[email protected]>
 
         CSP: Make violation console messages concise and consistent

Added: trunk/LayoutTests/js/regress-155776-expected.txt (0 => 198592)


--- trunk/LayoutTests/js/regress-155776-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/regress-155776-expected.txt	2016-03-23 21:03:02 UTC (rev 198592)
@@ -0,0 +1,10 @@
+Regresion test for 155776. This test should pass and not crash.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS stringResult is expectedString
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/regress-155776.html (0 => 198592)


--- trunk/LayoutTests/js/regress-155776.html	                        (rev 0)
+++ trunk/LayoutTests/js/regress-155776.html	2016-03-23 21:03:02 UTC (rev 198592)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/script-tests/regress-155776.js (0 => 198592)


--- trunk/LayoutTests/js/script-tests/regress-155776.js	                        (rev 0)
+++ trunk/LayoutTests/js/script-tests/regress-155776.js	2016-03-23 21:03:02 UTC (rev 198592)
@@ -0,0 +1,50 @@
+description("Regresion test for 155776. This test should pass and not crash.");
+
+var bigArray = [];
+var bigNum = 123456789;
+var smallNum = 123;
+var toStringCount = 0;
+
+function fillBigArrayViaToString(n) {
+    var results = [];
+
+    for (var i = 0; i < n; i++)
+        fillBigArrayViaToString.toString();
+
+    return results;
+}
+
+Function.prototype.toString = function(x) {
+    toStringCount++;
+    bigArray.push(smallNum);
+
+    if (toStringCount == 2000) {
+        var newArray = new Uint32Array(8000);
+        for (var i = 0; i < newArray.length; i++)
+            newArray[i] = 0x10000000;
+    }
+
+    bigArray.push(fillBigArrayViaToString);
+    bigArray.push(fillBigArrayViaToString);
+    bigArray.push(fillBigArrayViaToString);
+    return bigNum;
+};
+
+fillBigArrayViaToString(4000).join();
+
+bigArray.length = 4000;
+
+var stringResult = bigArray.join(":");
+
+var expectedArray = [];
+
+for (var i = 0; i < 1000; i++) {
+    expectedArray.push(smallNum);
+    expectedArray.push(bigNum);
+    expectedArray.push(bigNum);
+    expectedArray.push(bigNum);
+}
+
+var expectedString = expectedArray.join(":");
+
+shouldBe('stringResult', 'expectedString');

Modified: trunk/Source/_javascript_Core/ChangeLog (198591 => 198592)


--- trunk/Source/_javascript_Core/ChangeLog	2016-03-23 20:58:40 UTC (rev 198591)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-03-23 21:03:02 UTC (rev 198592)
@@ -1,3 +1,22 @@
+2016-03-23  Michael Saboff  <[email protected]>
+
+        _javascript_Core ArrayPrototype::join shouldn't cache butterfly when it makes effectful calls
+        https://bugs.webkit.org/show_bug.cgi?id=155776
+
+        Reviewed by Saam Barati.
+
+        Array.join ends up calling toString, possibly on some object.  Since these calls
+        could be effectful and could change the array itself, we can't hold the butterfly
+        pointer while making effectful calls.  Changed the code to fall back to the general
+        case when an effectful toString() call might be made.
+
+        * runtime/ArrayPrototype.cpp:
+        (JSC::join):
+        * runtime/JSStringJoiner.h:
+        (JSC::JSStringJoiner::appendWithoutSideEffects): New helper that doesn't make effectful
+        toString() calls.
+        (JSC::JSStringJoiner::append): Built upon appendWithoutSideEffects.
+
 2016-03-23  Keith Miller  <[email protected]>
 
         Array.prototype native functions' species constructors should work with proxies

Modified: trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp (198591 => 198592)


--- trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp	2016-03-23 20:58:40 UTC (rev 198591)
+++ trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp	2016-03-23 21:03:02 UTC (rev 198592)
@@ -495,9 +495,8 @@
         bool holesKnownToBeOK = false;
         for (unsigned i = 0; i < length; ++i) {
             if (JSValue value = data[i].get()) {
-                joiner.append(state, value);
-                if (state.hadException())
-                    return jsUndefined();
+                if (!joiner.appendWithoutSideEffects(state, value))
+                    goto generalCase;
             } else {
                 if (!holesKnownToBeOK) {
                     if (holesMustForwardToPrototype(state, thisObject))
@@ -545,9 +544,8 @@
         auto data = ""
         for (unsigned i = 0; i < length; ++i) {
             if (JSValue value = data[i].get()) {
-                joiner.append(state, value);
-                if (state.hadException())
-                    return jsUndefined();
+                if (!joiner.appendWithoutSideEffects(state, value))
+                    goto generalCase;
             } else
                 joiner.appendEmptyString();
         }

Modified: trunk/Source/_javascript_Core/runtime/JSStringJoiner.h (198591 => 198592)


--- trunk/Source/_javascript_Core/runtime/JSStringJoiner.h	2016-03-23 20:58:40 UTC (rev 198591)
+++ trunk/Source/_javascript_Core/runtime/JSStringJoiner.h	2016-03-23 21:03:02 UTC (rev 198592)
@@ -38,6 +38,7 @@
     ~JSStringJoiner();
 
     void append(ExecState&, JSValue);
+    bool appendWithoutSideEffects(ExecState&, JSValue);
     void appendEmptyString();
 
     JSValue join(ExecState&);
@@ -97,7 +98,7 @@
     m_strings.uncheckedAppend({ { }, { } });
 }
 
-ALWAYS_INLINE void JSStringJoiner::append(ExecState& state, JSValue value)
+ALWAYS_INLINE bool JSStringJoiner::appendWithoutSideEffects(ExecState& state, JSValue value)
 {
     // The following code differs from using the result of JSValue::toString in the following ways:
     // 1) It's inlined more than JSValue::toString is.
@@ -105,37 +106,46 @@
     // 3) It doesn't create a JSString for numbers, true, or false.
     // 4) It turns undefined and null into the empty string instead of "undefined" and "null".
     // 5) It uses optimized code paths for all the cases known to be 8-bit and for the empty string.
+    // If we might make an effectful calls, return false. Otherwise return true.
 
     if (value.isCell()) {
         JSString* jsString;
-        if (value.asCell()->isString())
-            jsString = asString(value);
-        else
-            jsString = value.toString(&state);
+        if (!value.asCell()->isString())
+            return false;
+        jsString = asString(value);
         append(jsString->viewWithUnderlyingString(state));
-        return;
+        return true;
     }
 
     if (value.isInt32()) {
         append8Bit(state.vm().numericStrings.add(value.asInt32()));
-        return;
+        return true;
     }
     if (value.isDouble()) {
         append8Bit(state.vm().numericStrings.add(value.asDouble()));
-        return;
+        return true;
     }
     if (value.isTrue()) {
         append8Bit(state.vm().propertyNames->trueKeyword.string());
-        return;
+        return true;
     }
     if (value.isFalse()) {
         append8Bit(state.vm().propertyNames->falseKeyword.string());
-        return;
+        return true;
     }
     ASSERT(value.isUndefinedOrNull());
     appendEmptyString();
+    return true;
 }
 
+ALWAYS_INLINE void JSStringJoiner::append(ExecState& state, JSValue value)
+{
+    if (!appendWithoutSideEffects(state, value)) {
+        JSString* jsString = value.toString(&state);
+        append(jsString->viewWithUnderlyingString(state));
+    }
 }
 
+}
+
 #endif
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to