Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
return;
}
+ if ($Object.isSealed(this)) {
+ throw MakeTypeError("array_functions_change_sealed",
+ ["Array.prototype.pop"]);
+ }
+
if (%IsObserved(this))
return ObservedArrayPop.call(this, n);
["Array.prototype.push"]);
}
+ var n = TO_UINT32(this.length);
+ var m = %_ArgumentsLength();
+ if (m > 0 && $Object.isSealed(this)) {
+ throw MakeTypeError("array_functions_change_sealed",
+ ["Array.prototype.push"]);
+ }
+
if (%IsObserved(this))
return ObservedArrayPush.apply(this, arguments);
- var n = TO_UINT32(this.length);
- var m = %_ArgumentsLength();
for (var i = 0; i < m; i++) {
this[i+n] = %_Arguments(i);
}
return;
}
+ if ($Object.isSealed(this)) {
+ throw MakeTypeError("array_functions_change_sealed",
+ ["Array.prototype.shift"]);
+ }
+
if (%IsObserved(this))
return ObservedArrayShift.call(this, len);
["Array.prototype.unshift"]);
}
- if (%IsObserved(this))
- return ObservedArrayUnshift.apply(this, arguments);
-
var len = TO_UINT32(this.length);
var num_arguments = %_ArgumentsLength();
+ var is_sealed = $Object.isSealed(this);
- if (IS_ARRAY(this)) {
+ if (num_arguments > 0 && is_sealed) {
+ throw MakeTypeError("array_functions_change_sealed",
+ ["Array.prototype.unshift"]);
+ }
+
+ if (%IsObserved(this))
+ return ObservedArrayUnshift.apply(this, arguments);
+
+ if (IS_ARRAY(this) && !is_sealed) {
SmartMove(this, 0, 0, len, num_arguments);
} else {
+ if (num_arguments == 0 && $Object.isFrozen(this)) {
+ // In the zero argument case, values from the prototype come into the
+ // object. This can't be allowed on frozen arrays.
+ for (var i = 0; i < len; i++) {
+ if (!this.hasOwnProperty(i) && !IS_UNDEFINED(this[i])) {
+ throw MakeTypeError("array_functions_on_frozen",
+ ["Array.prototype.shift"]);
+ }
+ }
+ }
+
SimpleMove(this, 0, 0, len, num_arguments);
}
this.length = len + num_arguments;
- return len + num_arguments;
+ return this.length;
}
deleted_elements.length = del_count;
var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;
+ if (del_count != num_elements_to_add && $Object.isSealed(this)) {
+ throw MakeTypeError("array_functions_change_sealed",
+ ["Array.prototype.splice"]);
+ } else if (del_count > 0 && $Object.isFrozen(this)) {
+ throw MakeTypeError("array_functions_on_frozen",
+ ["Array.prototype.splice"]);
+ }
+
var use_simple_splice = true;
if (IS_ARRAY(this) &&
num_elements_to_add !== del_count) {
if (!receiver->IsJSArray()) return NULL;
JSArray* array = JSArray::cast(receiver);
if (array->map()->is_observed()) return NULL;
+ if (!array->map()->is_extensible()) return NULL;
HeapObject* elms = array->elements();
Map* map = elms->map();
if (map == heap->fixed_array_map()) {
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
constructor_not_function: ["Constructor ", "%0", " requires 'new'"],
not_a_promise: ["%0", "is not a promise"],
promise_cyclic: ["Chaining cycle detected for promise", "%0"],
+ array_functions_on_frozen: ["Cannot modify frozen array elements"],
+ array_functions_change_sealed: ["Cannot add/remove sealed array elements"],
// RangeError
invalid_array_length: ["Invalid array length"],
invalid_array_buffer_length: ["Invalid array buffer length"],
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
if (is_access_check_needed()) {
PrintF(out, " - access_check_needed\n");
}
+ if (is_frozen()) {
+ PrintF(out, " - frozen\n");
+ } else if (!is_extensible()) {
+ PrintF(out, " - sealed\n");
+ }
PrintF(out, " - back pointer: ");
GetBackPointer()->ShortPrint(out);
PrintF(out, "\n - instance descriptors %s#%i: ",
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // ----------- S t a t e -------------
- // -- rcx : name
- // -- rsp[0] : return address
- // -- rsp[(argc - n) * 8] : arg[n] (zero-based)
- // -- ...
- // -- rsp[(argc + 1) * 8] : receiver
- // -----------------------------------
-
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
Handle<JSFunction> function,
Handle<String> name,
Code::StubType type) {
- // If object is not an array or is observed, bail out to regular call.
+ // If object is not an array or is observed or sealed, bail out to regular
+ // call.
if (!object->IsJSArray() ||
!cell.is_null() ||
- Handle<JSArray>::cast(object)->map()->is_observed()) {
+ Handle<JSArray>::cast(object)->map()->is_observed() ||
+ !Handle<JSArray>::cast(object)->map()->is_extensible()) {
return Handle<Code>::null();
}
Object.freeze(obj);
assertTrue(%HasFastProperties(obj));
assertTrue(Object.isFrozen(obj));
+
+// Test array built-in functions with freeze.
+obj = [1,2,3];
+Object.freeze(obj);
+// if frozen implies sealed, then the tests in object-seal.js are mostly
+// sufficient.
+assertTrue(Object.isSealed(obj));
+
+assertDoesNotThrow(function() { obj.push(); });
+assertDoesNotThrow(function() { obj.unshift(); });
+assertDoesNotThrow(function() { obj.splice(0,0); });
+assertTrue(Object.isFrozen(obj));
+
+// Verify that an item can't be changed with splice.
+assertThrows(function() { obj.splice(0,1,1); }, TypeError);
+
+// Verify that unshift() with no arguments will fail if it reifies from
+// the prototype into the object.
+obj = [1,,3];
+obj.__proto__[1] = 1;
+assertEquals(1, obj[1]);
+Object.freeze(obj);
+assertThrows(function() { obj.unshift(); }, TypeError);
// Tests the Object.seal and Object.isSealed methods - ES 15.2.3.9 and
// ES 15.2.3.12
+// Flags: --allow-natives-syntax --noalways-opt
// Test that we throw an error if an object is not passed as argument.
var non_objects = new Array(undefined, null, 1, -1, 0, 42.43);
// Make sure that Object.seal returns the sealed object.
var obj4 = {};
assertTrue(obj4 === Object.seal(obj4));
+
+//
+// Test that built-in array functions can't modify a sealed array.
+//
+obj = [1, 2, 3];
+var objControl = [4, 5, 6];
+
+// Allow these functions to set up monomorphic calls, using custom built-ins.
+var push_call = function(a) { a.push(10); return a; }
+var pop_call = function(a) { return a.pop(); }
+for (var i = 0; i < 3; i++) {
+ push_call(obj);
+ pop_call(obj);
+}
+
+Object.seal(obj);
+assertThrows(function() { push_call(obj); }, TypeError);
+assertThrows(function() { pop_call(obj); }, TypeError);
+
+// But the control object is fine at these sites.
+assertDoesNotThrow(function() { push_call(objControl); });
+assertDoesNotThrow(function() { pop_call(objControl); });
+
+assertDoesNotThrow(function() { obj.push(); });
+assertThrows(function() { obj.push(3); }, TypeError);
+assertThrows(function() { obj.pop(); }, TypeError);
+assertThrows(function() { obj.shift(3); }, TypeError);
+assertDoesNotThrow(function() { obj.unshift(); });
+assertThrows(function() { obj.unshift(1); }, TypeError);
+assertThrows(function() { obj.splice(0, 0, 100, 101, 102); }, TypeError);
+assertDoesNotThrow(function() { obj.splice(0,0); });
+
+assertDoesNotThrow(function() { objControl.push(3); });
+assertDoesNotThrow(function() { objControl.pop(); });
+assertDoesNotThrow(function() { objControl.shift(3); });
+assertDoesNotThrow(function() { objControl.unshift(); });
+assertDoesNotThrow(function() { objControl.splice(0, 0, 100, 101, 102); });
+
+// Verify that crankshaft still does the right thing.
+obj = [1, 2, 3];
+
+push_call = function(a) { a.push(1000); return a; }
+// Include a call site that doesn't have a custom built-in.
+var shift_call = function(a) { a.shift(1000); return a; }
+for (var i = 0; i < 3; i++) {
+ push_call(obj);
+ shift_call(obj);
+}
+
+%OptimizeFunctionOnNextCall(push_call);
+%OptimizeFunctionOnNextCall(shift_call);
+push_call(obj);
+shift_call(obj);
+assertOptimized(push_call);
+assertOptimized(shift_call);
+Object.seal(obj);
+assertThrows(function() { push_call(obj); }, TypeError);
+assertThrows(function() { shift_call(obj); }, TypeError);
+assertOptimized(push_call);
+// shift() doesn't have a custom call generator, so deopt will occur.
+assertUnoptimized(shift_call);
+assertDoesNotThrow(function() { push_call(objControl); });
+assertDoesNotThrow(function() { shift_call(objControl); });
+
+// Verify special behavior of splice on sealed objects.
+obj = [1,2,3];
+Object.seal(obj);
+assertDoesNotThrow(function() { obj.splice(0,1,100); });
+assertEquals(100, obj[0]);
+assertDoesNotThrow(function() { obj.splice(0,2,1,2); });
+assertDoesNotThrow(function() { obj.splice(1,2,1,2); });
+// Count of items to delete is clamped by length.
+assertDoesNotThrow(function() { obj.splice(1,2000,1,2); });
+assertThrows(function() { obj.splice(0,0,1); }, TypeError);
+assertThrows(function() { obj.splice(1,2000,1,2,3); }, TypeError);
// Test that frozen arrays don't let their length change
var a = Object.freeze([1]);
-a.push(2);
+assertThrows(function() { a.push(2); }, TypeError);
assertEquals(1, a.length);
-a.push(2);
+assertThrows(function() { a.push(2); }, TypeError);
assertEquals(1, a.length);
--- /dev/null
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+(function(){
+ "use strict";
+ var list = Object.freeze([1, 2, 3]);
+ assertThrows(function() { list.unshift(4); }, TypeError);
+ assertThrows(function() { list.shift(); }, TypeError);
+})();