Title: [207226] trunk
Revision
207226
Author
mark....@apple.com
Date
2016-10-12 11:27:50 -0700 (Wed, 12 Oct 2016)

Log Message

Array.prototype.slice should not modify frozen objects.
https://bugs.webkit.org/show_bug.cgi?id=163338

Reviewed by Filip Pizlo.

JSTests:

* stress/array-slice-on-frozen-object.js: Added.

Source/_javascript_Core:

1. The ES6 spec for Array.prototype.slice
   (https://tc39.github.io/ecma262/#sec-array.prototype.slice) states that it uses
   the CreateDataPropertyOrThrow()
   (https://tc39.github.io/ecma262/#sec-createdatapropertyorthrow) to add items to
   the result array.  The spec for CreateDataPropertyOrThrow states:

   "This abstract operation creates a property whose attributes are set to the
   same defaults used for properties created by the ECMAScript language assignment
   operator. Normally, the property will not already exist. If it does exist and
   is not configurable or if O is not extensible, [[DefineOwnProperty]] will
   return false causing this operation to throw a TypeError exception."

2. Array.prototype.slice also uses a Set function
   (https://tc39.github.io/ecma262/#sec-set-o-p-v-throw) to set the "length"
   property and passes true for the Throw argument.  Ultimately, it ends up
   calling the OrdinarySet function
   (https://tc39.github.io/ecma262/#sec-ordinaryset) that will fail if the
   property is not writable.  This failure should result in a TypeError being
   thrown in Set.

   Since the properties of frozen objects are not extensible, not configurable,
   and not writeable, Array.prototype.slice should fail to write to the result
   array if it is frozen.

If the source array being sliced has 1 or more elements, (1) will take effect
when we try to set the element in the non-writeable result obj.
If the source array being sliced has 0 elements, we will not set any elements and
(1) will not trigger.  Subsequently, (2) will take effect when we will try to
set the length of the result obj.

* runtime/ArrayPrototype.cpp:
(JSC::putLength):
(JSC::setLength):
(JSC::arrayProtoFuncSlice):
(JSC::arrayProtoFuncSplice):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (207225 => 207226)


--- trunk/JSTests/ChangeLog	2016-10-12 17:41:00 UTC (rev 207225)
+++ trunk/JSTests/ChangeLog	2016-10-12 18:27:50 UTC (rev 207226)
@@ -1,3 +1,12 @@
+2016-10-12  Mark Lam  <mark....@apple.com>
+
+        Array.prototype.slice should not modify frozen objects.
+        https://bugs.webkit.org/show_bug.cgi?id=163338
+
+        Reviewed by Filip Pizlo.
+
+        * stress/array-slice-on-frozen-object.js: Added.
+
 2016-10-11  Mark Lam  <mark....@apple.com>
 
         Array.prototype.concat should not modify frozen objects.

Added: trunk/JSTests/stress/array-slice-on-frozen-object.js (0 => 207226)


--- trunk/JSTests/stress/array-slice-on-frozen-object.js	                        (rev 0)
+++ trunk/JSTests/stress/array-slice-on-frozen-object.js	2016-10-12 18:27:50 UTC (rev 207226)
@@ -0,0 +1,64 @@
+//@ runFTLNoCJIT
+
+let totalFailed = 0;
+
+function shouldEqual(testId, actual, expected) {
+    if (actual != expected) {
+        throw testId + ": ERROR: expect " + expected + ", actual " + actual;
+    }
+}
+
+function makeArray() {
+    return ['unmodifiable'];
+}
+
+function makeArrayLikeObject() {
+    var obj = {};
+    obj[0] = 'unmodifiable';
+    obj.length = 1; 
+    return obj;
+}
+
+function emptyArraySourceMaker() {
+    return [];
+}
+
+function singleElementArraySourceMaker() {
+    return ['modified_1'];
+}
+
+// Make test functions with unique codeblocks.
+function makeSliceTest(testId) {
+    return new Function("arr", "arr.slice(0); return " + testId + ";");
+}
+
+let numIterations = 10000;
+
+function runTest(testId, testMaker, targetMaker, sourceMaker, expectedValue, expectedException) {
+    var test = testMaker(testId);
+    noInline(test);
+
+    for (var i = 0; i < numIterations; i++) {
+        var exception = undefined;
+
+        var obj = targetMaker();
+        obj = Object.freeze(obj);
+
+        var arr = sourceMaker();
+        arr.constructor = { [Symbol.species]: function() { return obj; } };
+
+        try {
+            test(arr);
+        } catch (e) {
+            exception = "" + e;
+            exception = exception.substr(0, 10); // Search for "TypeError:".
+        }
+        shouldEqual(testId, exception, expectedException);
+        shouldEqual(testId, obj[0], expectedValue);
+    }
+}
+
+runTest(10010, makeSliceTest, makeArray,           emptyArraySourceMaker,         "unmodifiable", "TypeError:");
+runTest(10011, makeSliceTest, makeArray,           singleElementArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10020, makeSliceTest, makeArrayLikeObject, emptyArraySourceMaker,         "unmodifiable", "TypeError:");
+runTest(10021, makeSliceTest, makeArrayLikeObject, singleElementArraySourceMaker, "unmodifiable", "TypeError:");

Modified: trunk/Source/_javascript_Core/ChangeLog (207225 => 207226)


--- trunk/Source/_javascript_Core/ChangeLog	2016-10-12 17:41:00 UTC (rev 207225)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-10-12 18:27:50 UTC (rev 207226)
@@ -1,3 +1,46 @@
+2016-10-12  Mark Lam  <mark....@apple.com>
+
+        Array.prototype.slice should not modify frozen objects.
+        https://bugs.webkit.org/show_bug.cgi?id=163338
+
+        Reviewed by Filip Pizlo.
+
+        1. The ES6 spec for Array.prototype.slice
+           (https://tc39.github.io/ecma262/#sec-array.prototype.slice) states that it uses
+           the CreateDataPropertyOrThrow()
+           (https://tc39.github.io/ecma262/#sec-createdatapropertyorthrow) to add items to
+           the result array.  The spec for CreateDataPropertyOrThrow states:
+
+           "This abstract operation creates a property whose attributes are set to the
+           same defaults used for properties created by the ECMAScript language assignment
+           operator. Normally, the property will not already exist. If it does exist and
+           is not configurable or if O is not extensible, [[DefineOwnProperty]] will
+           return false causing this operation to throw a TypeError exception."
+
+        2. Array.prototype.slice also uses a Set function
+           (https://tc39.github.io/ecma262/#sec-set-o-p-v-throw) to set the "length"
+           property and passes true for the Throw argument.  Ultimately, it ends up
+           calling the OrdinarySet function
+           (https://tc39.github.io/ecma262/#sec-ordinaryset) that will fail if the
+           property is not writable.  This failure should result in a TypeError being
+           thrown in Set.
+
+           Since the properties of frozen objects are not extensible, not configurable,
+           and not writeable, Array.prototype.slice should fail to write to the result
+           array if it is frozen.
+
+        If the source array being sliced has 1 or more elements, (1) will take effect
+        when we try to set the element in the non-writeable result obj.
+        If the source array being sliced has 0 elements, we will not set any elements and
+        (1) will not trigger.  Subsequently, (2) will take effect when we will try to
+        set the length of the result obj.
+
+        * runtime/ArrayPrototype.cpp:
+        (JSC::putLength):
+        (JSC::setLength):
+        (JSC::arrayProtoFuncSlice):
+        (JSC::arrayProtoFuncSplice):
+
 2016-10-12  Filip Pizlo  <fpi...@apple.com>
 
         Remove JITWriteBarrier.h

Modified: trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp (207225 => 207226)


--- trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp	2016-10-12 17:41:00 UTC (rev 207225)
+++ trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp	2016-10-12 18:27:50 UTC (rev 207226)
@@ -163,17 +163,24 @@
     return slot.getValue(exec, index);
 }
 
-static ALWAYS_INLINE void putLength(ExecState* exec, VM& vm, JSObject* obj, JSValue value)
+static ALWAYS_INLINE bool putLength(ExecState* exec, VM& vm, JSObject* obj, JSValue value)
 {
     PutPropertySlot slot(obj);
-    obj->methodTable()->put(obj, exec, vm.propertyNames->length, value, slot);
+    return obj->methodTable()->put(obj, exec, vm.propertyNames->length, value, slot);
 }
 
 static ALWAYS_INLINE void setLength(ExecState* exec, VM& vm, JSObject* obj, unsigned value)
 {
-    if (isJSArray(obj))
-        jsCast<JSArray*>(obj)->setLength(exec, value);
-    putLength(exec, vm, obj, jsNumber(value));
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    static const bool throwException = true;
+    if (isJSArray(obj)) {
+        jsCast<JSArray*>(obj)->setLength(exec, value, throwException);
+        RETURN_IF_EXCEPTION(scope, void());
+    }
+    bool success = putLength(exec, vm, obj, jsNumber(value));
+    RETURN_IF_EXCEPTION(scope, void());
+    if (UNLIKELY(!success))
+        throwTypeError(exec, scope, ASCIILiteral(ReadonlyPropertyWriteError));
 }
 
 inline bool speciesWatchpointsValid(ExecState* exec, JSObject* thisObject)
@@ -874,8 +881,9 @@
         JSValue v = getProperty(exec, thisObj, k);
         RETURN_IF_EXCEPTION(scope, encodedJSValue());
         if (v)
-            result->putDirectIndex(exec, n, v);
+            result->putDirectIndex(exec, n, v, 0, PutDirectIndexShouldThrow);
     }
+    scope.release();
     setLength(exec, vm, result, n);
     return JSValue::encode(result);
 }
@@ -907,6 +915,8 @@
         }
 
         setLength(exec, vm, result, 0);
+        RETURN_IF_EXCEPTION(scope, encodedJSValue());
+        scope.release();
         setLength(exec, vm, thisObj, length);
         return JSValue::encode(result);
     }
@@ -972,6 +982,7 @@
         RETURN_IF_EXCEPTION(scope, encodedJSValue());
     }
     
+    scope.release();
     setLength(exec, vm, thisObj, length - deleteCount + additionalArgs);
     return JSValue::encode(result);
 }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to