https://bugs.webkit.org/show_bug.cgi?id=163302
Reviewed by Filip Pizlo.
JSTests:
* stress/array-concat-on-frozen-object.js: Added.
Source/JavaScriptCore:
The ES6 spec for Array.prototype.concat states that it uses the
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."
Since the properties of frozen objects are not extensible, not configurable, and
not writable, Array.prototype.concat should fail to write to the result array if
it is frozen.
Ref: https://tc39.github.io/ecma262/#sec-array.prototype.concat,
https://tc39.github.io/ecma262/#sec-createdatapropertyorthrow, and
https://tc39.github.io/ecma262/#sec-createdataproperty.
The fix consists of 2 parts:
1. moveElement() should use the PutDirectIndexShouldThrow mode when invoking
putDirectIndex(), and
2. SparseArrayValueMap::putDirect() should check for the case where the property
is read only.
(2) ensures that we don't write into a non-writable property.
(1) ensures that we throw a TypeError for attempts to write to a non-writeable
property.
* runtime/ArrayPrototype.cpp:
(JSC::moveElements):
* runtime/SparseArrayValueMap.cpp:
(JSC::SparseArrayValueMap::putDirect):
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@207178
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2016-10-11 Mark Lam <mark.lam@apple.com>
+
+ Array.prototype.concat should not modify frozen objects.
+ https://bugs.webkit.org/show_bug.cgi?id=163302
+
+ Reviewed by Filip Pizlo.
+
+ * stress/array-concat-on-frozen-object.js: Added.
+
2016-10-11 Saam Barati <sbarati@apple.com>
ValueAdd should be constant folded if the operands are constant String,Primitive or Primitive,String
--- /dev/null
+//@ 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 makeConcatTest(testId) {
+ return new Function("arr", "return arr.concat(['" + testId + "'])");
+}
+function makeConcatOnHoleyArrayTest(testId) {
+ return new Function("arr", "var other = ['" + testId + "']; other[1000] = '" + testId + "'; return arr.concat(other);");
+}
+
+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, makeConcatTest, makeArray, emptyArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10011, makeConcatTest, makeArray, singleElementArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10020, makeConcatTest, makeArrayLikeObject, emptyArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10021, makeConcatTest, makeArrayLikeObject, singleElementArraySourceMaker, "unmodifiable", "TypeError:");
+
+runTest(10110, makeConcatOnHoleyArrayTest, makeArray, emptyArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10111, makeConcatOnHoleyArrayTest, makeArray, singleElementArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10120, makeConcatOnHoleyArrayTest, makeArrayLikeObject, emptyArraySourceMaker, "unmodifiable", "TypeError:");
+runTest(10121, makeConcatOnHoleyArrayTest, makeArrayLikeObject, singleElementArraySourceMaker, "unmodifiable", "TypeError:");
+2016-10-11 Mark Lam <mark.lam@apple.com>
+
+ Array.prototype.concat should not modify frozen objects.
+ https://bugs.webkit.org/show_bug.cgi?id=163302
+
+ Reviewed by Filip Pizlo.
+
+ The ES6 spec for Array.prototype.concat states that it uses the
+ 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."
+
+ Since the properties of frozen objects are not extensible, not configurable, and
+ not writable, Array.prototype.concat should fail to write to the result array if
+ it is frozen.
+
+ Ref: https://tc39.github.io/ecma262/#sec-array.prototype.concat,
+ https://tc39.github.io/ecma262/#sec-createdatapropertyorthrow, and
+ https://tc39.github.io/ecma262/#sec-createdataproperty.
+
+ The fix consists of 2 parts:
+ 1. moveElement() should use the PutDirectIndexShouldThrow mode when invoking
+ putDirectIndex(), and
+ 2. SparseArrayValueMap::putDirect() should check for the case where the property
+ is read only.
+
+ (2) ensures that we don't write into a non-writable property.
+ (1) ensures that we throw a TypeError for attempts to write to a non-writeable
+ property.
+
+ * runtime/ArrayPrototype.cpp:
+ (JSC::moveElements):
+ * runtime/SparseArrayValueMap.cpp:
+ (JSC::SparseArrayValueMap::putDirect):
+
2016-10-11 Yusuke Suzuki <utatane.tea@gmail.com>
[DOMJIT] DOMJIT::Patchpoint should have a way to receive constant folded arguments
for (unsigned i = 0; i < sourceLength; ++i) {
JSValue value = source->tryGetIndexQuickly(i);
if (value) {
- target->putDirectIndex(exec, targetOffset + i, value);
+ target->putDirectIndex(exec, targetOffset + i, value, 0, PutDirectIndexShouldThrow);
RETURN_IF_EXCEPTION(scope, false);
}
}
JSValue value = getProperty(exec, source, i);
RETURN_IF_EXCEPTION(scope, false);
if (value) {
- target->putDirectIndex(exec, targetOffset + i, value);
+ target->putDirectIndex(exec, targetOffset + i, value, 0, PutDirectIndexShouldThrow);
RETURN_IF_EXCEPTION(scope, false);
}
}
AddResult result = add(array, i);
SparseArrayEntry& entry = result.iterator->value;
- // To save a separate find & add, we first always add to the sparse map.
- // In the uncommon case that this is a new property, and the array is not
- // extensible, this is not the right thing to have done - so remove again.
- if (mode != PutDirectIndexLikePutDirect && result.isNewEntry && !array->isStructureExtensible()) {
- remove(result.iterator);
- return reject(exec, mode == PutDirectIndexShouldThrow, "Attempting to define property on object that is not extensible.");
+ if (mode != PutDirectIndexLikePutDirect && !array->isStructureExtensible()) {
+ // To save a separate find & add, we first always add to the sparse map.
+ // In the uncommon case that this is a new property, and the array is not
+ // extensible, this is not the right thing to have done - so remove again.
+ if (result.isNewEntry) {
+ remove(result.iterator);
+ return reject(exec, mode == PutDirectIndexShouldThrow, "Attempting to define property on object that is not extensible.");
+ }
+ if (entry.attributes & ReadOnly)
+ return reject(exec, mode == PutDirectIndexShouldThrow, ReadonlyPropertyWriteError);
}
-
entry.attributes = attributes;
entry.set(exec->vm(), this, value);
return true;