Title: [215777] trunk
Revision
215777
Author
sbar...@apple.com
Date
2017-04-25 17:52:35 -0700 (Tue, 25 Apr 2017)

Log Message

JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable is wrong because it does not do the necessary checks on the base object
https://bugs.webkit.org/show_bug.cgi?id=171150
<rdar://problem/31771880>

Reviewed by Sam Weinig.

JSTests:

* stress/spread-optimized-properly.js: Added.
(assert):
(test):
(shallowEq):
(makeArrayIterator):
(test.bar):
(test.callback):
(test.arr.__proto__.Symbol.iterator):
(test.arr.Symbol.iterator):
(test.get bar):
(test.hackedNext):
(test.test):
(test.):

Source/_javascript_Core:

This patch fixes a huge oversight from the patch that introduced
op_spread/Spread. The original patch did not account for the
base object having Symbol.iterator or getters that could
change the iterator protocol. This patch fixes the oversight both
in the C code, as well as the DFG/FTL backends. We only perform
the memcpy version of spread if we've proven that it's guaranteed
to be side-effect free (no indexed getters), and if the iterator
protocol is guaranteed to be the original protocol. To do this, we
must prove that:
1. The protocol on Array.prototype hasn't changed (this is the same as the
introductory patch for op_spread).
2. The base object's __proto__ is Array.prototype
3. The base object does not have indexed getters
4. The base object does not have Symbol.iterator property.

* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::canDoFastSpread):
* dfg/DFGGraph.h:
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileSpread):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileSpread):
* runtime/JSArray.cpp:
(JSC::JSArray::isIteratorProtocolFastAndNonObservable):
* runtime/JSArray.h:
* runtime/JSArrayInlines.h:
(JSC::JSArray::isIteratorProtocolFastAndNonObservable): Deleted.
* runtime/JSGlobalObject.h:
* runtime/JSGlobalObjectInlines.h:
(JSC::JSGlobalObject::isArrayPrototypeIteratorProtocolFastAndNonObservable):
(JSC::JSGlobalObject::isArrayIteratorProtocolFastAndNonObservable): Deleted.

Source/WebCore:

This patch moves the sequence converters to use the now fixed
JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable test
inside JSC.

This patch also fixes a few bugs:
1. Converting to a sequence of numbers must prove that the JSArray
is filled only with Int32/Double. If there is a chance the array
contains objects, the conversion to a numeric IDLType can be observable
(via valueOf()), and can change the iterator protocol.
2. There are other conversions that can have side effects a-la valueOf().
This patch introduces a new static constant in the various Converter
classes that tell the sequence converter if the conversion operation
can have JS side effects. If it does have side effects, we fall back to
the generic conversion that uses the iterator protocol. If not, we can
do a faster version that iterates over each element of the array,
reading it directly, and converting it.

Tests: js/sequence-iterator-protocol-2.html
       js/sequence-iterator-protocol.html

* bindings/js/JSDOMConvertAny.h: Does not have side effects.
* bindings/js/JSDOMConvertBase.h: We pessimistically assume inside DefaultConverter that converions have side effects.
* bindings/js/JSDOMConvertBoolean.h: Does not have side effects.
* bindings/js/JSDOMConvertCallbacks.h: Does not have side effects.
* bindings/js/JSDOMConvertObject.h: Does not have side effects.
* bindings/js/JSDOMConvertSequences.h:
(WebCore::Detail::NumericSequenceConverter::convert):
(WebCore::Detail::SequenceConverter::convert):

LayoutTests:

* js/sequence-iterator-protocol-2-expected.txt: Added.
* js/sequence-iterator-protocol-2.html: Added.
* js/sequence-iterator-protocol-expected.txt: Added.
* js/sequence-iterator-protocol.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (215776 => 215777)


--- trunk/JSTests/ChangeLog	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/JSTests/ChangeLog	2017-04-26 00:52:35 UTC (rev 215777)
@@ -1,3 +1,25 @@
+2017-04-25  Saam Barati  <sbar...@apple.com>
+
+        JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable is wrong because it does not do the necessary checks on the base object
+        https://bugs.webkit.org/show_bug.cgi?id=171150
+        <rdar://problem/31771880>
+
+        Reviewed by Sam Weinig.
+
+        * stress/spread-optimized-properly.js: Added.
+        (assert):
+        (test):
+        (shallowEq):
+        (makeArrayIterator):
+        (test.bar):
+        (test.callback):
+        (test.arr.__proto__.Symbol.iterator):
+        (test.arr.Symbol.iterator):
+        (test.get bar):
+        (test.hackedNext):
+        (test.test):
+        (test.):
+
 2017-04-25  Mark Lam  <mark....@apple.com>
 
         [Follow up] Array.prototype.slice() should ensure that end >= begin.

Added: trunk/JSTests/stress/spread-optimized-properly.js (0 => 215777)


--- trunk/JSTests/stress/spread-optimized-properly.js	                        (rev 0)
+++ trunk/JSTests/stress/spread-optimized-properly.js	2017-04-26 00:52:35 UTC (rev 215777)
@@ -0,0 +1,155 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad assertion");
+}
+
+function test(f) { f(); }
+
+function shallowEq(a, b) {
+    if (a.length !== b.length)
+        return false;
+    for (let i = 0; i < a.length; ++i) {
+        if (a[i] !== b[i])
+            return false;
+    }
+    return true;
+}
+
+function makeArrayIterator(arr, f) {
+    let i = 0;
+    return {
+        next() {
+            f();
+            if (i >= arr.length)
+                return {done: true};
+            return {value: arr[i++], done: false};
+        }
+    };
+} 
+
+test(function() {
+    let arr = [10, 20];
+    arr.__proto__ = {[Symbol.iterator]: Array.prototype[Symbol.iterator]};
+    function bar(a) {
+        a.x;
+        return [...a];
+    }
+    noInline(bar);
+    for (let i = 0; i < 10000; i++) {
+        assert(shallowEq(bar(arr), arr));
+    }
+});
+
+test(function() {
+    let arr = [10, 20];
+    let count = 0;
+    function callback() {
+        count++;
+    }
+
+    arr.__proto__ = {
+        [Symbol.iterator]: function() {
+            return makeArrayIterator(this, callback);
+        }
+    };
+
+    function bar(a) {
+        a.x;
+        return [...a];
+    }
+    noInline(bar);
+    for (let i = 0; i < 10000; i++) {
+        let t = bar(arr);
+        assert(count === 3);
+        count = 0;
+        assert(shallowEq(t, arr));
+    }
+});
+
+test(function() {
+    let arr = [10, 20];
+    let count = 0;
+    function callback() {
+        count++;
+    }
+
+    arr[Symbol.iterator] = function() {
+        return makeArrayIterator(this, callback);
+    };
+
+    function bar(a) {
+        a.x;
+        return [...a];
+    }
+    noInline(bar);
+    for (let i = 0; i < 10000; i++) {
+        let t = bar(arr);
+        assert(count === 3);
+        count = 0;
+        assert(shallowEq(t, arr));
+    }
+});
+
+test(function() {
+    let arr = [10, 20];
+    arr[Symbol.iterator] = Array.prototype[Symbol.iterator];
+    function bar(a) {
+        a.x;
+        return [...a];
+    }
+    noInline(bar);
+    for (let i = 0; i < 10000; i++) {
+        assert(shallowEq(bar(arr), arr));
+    }
+});
+
+test(function() {
+    let arr = [, 20];
+    let callCount = 0;
+    Object.defineProperty(arr, 0, {get() { ++callCount; return 10; }});
+    function bar(a) {
+        a.x;
+        return [...a];
+    }
+    noInline(bar);
+    for (let i = 0; i < 10000; i++) {
+        let t = bar(arr);
+        assert(callCount === 1);
+        assert(shallowEq(t, arr));
+        assert(callCount === 2);
+        callCount = 0;
+    }
+});
+
+// We run this test last since it fires watchpoints for the protocol.
+test(function() {
+    let iter = [][Symbol.iterator]();
+    let iterProto = Object.getPrototypeOf(iter);
+    let oldNext = iterProto.next;
+
+    function hackedNext() {
+        let val = oldNext.call(this);
+        if ("value" in val) {
+            val.value++;
+        }
+        return val;
+    }
+
+    function test(a) {
+        a.x;
+        return [...a];
+    }
+
+    for (let i = 0; i < 10000; ++i) {
+        let arr = [1,,3];
+        let callCount = 0;
+        Object.defineProperty(arr, 1, { get: function() { ++callCount; iterProto.next = hackedNext; return 2; } });
+        let t = test(arr);
+        assert(callCount === 1);
+        assert(t.length === 3);
+        assert(t[0] === 1);
+        assert(t[1] === 2);
+        assert(t[2] === 4);
+        iterProto.next = oldNext;
+    }
+});

Modified: trunk/LayoutTests/ChangeLog (215776 => 215777)


--- trunk/LayoutTests/ChangeLog	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/LayoutTests/ChangeLog	2017-04-26 00:52:35 UTC (rev 215777)
@@ -1,3 +1,16 @@
+2017-04-25  Saam Barati  <sbar...@apple.com>
+
+        JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable is wrong because it does not do the necessary checks on the base object
+        https://bugs.webkit.org/show_bug.cgi?id=171150
+        <rdar://problem/31771880>
+
+        Reviewed by Sam Weinig.
+
+        * js/sequence-iterator-protocol-2-expected.txt: Added.
+        * js/sequence-iterator-protocol-2.html: Added.
+        * js/sequence-iterator-protocol-expected.txt: Added.
+        * js/sequence-iterator-protocol.html: Added.
+
 2017-04-25  Ryan Haddad  <ryanhad...@apple.com>
 
         Mark media/modern-media-controls/pip-support/pip-support-click.html as flaky.

Added: trunk/LayoutTests/js/sequence-iterator-protocol-2-expected.txt (0 => 215777)


--- trunk/LayoutTests/js/sequence-iterator-protocol-2-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/sequence-iterator-protocol-2-expected.txt	2017-04-26 00:52:35 UTC (rev 215777)
@@ -0,0 +1,11 @@
+CONSOLE MESSAGE: line 28: Using an array but with objects with valueOf()
+CONSOLE MESSAGE: line 29: 1,2,3,1,2,3
+CONSOLE MESSAGE: line 33: 1,2,3,1,2,3
+CONSOLE MESSAGE: line 38: callCount: 3
+CONSOLE MESSAGE: line 66: 1,2,4,1,2,4
+CONSOLE MESSAGE: line 71: 1,2,4,1,2,4
+CONSOLE MESSAGE: line 75: callCount: 2
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/sequence-iterator-protocol-2.html (0 => 215777)


--- trunk/LayoutTests/js/sequence-iterator-protocol-2.html	                        (rev 0)
+++ trunk/LayoutTests/js/sequence-iterator-protocol-2.html	2017-04-26 00:52:35 UTC (rev 215777)
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src=""
+    </head>
+    <body>
+        <pre id='console'></pre>
+        <canvas></canvas>
+
+        <script>
+            "use strict";
+
+            const log = console.log.bind(console);
+            const canvas = document.querySelector("canvas");
+            const ctx = canvas.getContext("2d");
+
+            let callCount = 0;
+            function makeValue(x) {
+                return {
+                    valueOf() { ++callCount; return x; }
+                };
+            }
+
+            let array = [makeValue(1), makeValue(2), makeValue(3)];
+            ctx.setLineDash(array);
+            let a = ctx.getLineDash();
+            log("Using an array but with objects with valueOf()");
+            log(a);
+
+            ctx.setLineDash([1,2,3]);
+            let b = ctx.getLineDash();
+            log(b);
+
+            if (a.toString() !== b.toString())
+                throw new Error("Bad result. They should be equal.");
+
+            log(`callCount: ${callCount}`);
+            if (callCount !== 3)
+                throw new Error("Bad result. callCount should be 3.");
+
+
+            let iter = [][Symbol.iterator]();
+            let iterProto = Object.getPrototypeOf(iter);
+            let oldNext = iterProto.next;
+
+            callCount = 0;
+            function hackedNext() {
+                ++callCount;
+
+                let val = oldNext.call(this);
+                if ("value" in val) {
+                    val.value++;
+                }
+                return val;
+            }
+            const obj = {
+                valueOf: function() {
+                    iterProto.next = hackedNext;
+                    return 2;
+                }
+            };
+            array = [1, obj, 3];
+            ctx.setLineDash(array);
+            b = ctx.getLineDash();
+            log(`${b}`);
+
+            iterProto.next = oldNext;
+            ctx.setLineDash([1,2,4]);
+            a = ctx.getLineDash();
+            log(a);
+            if (a.toString() !== b.toString())
+                throw new Error("Bad result. They should be equal.");
+
+            log(`callCount: ${callCount}`);
+            if (callCount !== 2)
+                throw new Error("Bad result. callCount should be 2.");
+
+        </script>
+
+        <script src=""
+
+    </body>
+</html>

Added: trunk/LayoutTests/js/sequence-iterator-protocol-expected.txt (0 => 215777)


--- trunk/LayoutTests/js/sequence-iterator-protocol-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/sequence-iterator-protocol-expected.txt	2017-04-26 00:52:35 UTC (rev 215777)
@@ -0,0 +1,10 @@
+CONSOLE MESSAGE: line 19: 
+CONSOLE MESSAGE: line 22: Using an array: 1,2,3,1,2,3
+CONSOLE MESSAGE: line 33: Using an iterator: 10,11,12,10,11,12
+CONSOLE MESSAGE: line 41: Using an array but with customized iteration (should be same as iterator): 10,11,12,10,11,12
+The second and third results should be the same.
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/sequence-iterator-protocol.html (0 => 215777)


--- trunk/LayoutTests/js/sequence-iterator-protocol.html	                        (rev 0)
+++ trunk/LayoutTests/js/sequence-iterator-protocol.html	2017-04-26 00:52:35 UTC (rev 215777)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src=""
+    </head>
+    <body>
+        <p>The second and third results should be the same.</p>
+        <pre id='console'></pre>
+        <canvas></canvas>
+
+        <script>
+            "use strict";
+
+            const log = console.log.bind(console);
+
+            const canvas = document.querySelector("canvas");
+            const ctx = canvas.getContext("2d");
+            log(ctx.getLineDash());
+
+            ctx.setLineDash([1, 2, 3]);
+            log("Using an array: " + ctx.getLineDash());
+
+            function* generator() {
+                yield 10;
+                yield 11;
+                yield 12;
+            }
+
+            const iterator = generator();
+            ctx.setLineDash(iterator);
+            let a = ctx.getLineDash();
+            log("Using an iterator: " + a);
+
+            const array = [1, 2, 3];
+            Object.defineProperty(array, Symbol.iterator, {
+                value: () => generator()
+            });
+            ctx.setLineDash(array);
+            let b = ctx.getLineDash();
+            log("Using an array but with customized iteration (should be same as iterator): " + b);
+
+            if (a.toString() !== b.toString())
+                throw new Error("Bad result. They should be equal.");
+
+        </script>
+
+        <script src=""
+
+    </body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (215776 => 215777)


--- trunk/Source/_javascript_Core/ChangeLog	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-04-26 00:52:35 UTC (rev 215777)
@@ -1,3 +1,43 @@
+2017-04-25  Saam Barati  <sbar...@apple.com>
+
+        JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable is wrong because it does not do the necessary checks on the base object
+        https://bugs.webkit.org/show_bug.cgi?id=171150
+        <rdar://problem/31771880>
+
+        Reviewed by Sam Weinig.
+
+        This patch fixes a huge oversight from the patch that introduced
+        op_spread/Spread. The original patch did not account for the
+        base object having Symbol.iterator or getters that could
+        change the iterator protocol. This patch fixes the oversight both
+        in the C code, as well as the DFG/FTL backends. We only perform
+        the memcpy version of spread if we've proven that it's guaranteed
+        to be side-effect free (no indexed getters), and if the iterator
+        protocol is guaranteed to be the original protocol. To do this, we
+        must prove that:
+        1. The protocol on Array.prototype hasn't changed (this is the same as the
+        introductory patch for op_spread).
+        2. The base object's __proto__ is Array.prototype
+        3. The base object does not have indexed getters
+        4. The base object does not have Symbol.iterator property.
+
+        * dfg/DFGGraph.cpp:
+        (JSC::DFG::Graph::canDoFastSpread):
+        * dfg/DFGGraph.h:
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileSpread):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileSpread):
+        * runtime/JSArray.cpp:
+        (JSC::JSArray::isIteratorProtocolFastAndNonObservable):
+        * runtime/JSArray.h:
+        * runtime/JSArrayInlines.h:
+        (JSC::JSArray::isIteratorProtocolFastAndNonObservable): Deleted.
+        * runtime/JSGlobalObject.h:
+        * runtime/JSGlobalObjectInlines.h:
+        (JSC::JSGlobalObject::isArrayPrototypeIteratorProtocolFastAndNonObservable):
+        (JSC::JSGlobalObject::isArrayIteratorProtocolFastAndNonObservable): Deleted.
+
 2017-04-25  Mark Lam  <mark....@apple.com>
 
         Array.prototype.slice() should ensure that end >= begin.

Modified: trunk/Source/_javascript_Core/dfg/DFGGraph.cpp (215776 => 215777)


--- trunk/Source/_javascript_Core/dfg/DFGGraph.cpp	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/dfg/DFGGraph.cpp	2017-04-26 00:52:35 UTC (rev 215777)
@@ -1723,6 +1723,37 @@
     RELEASE_ASSERT_NOT_REACHED();
 }
 
+bool Graph::canDoFastSpread(Node* node, const AbstractValue& value)
+{
+    // The parameter 'value' is the AbstractValue for child1 (the thing being spread).
+    ASSERT(node->op() == Spread);
+
+    if (node->child1().useKind() != ArrayUse) {
+        // Note: we only speculate on ArrayUse when we've set up the necessary watchpoints
+        // to prove that the iteration protocol is non-observable starting from ArrayPrototype.
+        return false;
+    }
+
+    // FIXME: We should add profiling of the incoming operand to Spread
+    // so we can speculate in such a way that we guarantee that this
+    // function would return true:
+    // https://bugs.webkit.org/show_bug.cgi?id=171198
+
+    if (!value.m_structure.isFinite())
+        return false;
+
+    ArrayPrototype* arrayPrototype = globalObjectFor(node->child1()->origin.semantic)->arrayPrototype();
+    bool allGood = true;
+    value.m_structure.forEach([&] (RegisteredStructure structure) {
+        allGood &= structure->storedPrototype() == arrayPrototype
+            && !structure->isDictionary()
+            && structure->getConcurrently(m_vm.propertyNames->iteratorSymbol.impl()) == invalidOffset
+            && !structure->mayInterceptIndexedAccesses();
+    });
+
+    return allGood;
+}
+
 } } // namespace JSC::DFG
 
 #endif // ENABLE(DFG_JIT)

Modified: trunk/Source/_javascript_Core/dfg/DFGGraph.h (215776 => 215777)


--- trunk/Source/_javascript_Core/dfg/DFGGraph.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/dfg/DFGGraph.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -884,6 +884,8 @@
     
     JSArrayBufferView* tryGetFoldableView(JSValue);
     JSArrayBufferView* tryGetFoldableView(JSValue, ArrayMode arrayMode);
+
+    bool canDoFastSpread(Node*, const AbstractValue&);
     
     void registerFrozenValues();
     

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp (215776 => 215777)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2017-04-26 00:52:35 UTC (rev 215777)
@@ -7033,11 +7033,10 @@
     SpeculateCellOperand operand(this, node->child1());
     GPRReg argument = operand.gpr();
 
-    if (node->child1().useKind() == ArrayUse) {
-        // Note: we only speculate on ArrayUse when we've set up the necessary watchpoints
-        // to prove that the iteration protocol is non-observable.
+    if (node->child1().useKind() == ArrayUse)
         speculateArray(node->child1(), argument);
 
+    if (m_jit.graph().canDoFastSpread(node, m_state.forNode(node->child1()))) {
 #if USE(JSVALUE64)
         GPRTemporary result(this);
         GPRTemporary scratch1(this);

Modified: trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp (215776 => 215777)


--- trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2017-04-26 00:52:35 UTC (rev 215777)
@@ -4676,9 +4676,11 @@
         LValue argument = lowCell(m_node->child1());
 
         LValue result;
-        if (m_node->child1().useKind() == ArrayUse) {
+
+        if (m_node->child1().useKind() == ArrayUse)
             speculateArray(m_node->child1());
 
+        if (m_graph.canDoFastSpread(m_node, m_state.forNode(m_node->child1()))) {
             LBasicBlock preLoop = m_out.newBlock();
             LBasicBlock loopSelection = m_out.newBlock();
             LBasicBlock contiguousLoopStart = m_out.newBlock();

Modified: trunk/Source/_javascript_Core/runtime/JSArray.cpp (215776 => 215777)


--- trunk/Source/_javascript_Core/runtime/JSArray.cpp	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/runtime/JSArray.cpp	2017-04-26 00:52:35 UTC (rev 215777)
@@ -1403,4 +1403,27 @@
     }
 }
 
+bool JSArray::isIteratorProtocolFastAndNonObservable()
+{
+    JSGlobalObject* globalObject = this->globalObject();
+    if (!globalObject->isArrayPrototypeIteratorProtocolFastAndNonObservable())
+        return false;
+
+    Structure* structure = this->structure();
+    // This is the fast case. Many arrays will be an original array.
+    if (globalObject->isOriginalArrayStructure(structure))
+        return true;
+
+    if (structure->mayInterceptIndexedAccesses())
+        return false;
+
+    if (structure->storedPrototype() != globalObject->arrayPrototype())
+        return false;
+
+    if (getDirectOffset(globalObject->vm(), globalObject->vm().propertyNames->iteratorSymbol) != invalidOffset)
+        return false;
+
+    return true;
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/JSArray.h (215776 => 215777)


--- trunk/Source/_javascript_Core/runtime/JSArray.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/runtime/JSArray.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -150,7 +150,7 @@
     JS_EXPORT_PRIVATE void fillArgList(ExecState*, MarkedArgumentBuffer&);
     JS_EXPORT_PRIVATE void copyToArguments(ExecState*, VirtualRegister firstElementDest, unsigned offset, unsigned length);
 
-    bool isIteratorProtocolFastAndNonObservable();
+    JS_EXPORT_PRIVATE bool isIteratorProtocolFastAndNonObservable();
 
     static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, IndexingType indexingType)
     {

Modified: trunk/Source/_javascript_Core/runtime/JSArrayInlines.h (215776 => 215777)


--- trunk/Source/_javascript_Core/runtime/JSArrayInlines.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/runtime/JSArrayInlines.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -93,9 +93,4 @@
     return lengthValue.toLength(exec);
 }
 
-ALWAYS_INLINE bool JSArray::isIteratorProtocolFastAndNonObservable()
-{
-    return globalObject()->isArrayIteratorProtocolFastAndNonObservable();
-}
-
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.h (215776 => 215777)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -412,7 +412,7 @@
     std::unique_ptr<ArrayIteratorAdaptiveWatchpoint> m_arrayPrototypeSymbolIteratorWatchpoint;
     std::unique_ptr<ArrayIteratorAdaptiveWatchpoint> m_arrayIteratorPrototypeNext;
 
-    bool isArrayIteratorProtocolFastAndNonObservable();
+    bool isArrayPrototypeIteratorProtocolFastAndNonObservable();
 
     TemplateRegistry m_templateRegistry;
 

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObjectInlines.h (215776 => 215777)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObjectInlines.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObjectInlines.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -53,7 +53,7 @@
 }
 
 
-ALWAYS_INLINE bool JSGlobalObject::isArrayIteratorProtocolFastAndNonObservable()
+ALWAYS_INLINE bool JSGlobalObject::isArrayPrototypeIteratorProtocolFastAndNonObservable()
 {
     // We're fast if we don't have any indexed properties on the prototype.
     // We're non-observable if the iteration protocol hasn't changed.

Modified: trunk/Source/WebCore/ChangeLog (215776 => 215777)


--- trunk/Source/WebCore/ChangeLog	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/ChangeLog	2017-04-26 00:52:35 UTC (rev 215777)
@@ -1,3 +1,40 @@
+2017-04-25  Saam Barati  <sbar...@apple.com>
+
+        JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable is wrong because it does not do the necessary checks on the base object
+        https://bugs.webkit.org/show_bug.cgi?id=171150
+        <rdar://problem/31771880>
+
+        Reviewed by Sam Weinig.
+
+        This patch moves the sequence converters to use the now fixed
+        JSArray::isArrayPrototypeIteratorProtocolFastAndNonObservable test
+        inside JSC.
+        
+        This patch also fixes a few bugs:
+        1. Converting to a sequence of numbers must prove that the JSArray
+        is filled only with Int32/Double. If there is a chance the array
+        contains objects, the conversion to a numeric IDLType can be observable
+        (via valueOf()), and can change the iterator protocol.
+        2. There are other conversions that can have side effects a-la valueOf().
+        This patch introduces a new static constant in the various Converter
+        classes that tell the sequence converter if the conversion operation
+        can have JS side effects. If it does have side effects, we fall back to
+        the generic conversion that uses the iterator protocol. If not, we can
+        do a faster version that iterates over each element of the array,
+        reading it directly, and converting it.
+
+        Tests: js/sequence-iterator-protocol-2.html
+               js/sequence-iterator-protocol.html
+
+        * bindings/js/JSDOMConvertAny.h: Does not have side effects.
+        * bindings/js/JSDOMConvertBase.h: We pessimistically assume inside DefaultConverter that converions have side effects.
+        * bindings/js/JSDOMConvertBoolean.h: Does not have side effects.
+        * bindings/js/JSDOMConvertCallbacks.h: Does not have side effects.
+        * bindings/js/JSDOMConvertObject.h: Does not have side effects.
+        * bindings/js/JSDOMConvertSequences.h:
+        (WebCore::Detail::NumericSequenceConverter::convert):
+        (WebCore::Detail::SequenceConverter::convert):
+
 2017-04-25  Michael Saboff  <msab...@apple.com>
 
         Call bmalloc scavenger first when handling a memory pressure event

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertAny.h (215776 => 215777)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertAny.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertAny.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -32,7 +32,9 @@
 
 template<> struct Converter<IDLAny> : DefaultConverter<IDLAny> {
     using ReturnType = JSC::JSValue;
-    
+
+    static constexpr bool conversionHasSideEffects = false;
+
     static JSC::JSValue convert(JSC::ExecState&, JSC::JSValue value)
     {
         return value;

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertBase.h (215776 => 215777)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertBase.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertBase.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -178,6 +178,16 @@
 
 template<typename T> struct DefaultConverter {
     using ReturnType = typename T::ImplementationType;
+
+    // We assume the worst, subtypes can override to be less pessimistic.
+    // An example of something that can have side effects
+    // is having a converter that does JSC::JSValue::toNumber.
+    // toNumber() in _javascript_ can call arbitrary JS functions.
+    //
+    // An example of something that does not have side effects
+    // is something having a converter that does JSC::JSValue::toBoolean.
+    // toBoolean() in JS can't call arbitrary functions.
+    static constexpr bool conversionHasSideEffects = true;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertBoolean.h (215776 => 215777)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertBoolean.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertBoolean.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -31,6 +31,9 @@
 namespace WebCore {
 
 template<> struct Converter<IDLBoolean> : DefaultConverter<IDLBoolean> {
+
+    static constexpr bool conversionHasSideEffects = false;
+
     static bool convert(JSC::ExecState& state, JSC::JSValue value)
     {
         return value.toBoolean(&state);

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertCallbacks.h (215776 => 215777)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertCallbacks.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertCallbacks.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -31,6 +31,9 @@
 namespace WebCore {
 
 template<typename T> struct Converter<IDLCallbackFunction<T>> : DefaultConverter<IDLCallbackFunction<T>> {
+
+    static constexpr bool conversionHasSideEffects = false;
+
     template<typename ExceptionThrower = DefaultExceptionThrower>
     static RefPtr<T> convert(JSC::ExecState& state, JSC::JSValue value, JSDOMGlobalObject& globalObject, ExceptionThrower&& exceptionThrower = ExceptionThrower())
     {

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertObject.h (215776 => 215777)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertObject.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertObject.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -31,6 +31,9 @@
 namespace WebCore {
 
 template<> struct Converter<IDLObject> : DefaultConverter<IDLObject> {
+
+    static constexpr bool conversionHasSideEffects = false;
+
     template<typename ExceptionThrower = DefaultExceptionThrower>
     static JSC::Strong<JSC::JSObject> convert(JSC::ExecState& state, JSC::JSValue value, ExceptionThrower&& exceptionThrower = ExceptionThrower())
     {

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertSequences.h (215776 => 215777)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertSequences.h	2017-04-26 00:42:32 UTC (rev 215776)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertSequences.h	2017-04-26 00:52:35 UTC (rev 215777)
@@ -41,7 +41,11 @@
 
     static ReturnType convert(JSC::ExecState& state, JSC::JSObject* jsObject)
     {
-        ReturnType result;
+        return convert(state, jsObject, ReturnType());
+    }
+
+    static ReturnType convert(JSC::ExecState& state, JSC::JSObject* jsObject, ReturnType&& result)
+    {
         forEachInIterable(&state, jsObject, [&result](JSC::VM& vm, JSC::ExecState* state, JSC::JSValue jsValue) {
             auto scope = DECLARE_THROW_SCOPE(vm);
 
@@ -78,35 +82,27 @@
             return GenericConverter::convert(state, object);
 
         JSC::JSArray* array = JSC::asArray(object);
-        if (!array->globalObject()->isArrayIteratorProtocolFastAndNonObservable())
+        if (!array->isIteratorProtocolFastAndNonObservable())
             return GenericConverter::convert(state, object);
 
         unsigned length = array->length();
-
         ReturnType result;
+        // If we're not an int32/double array, it's possible that converting a
+        // JSValue to a number could cause the iterator protocol to change, hence,
+        // we may need more capacity, or less. In such cases, we use the length
+        // as a proxy for the capacity we will most likely need (it's unlikely that 
+        // a program is written with a valueOf that will augment the iterator protocol).
+        // If we are an int32/double array, then length is precisely the capacity we need.
         if (!result.tryReserveCapacity(length)) {
             // FIXME: Is the right exception to throw?
             throwTypeError(&state, scope);
             return { };
         }
-
+        
         JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask;
+        if (indexingType != JSC::Int32Shape && indexingType != JSC::DoubleShape)
+            return GenericConverter::convert(state, object, WTFMove(result));
 
-        if (indexingType == JSC::ContiguousShape) {
-            for (unsigned i = 0; i < length; i++) {
-                auto indexValue = array->butterfly()->contiguous()[i].get();
-                if (!indexValue)
-                    result.uncheckedAppend(0);
-                else {
-                    auto convertedValue = Converter<IDLType>::convert(state, indexValue);
-                    RETURN_IF_EXCEPTION(scope, { });
-
-                    result.uncheckedAppend(convertedValue);
-                }
-            }
-            return result;
-        }
-        
         if (indexingType == JSC::Int32Shape) {
             for (unsigned i = 0; i < length; i++) {
                 auto indexValue = array->butterfly()->contiguousInt32()[i].get();
@@ -119,31 +115,15 @@
             return result;
         }
 
-        if (indexingType == JSC::DoubleShape) {
-            for (unsigned i = 0; i < length; i++) {
-                auto doubleValue = array->butterfly()->contiguousDouble()[i];
-                if (std::isnan(doubleValue))
-                    result.uncheckedAppend(0);
-                else {
-                    auto convertedValue = Converter<IDLType>::convert(state, scope, doubleValue);
-                    RETURN_IF_EXCEPTION(scope, { });
-
-                    result.uncheckedAppend(convertedValue);
-                }
-            }
-            return result;
-        }
-
+        ASSERT(indexingType == JSC::DoubleShape);
         for (unsigned i = 0; i < length; i++) {
-            auto indexValue = array->getDirectIndex(&state, i);
-            RETURN_IF_EXCEPTION(scope, { });
-            
-            if (!indexValue)
+            auto doubleValue = array->butterfly()->contiguousDouble()[i];
+            if (std::isnan(doubleValue))
                 result.uncheckedAppend(0);
             else {
-                auto convertedValue = Converter<IDLType>::convert(state, indexValue);
+                auto convertedValue = Converter<IDLType>::convert(state, scope, doubleValue);
                 RETURN_IF_EXCEPTION(scope, { });
-                
+
                 result.uncheckedAppend(convertedValue);
             }
         }
@@ -167,11 +147,14 @@
         }
 
         JSC::JSObject* object = JSC::asObject(value);
+        if (Converter<IDLType>::conversionHasSideEffects)
+            return GenericConverter::convert(state, object);
+
         if (!JSC::isJSArray(object))
             return GenericConverter::convert(state, object);
 
         JSC::JSArray* array = JSC::asArray(object);
-        if (!array->globalObject()->isArrayIteratorProtocolFastAndNonObservable())
+        if (!array->isIteratorProtocolFastAndNonObservable())
             return GenericConverter::convert(state, object);
 
         unsigned length = array->length();
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to