TypedArrays need more isNeutered checks.
authorkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Jul 2016 16:27:35 +0000 (16:27 +0000)
committerkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Jul 2016 16:27:35 +0000 (16:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=159231

Reviewed by Filip Pizlo.

Source/JavaScriptCore:

According to the ES6 spec if a user tries to get, set, or define a
property on a neutered TypedArray we should throw an
exception. Currently, if a user tries to get an out of bounds
access on a TypedArray we will always OSR.  This makes handling
the exception easy as all we need to do is make out of bounds gets
in PolymorphicAccess go to the slow path, which will then throw
the appropriate exception. For the case of set, we need ensure we
don't OSR on each out of bounds put since, for some confusing
reason, people do this.  Thus, for GetByVal in the DFG/FTL if the
user accesses out of bounds we then need to check if the view has
been neutered. If it is neutered then we will OSR.

Additionally, this patch adds a bunch of isNeutered checks to
various prototype functions for TypedArray, which are needed for
correctness.

* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::jumpForTypedArrayIsNeuteredIfOutOfBounds):
(JSC::DFG::SpeculativeJIT::compilePutByValForIntTypedArray):
(JSC::DFG::SpeculativeJIT::compilePutByValForFloatTypedArray):
* dfg/DFGSpeculativeJIT.h:
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compilePutByVal):
(JSC::FTL::DFG::LowerDFGToB3::speculateTypedArrayIsNotNeutered):
* jit/JITPropertyAccess.cpp:
(JSC::JIT::emitIntTypedArrayPutByVal):
(JSC::JIT::emitFloatTypedArrayPutByVal):
* runtime/JSArrayBufferView.h:
* runtime/JSCJSValue.h:
(JSC::encodedJSUndefined):
(JSC::encodedJSValue):
* runtime/JSGenericTypedArrayView.h:
* runtime/JSGenericTypedArrayViewInlines.h:
(JSC::JSGenericTypedArrayView<Adaptor>::throwNeuteredTypedArrayTypeError):
(JSC::JSGenericTypedArrayView<Adaptor>::getOwnPropertySlot):
(JSC::JSGenericTypedArrayView<Adaptor>::put):
(JSC::JSGenericTypedArrayView<Adaptor>::defineOwnProperty):
(JSC::JSGenericTypedArrayView<Adaptor>::deleteProperty):
(JSC::JSGenericTypedArrayView<Adaptor>::getOwnPropertySlotByIndex):
(JSC::JSGenericTypedArrayView<Adaptor>::putByIndex):
(JSC::JSGenericTypedArrayView<Adaptor>::deletePropertyByIndex):
* runtime/JSGenericTypedArrayViewPrototypeFunctions.h:
(JSC::genericTypedArrayViewProtoFuncCopyWithin):
(JSC::genericTypedArrayViewProtoFuncFill):
(JSC::genericTypedArrayViewProtoFuncIndexOf):
(JSC::genericTypedArrayViewProtoFuncJoin):
(JSC::genericTypedArrayViewProtoFuncLastIndexOf):
(JSC::genericTypedArrayViewProtoFuncSlice):
(JSC::genericTypedArrayViewProtoFuncSubarray):
* tests/stress/fold-typed-array-properties.js:
* tests/stress/typedarray-access-monomorphic-neutered.js: Added.
(check):
(test):
(testFTL):
* tests/stress/typedarray-access-neutered.js: Added.
(check):
(test):
* tests/stress/typedarray-functions-with-neutered.js:
(defaultForArg):
(callWithArgs):
(checkArgumentsForType):
(checkArguments):
* tests/stress/typedarray-view-string-properties-neutered.js: Added.
(call):
(test):

LayoutTests:

Update tests that assert that we can access indexed properties on
a neutered TypedArray.

* fast/canvas/webgl/script-tests/arraybuffer-transfer-of-control.js:
(assertViewClosed):
* js/dom/dfg-typed-array-neuter-expected.txt:
* js/dom/script-tests/dfg-typed-array-neuter.js:

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

19 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/canvas/webgl/script-tests/arraybuffer-transfer-of-control.js
LayoutTests/js/dom/dfg-typed-array-neuter-expected.txt
LayoutTests/js/dom/script-tests/dfg-typed-array-neuter.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JITPropertyAccess.cpp
Source/JavaScriptCore/runtime/JSArrayBufferView.h
Source/JavaScriptCore/runtime/JSCJSValue.h
Source/JavaScriptCore/runtime/JSGenericTypedArrayView.h
Source/JavaScriptCore/runtime/JSGenericTypedArrayViewInlines.h
Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeFunctions.h
Source/JavaScriptCore/tests/stress/fold-typed-array-properties.js
Source/JavaScriptCore/tests/stress/typedarray-access-monomorphic-neutered.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/typedarray-access-neutered.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/typedarray-functions-with-neutered.js
Source/JavaScriptCore/tests/stress/typedarray-view-string-properties-neutered.js [new file with mode: 0644]

index 473373b..91e6ce4 100644 (file)
@@ -1,3 +1,18 @@
+2016-07-08  Keith Miller  <keith_miller@apple.com>
+
+        TypedArrays need more isNeutered checks.
+        https://bugs.webkit.org/show_bug.cgi?id=159231
+
+        Reviewed by Filip Pizlo.
+
+        Update tests that assert that we can access indexed properties on
+        a neutered TypedArray.
+
+        * fast/canvas/webgl/script-tests/arraybuffer-transfer-of-control.js:
+        (assertViewClosed):
+        * js/dom/dfg-typed-array-neuter-expected.txt:
+        * js/dom/script-tests/dfg-typed-array-neuter.js:
+
 2016-07-08  Frederic Wang  <fwang@igalia.com>
 
         Use Fraction* parameters from the OpenType MATH table
index 19a384b..637f7f9 100644 (file)
@@ -91,35 +91,36 @@ function assertViewClosed(testName, view)
             }
             try {
                 var v = view[0];
-                if (v !== undefined) {
-                    testFailed(testName + ": index get on a closed view did not return undefined.");
+                testFailed(testName + ": index get on a closed view did not throw an exception");
+                return false;
+            } catch(xn) {
+                if (xn != "TypeError: Underlying ArrayBuffer has been detached from the view") {
+                    testFailed(testName + ": index get on a closed view threw the wrong exception: " + xn);
                     return false;
                 }
-            } catch(xn) {
-                testFailed(testName + ": index get on a closed view threw an exception: " + xn);
-                return false;
             }
+
             try {
                 view[0] = 42;
-                var v = view[0];
-                if (v !== undefined) {
-                    testFailed(testName + ": index set then get on a closed view did not return undefined.");
+                testFailed(testName + ": index set on a closed view did not throw an exception");
+                return false;
+            } catch(xn) {
+                if (xn != "TypeError: Underlying ArrayBuffer has been detached from the view") {
+                    testFailed(testName + ": index set then get on a closed view threw the wrong exception: " + xn);
                     return false;
                 }
-            } catch(xn) {
-                testFailed(testName + ": index set then get on a closed view threw an exception: " + xn);
-                return false;
             }
-            try {
-                view.get(0);
-                testFailed(testName + ": get on a closed view succeeded");
-                return false;
-            } catch (xn) { }
+
             try {
                 view.set(0, 1);
                 testFailed(testName + ": set on a closed view succeeded");
                 return false;
-            } catch (xn) { }
+            } catch (xn) {
+                if (xn != "TypeError: Underlying ArrayBuffer has been detached from the view") {
+                    testFailed(testName + ": set on a closed view threw the wrong exception: " + xn);
+                    return false;
+                }
+            }
         } else {
             try {
                 view.getInt8(0);
index 2ae1b18..556bd4d 100644 (file)
@@ -8,9 +8,9 @@ PASS array[0] is 42
 PASS foo(array) is 100
 PASS bar(array) is 42
 PASS array.length is 0
-PASS array[0] is void 0
+PASS array[0] threw exception TypeError: Underlying ArrayBuffer has been detached from the view.
 PASS foo(array) is 0
-PASS bar(array) is void 0
+PASS bar(array) threw exception TypeError: Underlying ArrayBuffer has been detached from the view.
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 929ae2b..78cafc0 100644 (file)
@@ -24,7 +24,7 @@ shouldBe("bar(array)", "42");
 window.postMessage(array, "*", [array.buffer]);
 
 shouldBe("array.length", "0");
-shouldBe("array[0]", "void 0");
+shouldThrow("array[0]");
 
 shouldBe("foo(array)", "0");
-shouldBe("bar(array)", "void 0");
+shouldThrow("bar(array)");
index f65b885..954c818 100644 (file)
@@ -1,3 +1,76 @@
+2016-07-08  Keith Miller  <keith_miller@apple.com>
+
+        TypedArrays need more isNeutered checks.
+        https://bugs.webkit.org/show_bug.cgi?id=159231
+
+        Reviewed by Filip Pizlo.
+
+        According to the ES6 spec if a user tries to get, set, or define a
+        property on a neutered TypedArray we should throw an
+        exception. Currently, if a user tries to get an out of bounds
+        access on a TypedArray we will always OSR.  This makes handling
+        the exception easy as all we need to do is make out of bounds gets
+        in PolymorphicAccess go to the slow path, which will then throw
+        the appropriate exception. For the case of set, we need ensure we
+        don't OSR on each out of bounds put since, for some confusing
+        reason, people do this.  Thus, for GetByVal in the DFG/FTL if the
+        user accesses out of bounds we then need to check if the view has
+        been neutered. If it is neutered then we will OSR.
+
+        Additionally, this patch adds a bunch of isNeutered checks to
+        various prototype functions for TypedArray, which are needed for
+        correctness.
+
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::jumpForTypedArrayIsNeuteredIfOutOfBounds):
+        (JSC::DFG::SpeculativeJIT::compilePutByValForIntTypedArray):
+        (JSC::DFG::SpeculativeJIT::compilePutByValForFloatTypedArray):
+        * dfg/DFGSpeculativeJIT.h:
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compilePutByVal):
+        (JSC::FTL::DFG::LowerDFGToB3::speculateTypedArrayIsNotNeutered):
+        * jit/JITPropertyAccess.cpp:
+        (JSC::JIT::emitIntTypedArrayPutByVal):
+        (JSC::JIT::emitFloatTypedArrayPutByVal):
+        * runtime/JSArrayBufferView.h:
+        * runtime/JSCJSValue.h:
+        (JSC::encodedJSUndefined):
+        (JSC::encodedJSValue):
+        * runtime/JSGenericTypedArrayView.h:
+        * runtime/JSGenericTypedArrayViewInlines.h:
+        (JSC::JSGenericTypedArrayView<Adaptor>::throwNeuteredTypedArrayTypeError):
+        (JSC::JSGenericTypedArrayView<Adaptor>::getOwnPropertySlot):
+        (JSC::JSGenericTypedArrayView<Adaptor>::put):
+        (JSC::JSGenericTypedArrayView<Adaptor>::defineOwnProperty):
+        (JSC::JSGenericTypedArrayView<Adaptor>::deleteProperty):
+        (JSC::JSGenericTypedArrayView<Adaptor>::getOwnPropertySlotByIndex):
+        (JSC::JSGenericTypedArrayView<Adaptor>::putByIndex):
+        (JSC::JSGenericTypedArrayView<Adaptor>::deletePropertyByIndex):
+        * runtime/JSGenericTypedArrayViewPrototypeFunctions.h:
+        (JSC::genericTypedArrayViewProtoFuncCopyWithin):
+        (JSC::genericTypedArrayViewProtoFuncFill):
+        (JSC::genericTypedArrayViewProtoFuncIndexOf):
+        (JSC::genericTypedArrayViewProtoFuncJoin):
+        (JSC::genericTypedArrayViewProtoFuncLastIndexOf):
+        (JSC::genericTypedArrayViewProtoFuncSlice):
+        (JSC::genericTypedArrayViewProtoFuncSubarray):
+        * tests/stress/fold-typed-array-properties.js:
+        * tests/stress/typedarray-access-monomorphic-neutered.js: Added.
+        (check):
+        (test):
+        (testFTL):
+        * tests/stress/typedarray-access-neutered.js: Added.
+        (check):
+        (test):
+        * tests/stress/typedarray-functions-with-neutered.js:
+        (defaultForArg):
+        (callWithArgs):
+        (checkArgumentsForType):
+        (checkArguments):
+        * tests/stress/typedarray-view-string-properties-neutered.js: Added.
+        (call):
+        (test):
+
 2016-07-08  Youenn Fablet  <youenn@apple.com>
 
         Generate WebCore builtin wrapper files
index f7391dc..130caea 100644 (file)
@@ -2613,6 +2613,31 @@ void SpeculativeJIT::emitTypedArrayBoundsCheck(Node* node, GPRReg baseGPR, GPRRe
     speculationCheck(OutOfBounds, JSValueRegs(), 0, jump);
 }
 
+JITCompiler::Jump SpeculativeJIT::jumpForTypedArrayIsNeuteredIfOutOfBounds(Node* node, GPRReg base, JITCompiler::Jump outOfBounds)
+{
+    JITCompiler::Jump done;
+    if (outOfBounds.isSet()) {
+        done = m_jit.jump();
+        if (node->arrayMode().isInBounds())
+            speculationCheck(OutOfBounds, JSValueSource(), 0, outOfBounds);
+        else {
+            outOfBounds.link(&m_jit);
+
+            JITCompiler::Jump notWasteful = m_jit.branch32(
+                MacroAssembler::NotEqual,
+                MacroAssembler::Address(base, JSArrayBufferView::offsetOfMode()),
+                TrustedImm32(WastefulTypedArray));
+
+            JITCompiler::Jump hasNullVector = m_jit.branchTestPtr(
+                MacroAssembler::Zero,
+                MacroAssembler::Address(base, JSArrayBufferView::offsetOfVector()));
+            speculationCheck(Uncountable, JSValueSource(), node, hasNullVector);
+            notWasteful.link(&m_jit);
+        }
+    }
+    return done;
+}
+
 void SpeculativeJIT::compileGetByValOnIntTypedArray(Node* node, TypedArrayType type)
 {
     ASSERT(isInt(type));
@@ -2790,11 +2815,7 @@ void SpeculativeJIT::compilePutByValForIntTypedArray(GPRReg base, GPRReg propert
     ASSERT_UNUSED(valueGPR, valueGPR != property);
     ASSERT(valueGPR != base);
     ASSERT(valueGPR != storageReg);
-    MacroAssembler::Jump outOfBounds = jumpForTypedArrayOutOfBounds(node, base, property);
-    if (node->arrayMode().isInBounds() && outOfBounds.isSet()) {
-        speculationCheck(OutOfBounds, JSValueSource(), 0, outOfBounds);
-        outOfBounds = MacroAssembler::Jump();
-    }
+    JITCompiler::Jump outOfBounds = jumpForTypedArrayOutOfBounds(node, base, property);
 
     switch (elementSize(type)) {
     case 1:
@@ -2809,8 +2830,10 @@ void SpeculativeJIT::compilePutByValForIntTypedArray(GPRReg base, GPRReg propert
     default:
         CRASH();
     }
-    if (outOfBounds.isSet())
-        outOfBounds.link(&m_jit);
+
+    JITCompiler::Jump done = jumpForTypedArrayIsNeuteredIfOutOfBounds(node, base, outOfBounds);
+    if (done.isSet())
+        done.link(&m_jit);
     noResult(node);
 }
 
@@ -2865,10 +2888,6 @@ void SpeculativeJIT::compilePutByValForFloatTypedArray(GPRReg base, GPRReg prope
     ASSERT_UNUSED(baseUse, node->arrayMode().alreadyChecked(m_jit.graph(), node, m_state.forNode(baseUse)));
     
     MacroAssembler::Jump outOfBounds = jumpForTypedArrayOutOfBounds(node, base, property);
-    if (node->arrayMode().isInBounds() && outOfBounds.isSet()) {
-        speculationCheck(OutOfBounds, JSValueSource(), 0, outOfBounds);
-        outOfBounds = MacroAssembler::Jump();
-    }
     
     switch (elementSize(type)) {
     case 4: {
@@ -2883,8 +2902,10 @@ void SpeculativeJIT::compilePutByValForFloatTypedArray(GPRReg base, GPRReg prope
     default:
         RELEASE_ASSERT_NOT_REACHED();
     }
-    if (outOfBounds.isSet())
-        outOfBounds.link(&m_jit);
+
+    JITCompiler::Jump done = jumpForTypedArrayIsNeuteredIfOutOfBounds(node, base, outOfBounds);
+    if (done.isSet())
+        done.link(&m_jit);
     noResult(node);
 }
 
index b77ead6..6ca60bf 100644 (file)
@@ -2521,6 +2521,7 @@ public:
     void compileConstantStoragePointer(Node*);
     void compileGetIndexedPropertyStorage(Node*);
     JITCompiler::Jump jumpForTypedArrayOutOfBounds(Node*, GPRReg baseGPR, GPRReg indexGPR);
+    JITCompiler::Jump jumpForTypedArrayIsNeuteredIfOutOfBounds(Node*, GPRReg baseGPR, JITCompiler::Jump outOfBounds);
     void emitTypedArrayBoundsCheck(Node*, GPRReg baseGPR, GPRReg indexGPR);
     void compileGetTypedArrayByteOffset(Node*);
     void compileGetByValOnIntTypedArray(Node*, TypedArrayType);
index 7c9cd82..3fb7873 100644 (file)
@@ -3317,27 +3317,32 @@ private:
                         DFG_CRASH(m_graph, m_node, "Bad typed array type");
                     }
                 }
-                
+
                 if (m_node->arrayMode().isInBounds() || m_node->op() == PutByValAlias)
                     m_out.store(valueToStore, pointer, storeType);
                 else {
                     LBasicBlock isInBounds = m_out.newBlock();
+                    LBasicBlock isOutOfBounds = m_out.newBlock();
                     LBasicBlock continuation = m_out.newBlock();
                     
                     m_out.branch(
                         m_out.aboveOrEqual(index, lowInt32(child5)),
-                        unsure(continuation), unsure(isInBounds));
+                        unsure(isOutOfBounds), unsure(isInBounds));
                     
-                    LBasicBlock lastNext = m_out.appendTo(isInBounds, continuation);
+                    LBasicBlock lastNext = m_out.appendTo(isInBounds, isOutOfBounds);
                     m_out.store(valueToStore, pointer, storeType);
                     m_out.jump(continuation);
+
+                    m_out.appendTo(isOutOfBounds, continuation);
+                    speculateTypedArrayIsNotNeutered(base);
+                    m_out.jump(continuation);
                     
                     m_out.appendTo(continuation, lastNext);
                 }
                 
                 return;
             }
-            
+
             DFG_CRASH(m_graph, m_node, "Bad array type");
             break;
         }
@@ -10587,7 +10592,24 @@ private:
         LValue value = lowJSValue(edge, ManualOperandSpeculation);
         typeCheck(jsValueValue(value), edge, SpecMisc, isNotMisc(value));
     }
-    
+
+    void speculateTypedArrayIsNotNeutered(LValue base)
+    {
+        LBasicBlock isWasteful = m_out.newBlock();
+        LBasicBlock continuation = m_out.newBlock();
+
+        LValue mode = m_out.load32(base, m_heaps.JSArrayBufferView_mode);
+        m_out.branch(m_out.equal(mode, m_out.constInt32(WastefulTypedArray)),
+            unsure(isWasteful), unsure(continuation));
+
+        LBasicBlock lastNext = m_out.appendTo(isWasteful, continuation);
+        LValue vector = m_out.loadPtr(base, m_heaps.JSArrayBufferView_vector);
+        speculate(Uncountable, jsValueValue(vector), m_node, m_out.notZero64(vector));
+        m_out.jump(continuation);
+
+        m_out.appendTo(continuation, lastNext);
+    }
+
     bool masqueradesAsUndefinedWatchpointIsStillValid()
     {
         return m_graph.masqueradesAsUndefinedWatchpointIsStillValid(m_node->origin.semantic);
index 4383a13..9d1344e 100644 (file)
@@ -1655,7 +1655,7 @@ JIT::JumpList JIT::emitIntTypedArrayPutByVal(Instruction* currentInstruction, Pa
     badType = patchableBranch32(NotEqual, earlyScratch, TrustedImm32(typeForTypedArrayType(type)));
     Jump inBounds = branch32(Below, property, Address(base, JSArrayBufferView::offsetOfLength()));
     emitArrayProfileOutOfBoundsSpecialCase(profile);
-    Jump done = jump();
+    slowCases.append(jump());
     inBounds.link(this);
     
 #if USE(JSVALUE64)
@@ -1697,8 +1697,6 @@ JIT::JumpList JIT::emitIntTypedArrayPutByVal(Instruction* currentInstruction, Pa
         CRASH();
     }
     
-    done.link(this);
-    
     return slowCases;
 }
 
@@ -1727,7 +1725,7 @@ JIT::JumpList JIT::emitFloatTypedArrayPutByVal(Instruction* currentInstruction,
     badType = patchableBranch32(NotEqual, earlyScratch, TrustedImm32(typeForTypedArrayType(type)));
     Jump inBounds = branch32(Below, property, Address(base, JSArrayBufferView::offsetOfLength()));
     emitArrayProfileOutOfBoundsSpecialCase(profile);
-    Jump done = jump();
+    slowCases.append(jump());
     inBounds.link(this);
     
 #if USE(JSVALUE64)
@@ -1767,8 +1765,6 @@ JIT::JumpList JIT::emitFloatTypedArrayPutByVal(Instruction* currentInstruction,
         CRASH();
     }
     
-    done.link(this);
-    
     return slowCases;
 }
 
index 96132c2..8b9c8b4 100644 (file)
@@ -53,7 +53,7 @@ class LLIntOffsetsExtractor;
 // Typed array views have different modes depending on how big they are and
 // whether the user has done anything that requires a separate backing
 // buffer or the DOM-specified neutering capabilities.
-enum TypedArrayMode {
+enum TypedArrayMode : uint32_t {
     // Small and fast typed array. B is unused, V points to a vector
     // allocated in copied space, and M = FastTypedArray. V's liveness is
     // determined entirely by the view's liveness.
index 38ed820..40ee7fa 100644 (file)
@@ -583,6 +583,16 @@ ALWAYS_INLINE JSValue jsNumber(unsigned long long i)
     return JSValue(i);
 }
 
+ALWAYS_INLINE EncodedJSValue encodedJSUndefined()
+{
+    return JSValue::encode(jsUndefined());
+}
+
+ALWAYS_INLINE EncodedJSValue encodedJSValue()
+{
+    return JSValue::encode(JSValue());
+}
+
 inline bool operator==(const JSValue a, const JSCell* b) { return a == JSValue(b); }
 inline bool operator==(const JSCell* a, const JSValue b) { return JSValue(a) == b; }
 
index 407a712..5841f27 100644 (file)
@@ -269,10 +269,12 @@ public:
     ArrayBuffer* existingBuffer();
 
     static const TypedArrayType TypedArrayStorageType = Adaptor::typeValue;
-    
+
 protected:
     friend struct TypedArrayClassInfos;
 
+    static EncodedJSValue throwNeuteredTypedArrayTypeError(ExecState*, EncodedJSValue, PropertyName);
+
     static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&);
     static bool put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&);
     static bool defineOwnProperty(JSObject*, ExecState*, PropertyName, const PropertyDescriptor&, bool shouldThrow);
index 0be2d8a..d2e8969 100644 (file)
@@ -37,6 +37,8 @@
 
 namespace JSC {
 
+static const char* typedArrayBufferHasBeenDetachedErrorMessage = "Underlying ArrayBuffer has been detached from the view";
+
 template<typename Adaptor>
 JSGenericTypedArrayView<Adaptor>::JSGenericTypedArrayView(
     VM& vm, ConstructionContext& context)
@@ -288,14 +290,28 @@ ArrayBuffer* JSGenericTypedArrayView<Adaptor>::existingBuffer()
 }
 
 template<typename Adaptor>
+EncodedJSValue JSGenericTypedArrayView<Adaptor>::throwNeuteredTypedArrayTypeError(ExecState* exec, EncodedJSValue object, PropertyName)
+{
+    ASSERT_UNUSED(object, jsCast<JSGenericTypedArrayView*>(JSValue::decode(object))->isNeutered());
+    return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
+}
+
+template<typename Adaptor>
 bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlot(
     JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
 
-    Optional<uint32_t> index = parseIndex(propertyName);
-    if (index && thisObject->canGetIndexQuickly(index.value())) {
-        slot.setValue(thisObject, DontDelete | ReadOnly, thisObject->getIndexQuickly(index.value()));
+    if (Optional<uint32_t> index = parseIndex(propertyName)) {
+        if (thisObject->isNeutered()) {
+            slot.setCustom(thisObject, None, throwNeuteredTypedArrayTypeError);
+            return true;
+        }
+
+        if (thisObject->canGetIndexQuickly(index.value()))
+            slot.setValue(thisObject, DontDelete | ReadOnly, thisObject->getIndexQuickly(index.value()));
+        else
+            slot.setValue(thisObject, DontDelete | ReadOnly, jsUndefined());
         return true;
     }
     
@@ -308,7 +324,10 @@ bool JSGenericTypedArrayView<Adaptor>::put(
     PutPropertySlot& slot)
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
-    
+
+    if (thisObject->isNeutered())
+        return reject(exec, true, typedArrayBufferHasBeenDetachedErrorMessage);
+
     // https://tc39.github.io/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
     // Ignore the receiver even if the receiver is altered to non base value.
     // 9.4.5.5-2-b-i Return ? IntegerIndexedElementSet(O, numericIndex, V).
@@ -324,12 +343,20 @@ bool JSGenericTypedArrayView<Adaptor>::defineOwnProperty(
     const PropertyDescriptor& descriptor, bool shouldThrow)
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
-    
-    // This is matching Firefox behavior. In particular, it rejects all attempts to
-    // defineOwnProperty for indexed properties on typed arrays, even if they're out
-    // of bounds.
-    if (parseIndex(propertyName))
-        return reject(exec, shouldThrow, "Attempting to write to a read-only typed array property.");
+
+    if (parseIndex(propertyName)) {
+        if (descriptor.isAccessorDescriptor())
+            return reject(exec, shouldThrow, "Attempting to store accessor indexed property on a typed array.");
+
+        if (descriptor.attributes() & (DontEnum | DontDelete | ReadOnly))
+            return reject(exec, shouldThrow, "Attempting to store non-enumerable, non-configurable or non-writable indexed property on a typed array.");
+
+        if (descriptor.value()) {
+            PutPropertySlot unused(JSValue(thisObject), shouldThrow);
+            return thisObject->put(thisObject, exec, propertyName, descriptor.value(), unused);
+        }
+        return true;
+    }
     
     return Base::defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow);
 }
@@ -339,7 +366,10 @@ bool JSGenericTypedArrayView<Adaptor>::deleteProperty(
     JSCell* cell, ExecState* exec, PropertyName propertyName)
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
-    
+
+    if (thisObject->isNeutered())
+        return reject(exec, true, typedArrayBufferHasBeenDetachedErrorMessage);
+
     if (parseIndex(propertyName))
         return false;
     
@@ -351,7 +381,12 @@ bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlotByIndex(
     JSObject* object, ExecState* exec, unsigned propertyName, PropertySlot& slot)
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object);
-    
+
+    if (thisObject->isNeutered()) {
+        slot.setCustom(thisObject, None, throwNeuteredTypedArrayTypeError);
+        return true;
+    }
+
     if (propertyName > MAX_ARRAY_INDEX) {
         return thisObject->methodTable()->getOwnPropertySlot(
             thisObject, exec, Identifier::from(exec, propertyName), slot);
@@ -369,7 +404,10 @@ bool JSGenericTypedArrayView<Adaptor>::putByIndex(
     JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
 {
     JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell);
-    
+
+    if (thisObject->isNeutered())
+        return reject(exec, true, typedArrayBufferHasBeenDetachedErrorMessage);
+
     if (propertyName > MAX_ARRAY_INDEX) {
         PutPropertySlot slot(JSValue(thisObject), shouldThrow);
         return thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot);
@@ -382,12 +420,7 @@ template<typename Adaptor>
 bool JSGenericTypedArrayView<Adaptor>::deletePropertyByIndex(
     JSCell* cell, ExecState* exec, unsigned propertyName)
 {
-    if (propertyName > MAX_ARRAY_INDEX) {
-        return cell->methodTable()->deleteProperty(
-            cell, exec, Identifier::from(exec, propertyName));
-    }
-    
-    return false;
+    return cell->methodTable()->deleteProperty(cell, exec, Identifier::from(exec, propertyName));
 }
 
 template<typename Adaptor>
index 1f9bc24..1095ab3 100644 (file)
@@ -42,8 +42,6 @@
 
 namespace JSC {
 
-static const char* typedArrayBufferHasBeenDetachedErrorMessage = "Underlying ArrayBuffer has been detached from the view";
-
 // This implements 22.2.4.7 TypedArraySpeciesCreate
 // Note, that this function throws.
 template<typename Functor>
@@ -140,20 +138,24 @@ template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncCopyWithin(ExecState* exec)
 {
     // 22.2.3.5
+    VM& vm = exec->vm();
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
-    if (exec->argumentCount() < 2)
-        return throwVMTypeError(exec, ASCIILiteral("Expected at least two arguments"));
-
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
-
     long length = thisObject->length();
     long to = argumentClampedIndexFromStartOrEnd(exec, 0, length);
+    if (vm.exception())
+        return encodedJSValue();
     long from = argumentClampedIndexFromStartOrEnd(exec, 1, length);
+    if (vm.exception())
+        return encodedJSValue();
     long final = argumentClampedIndexFromStartOrEnd(exec, 2, length, length);
+    if (vm.exception())
+        return encodedJSValue();
+
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     if (final < from)
         return JSValue::encode(exec->thisValue());
@@ -170,6 +172,7 @@ template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncFill(ExecState* exec)
 {
     // 22.2.3.8
+    VM& vm = exec->vm();
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
@@ -180,7 +183,14 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncFill(ExecState* exec)
 
     unsigned length = thisObject->length();
     unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 1, length);
+    if (vm.exception())
+        return encodedJSValue();
     unsigned end = argumentClampedIndexFromStartOrEnd(exec, 2, length, length);
+    if (vm.exception())
+        return encodedJSValue();
+
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     if (end < begin)
         return JSValue::encode(exec->thisValue());
@@ -195,6 +205,7 @@ template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIndexOf(ExecState* exec)
 {
     // 22.2.3.13
+    VM& vm = exec->vm();
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
@@ -206,6 +217,11 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncIndexOf(ExecState* ex
 
     JSValue valueToFind = exec->argument(0);
     unsigned index = argumentClampedIndexFromStartOrEnd(exec, 1, length);
+    if (vm.exception())
+        return encodedJSValue();
+
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     typename ViewClass::ElementType* array = thisObject->typedVector();
     typename ViewClass::ElementType target = ViewClass::toAdaptorNativeFromValue(exec, valueToFind);
@@ -252,6 +268,9 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncJoin(ExecState* exec)
     JSString* separatorString = separatorValue.toString(exec);
     if (exec->hadException())
         return JSValue::encode(jsUndefined());
+
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
     return joinWithSeparator(separatorString->view(exec).get());
 }
 
@@ -283,6 +302,9 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncLastIndexOf(ExecState
             index = static_cast<unsigned>(fromDouble);
     }
 
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
+
     typename ViewClass::ElementType* array = thisObject->typedVector();
     typename ViewClass::ElementType target = ViewClass::toAdaptorNativeFromValue(exec, valueToFind);
     if (exec->hadException())
@@ -363,6 +385,7 @@ template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncSlice(ExecState* exec)
 {
     // 22.2.3.26
+    VM& vm = exec->vm();
     JSFunction* callee = jsCast<JSFunction*>(exec->callee());
 
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
@@ -372,7 +395,14 @@ EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncSlice(ExecState* exec
     unsigned thisLength = thisObject->length();
 
     unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, thisLength);
+    if (vm.exception())
+        return encodedJSValue();
     unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, thisLength, thisLength);
+    if (vm.exception())
+        return encodedJSValue();
+
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     // Clamp end to begin.
     end = std::max(begin, end);
@@ -435,20 +465,25 @@ template<typename ViewClass>
 EncodedJSValue JSC_HOST_CALL genericTypedArrayViewProtoFuncSubarray(ExecState* exec)
 {
     // 22.2.3.23
+    VM& vm = exec->vm();
     JSFunction* callee = jsCast<JSFunction*>(exec->callee());
 
     ViewClass* thisObject = jsCast<ViewClass*>(exec->thisValue());
     if (thisObject->isNeutered())
         return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
-    if (!exec->argumentCount())
-        return throwVMTypeError(exec, ASCIILiteral("Expected at least one argument"));
-
     // Get the length here; later assert that the length didn't change.
     unsigned thisLength = thisObject->length();
 
     unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, thisLength);
+    if (vm.exception())
+        return encodedJSValue();
     unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, thisLength, thisLength);
+    if (vm.exception())
+        return encodedJSValue();
+
+    if (thisObject->isNeutered())
+        return throwVMTypeError(exec, typedArrayBufferHasBeenDetachedErrorMessage);
 
     // Clamp end to begin.
     end = std::max(begin, end);
index be5b482..2d17d77 100644 (file)
@@ -33,10 +33,3 @@ try {
 
 if (!didThrow)
     throw "Should have thrown.";
-
-if (a.length != 0)
-    throw "Error: bad length (end): " + a.length;
-if (a.byteOffset != 0)
-    throw "Error: bad offset (end): " + a.byteOffset;
-if (a.byteLength != 0)
-    throw "Error: bad byte length (end): " + a.byteLength;
diff --git a/Source/JavaScriptCore/tests/stress/typedarray-access-monomorphic-neutered.js b/Source/JavaScriptCore/tests/stress/typedarray-access-monomorphic-neutered.js
new file mode 100644 (file)
index 0000000..6c66570
--- /dev/null
@@ -0,0 +1,54 @@
+typedArrays = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array];
+
+
+function check(array, thunk, count) {
+    let failed = true;
+    try {
+        thunk(array, count);
+    } catch(e) {
+        if (e != "TypeError: Underlying ArrayBuffer has been detached from the view")
+            throw new Error([thunk, count, e]);
+        failed = false;
+    }
+    if (failed)
+        throw new Error([thunk, count]);
+}
+noInline(check);
+
+function test(thunk, array) {
+    let fn = Function("array", "i", thunk);
+    noInline(fn);
+    for (let i = 0; i < 10000; i++)
+        check(array, fn, i);
+}
+
+for (let constructor of typedArrays) {
+    let array = new constructor(10);
+    transferArrayBuffer(array.buffer);
+    test("array[0]", array);
+    test("delete array[0]", array);
+    test("Object.getOwnPropertyDescriptor(array, 0)", array);
+    test("Object.defineProperty(array, 0, { value: 1, writable: true, configurable: true, enumerable: true })", array);
+    test("array[0] = 1", array);
+    test("array[i] = 1", array);
+}
+
+function testFTL(thunk, array, failArray) {
+    let fn = Function("array", "i", thunk);
+    noInline(fn);
+    for (let i = 0; i < 10000; i++)
+        fn(array, i)
+    check(failArray, fn, 10000);
+}
+
+for (let constructor of typedArrays) {
+    let array = new constructor(10);
+    let failArray = new constructor(10);
+    transferArrayBuffer(failArray.buffer);
+    testFTL("array[0]", array, failArray);
+    testFTL("delete array[0]", array, failArray);
+    testFTL("Object.getOwnPropertyDescriptor(array, 0)", array, failArray);
+    testFTL("Object.defineProperty(array, 0, { value: 1, writable: true, configurable: true, enumerable: true })", array, failArray);
+    testFTL("array[0] = 1", array, failArray);
+    testFTL("array[i] = 1", array, failArray);
+}
diff --git a/Source/JavaScriptCore/tests/stress/typedarray-access-neutered.js b/Source/JavaScriptCore/tests/stress/typedarray-access-neutered.js
new file mode 100644 (file)
index 0000000..8a814f5
--- /dev/null
@@ -0,0 +1,30 @@
+typedArrays = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array];
+
+
+function check(thunk, count) {
+    let array = new constructor(10);
+    transferArrayBuffer(array.buffer);
+    let failed = true;
+    try {
+        thunk(array);
+    } catch(e) {
+        if (e != "TypeError: Underlying ArrayBuffer has been detached from the view")
+            throw new Error([thunk, count, e]);
+        failed = false;
+    }
+    if (failed)
+        throw new Error([thunk, count]);
+}
+
+function test(thunk, count) {
+    for (constructor of typedArrays)
+        check(thunk, count);
+}
+
+for (let i = 0; i < 10000; i++) {
+    test((array) => array[0], i);
+    test((array) => delete array[0], i);
+    test((array) => Object.getOwnPropertyDescriptor(array, 0), i);
+    test((array) => Object.defineProperty(array, 0, { value: 1, writable: true, configurable: true, enumerable: true }), i)
+    test((array) => array[0] = 1, i);
+}
index 0910c0c..6f68c41 100644 (file)
@@ -73,3 +73,88 @@ function test() {
 for (var i = 0; i < 1000; i++)
     test();
 
+// Test that we handle neutering for any toInteger neutering the arraybuffer.
+prototypeFunctions = [
+    { func:proto.copyWithin, args:["prim", "prim", "prim"] },
+    { func:proto.every, args:["func"] },
+    { func:proto.fill, args:["ins", "prim", "prim"] },
+    { func:proto.filter, args:["func"] },
+    { func:proto.find, args:["func"] },
+    { func:proto.findIndex, args:["func"] },
+    { func:proto.forEach, args:["func"] },
+    { func:proto.indexOf, args:["na", "prim"] },
+    { func:proto.join, args:["prim"] },
+    { func:proto.lastIndexOf, args:["na", "prim"] },
+    { func:proto.map, args:["func"] },
+    { func:proto.reduce, args:["func"] },
+    { func:proto.reduceRight, args:["func"] },
+    { func:proto.set, args:["array", "prim"] },
+    { func:proto.slice, args:["prim", "prim"] },
+    { func:proto.some, args:["func"] },
+    { func:proto.sort, args:["func"] },
+    { func:proto.subarray, args:["prim", "prim"] },
+];
+
+function defaultForArg(arg)
+{
+    if (arg === "func")
+        return () => { return 1; }
+
+    return 1;
+}
+
+function callWithArgs(func, array, args) {
+    let failed = true;
+    try {
+        func.call(array, ...args);
+    } catch (e) {
+        if (e != "TypeError: Underlying ArrayBuffer has been detached from the view")
+            throw new Error(e);
+        failed = false;
+    }
+    if (failed)
+        throw new Error([func, args]);
+}
+
+
+function checkArgumentsForType(func, args, constructor) {
+    let defaultArgs = args.map(defaultForArg);
+
+    for (let argNum = 0; argNum < args.length; argNum++) {
+        let arg = args[argNum];
+        let callArgs = defaultArgs.slice();
+
+        if (arg === "na")
+            continue;
+
+        let len = 10;
+        if (arg === "func") {
+            let array = new constructor(len);
+            callArgs[argNum] = () => {
+                transferArrayBuffer(array.buffer);
+                return func === array.every ? 1 : 0;
+            };
+            callWithArgs(func, array, callArgs);
+        }
+
+        if (arg === "prim") {
+            let array = new constructor(len)
+            callArgs[argNum] = { [Symbol.toPrimitive]() {
+                transferArrayBuffer(array.buffer);
+                return 1;
+            } };
+            callWithArgs(func, array, callArgs);
+        }
+    }
+}
+
+function checkArguments({func, args}) {
+    for (constructor of typedArrays)
+        checkArgumentsForType(func, args, constructor)
+}
+
+function test() {
+    prototypeFunctions.forEach(checkArguments);
+}
+
+test();
diff --git a/Source/JavaScriptCore/tests/stress/typedarray-view-string-properties-neutered.js b/Source/JavaScriptCore/tests/stress/typedarray-view-string-properties-neutered.js
new file mode 100644 (file)
index 0000000..dbf6815
--- /dev/null
@@ -0,0 +1,49 @@
+typedArrays = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array];
+
+
+function call(thunk) {
+    thunk();
+}
+noInline(call);
+
+let name = ["map", "reduce"];
+// First test with every access being neutered.
+function test(constructor) {
+    let array = new constructor(10);
+    transferArrayBuffer(array.buffer);
+    for (let i = 0; i < 10000; i++) {
+        call(() => {
+            if (array.map !== constructor.prototype.map)
+                throw new Error();
+        });
+        call(() => {
+            if (array[name[i % 2]] !== constructor.prototype[name[i % 2]])
+                throw new Error();
+        });
+    }
+}
+
+for (constructor of typedArrays) {
+    test(constructor);
+}
+
+// Next test with neutered after tier up.
+function test(constructor) {
+    let array = new constructor(10);
+    let i = 0;
+    fnId = () =>  {
+        if (array.map !== constructor.prototype.map)
+            throw new Error();
+    };
+    fnVal = () => {
+        if (array[name[i % 2]] !== constructor.prototype[name[i % 2]])
+            throw new Error();
+    };
+    for (; i < 10000; i++) {
+        call(fnId);
+        call(fnVal);
+    }
+    transferArrayBuffer(array.buffer);
+    call(fnId);
+    call(fnVal);
+}