Add ES5 Object.isExtensible and Object.preventExtensions.

Review URL: http://codereview.chromium.org/2819034

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5011 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
ricow@chromium.org 2010-07-02 14:36:34 +00:00
parent c56b92d65d
commit eed4ed99c8
10 changed files with 279 additions and 20 deletions

View File

@ -196,6 +196,7 @@ function FormatMessage(message) {
circular_structure: "Converting circular structure to JSON",
obj_ctor_property_non_object: "Object.%0 called on non-object",
array_indexof_not_defined: "Array.getIndexOf: Argument undefined",
object_not_extensible: "Can't add property %0, object is not extensible",
illegal_access: "illegal access"
};
}

View File

@ -2199,6 +2199,20 @@ bool Map::is_access_check_needed() {
}
void Map::set_is_extensible(bool value) {
if (value) {
set_bit_field2(bit_field2() | (1 << kIsExtensible));
} else {
set_bit_field2(bit_field2() & ~(1 << kIsExtensible));
}
}
bool Map::is_extensible() {
return ((1 << kIsExtensible) & bit_field2()) != 0;
}
Code::Flags Code::flags() {
return static_cast<Flags>(READ_INT_FIELD(this, kFlagsOffset));
}

View File

@ -1386,6 +1386,11 @@ Object* JSObject::AddProperty(String* name,
Object* value,
PropertyAttributes attributes) {
ASSERT(!IsJSGlobalProxy());
if (!map()->is_extensible()) {
Handle<Object> args[1] = {Handle<String>(name)};
return Top::Throw(*Factory::NewTypeError("object_not_extensible",
HandleVector(args, 1)));
}
if (HasFastProperties()) {
// Ensure the descriptor array does not get too big.
if (map()->instance_descriptors()->number_of_descriptors() <
@ -2574,6 +2579,25 @@ bool JSObject::ReferencesObject(Object* obj) {
}
Object* JSObject::PreventExtensions() {
// If there are fast elements we normalize.
if (HasFastElements()) {
NormalizeElements();
}
// Make sure that we never go back to fast case.
element_dictionary()->set_requires_slow_elements();
// Do a map transition, other objects with this map may still
// be extensible.
Object* new_map = map()->CopyDropTransitions();
if (new_map->IsFailure()) return new_map;
Map::cast(new_map)->set_is_extensible(false);
set_map(Map::cast(new_map));
ASSERT(!map()->is_extensible());
return new_map;
}
// Tests for the fast common case for property enumeration:
// - This object and all prototypes has an enum cache (which means that it has
// no interceptors and needs no access checks).
@ -3074,7 +3098,7 @@ Object* Map::CopyDropTransitions() {
Object* descriptors = instance_descriptors()->RemoveTransitions();
if (descriptors->IsFailure()) return descriptors;
cast(new_map)->set_instance_descriptors(DescriptorArray::cast(descriptors));
return cast(new_map);
return new_map;
}
@ -6207,6 +6231,15 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) {
return value;
}
}
// When we set the is_extensible flag to false we always force
// the element into dictionary mode (and force them to stay there).
if (!map()->is_extensible()) {
Handle<Object> number(Heap::NumberFromUint32(index));
Handle<String> index_string(Factory::NumberToString(number));
Handle<Object> args[1] = { index_string };
return Top::Throw(*Factory::NewTypeError("object_not_extensible",
HandleVector(args, 1)));
}
Object* result = dictionary->AtNumberPut(index, value);
if (result->IsFailure()) return result;
if (elms != FixedArray::cast(result)) {

View File

@ -1517,6 +1517,10 @@ class JSObject: public HeapObject {
// Casting.
static inline JSObject* cast(Object* obj);
// Disalow further properties to be added to the object.
Object* PreventExtensions();
// Dispatched behavior.
void JSObjectIterateBody(int object_size, ObjectVisitor* v);
void JSObjectShortPrint(StringStream* accumulator);
@ -2989,13 +2993,8 @@ class Map: public HeapObject {
return ((1 << kHasInstanceCallHandler) & bit_field()) != 0;
}
inline void set_is_extensible() {
set_bit_field2(bit_field2() | (1 << kIsExtensible));
}
inline bool is_extensible() {
return ((1 << kIsExtensible) & bit_field2()) != 0;
}
inline void set_is_extensible(bool value);
inline bool is_extensible();
// Tells whether the instance has fast elements.
void set_has_fast_elements(bool value) {

View File

@ -678,6 +678,12 @@ static Object* Runtime_GetOwnProperty(Arguments args) {
}
static Object* Runtime_PreventExtensions(Arguments args) {
ASSERT(args.length() == 1);
CONVERT_CHECKED(JSObject, obj, args[0]);
return obj->PreventExtensions();
}
static Object* Runtime_IsExtensible(Arguments args) {
ASSERT(args.length() == 1);
CONVERT_CHECKED(JSObject, obj, args[0]);

View File

@ -72,6 +72,7 @@ namespace internal {
F(GetOwnProperty, 2, 1) \
\
F(IsExtensible, 1, 1) \
F(PreventExtensions, 1, 1)\
\
/* Utilities */ \
F(GetFunctionDelegate, 1, 1) \

View File

@ -745,6 +745,27 @@ function ObjectDefineProperties(obj, properties) {
}
// ES5 section 15.2.3.10
function ObjectPreventExtension(obj) {
if ((!IS_SPEC_OBJECT_OR_NULL(obj) || IS_NULL_OR_UNDEFINED(obj)) &&
!IS_UNDETECTABLE(obj)) {
throw MakeTypeError("obj_ctor_property_non_object", ["preventExtension"]);
}
%PreventExtensions(obj);
return obj;
}
// ES5 section 15.2.3.13
function ObjectIsExtensible(obj) {
if ((!IS_SPEC_OBJECT_OR_NULL(obj) || IS_NULL_OR_UNDEFINED(obj)) &&
!IS_UNDETECTABLE(obj)) {
throw MakeTypeError("obj_ctor_property_non_object", ["preventExtension"]);
}
return %IsExtensible(obj);
}
%SetCode($Object, function(x) {
if (%_IsConstructCall()) {
if (x == null) return this;
@ -780,7 +801,9 @@ function SetupObject() {
"defineProperties", ObjectDefineProperties,
"getPrototypeOf", ObjectGetPrototypeOf,
"getOwnPropertyDescriptor", ObjectGetOwnPropertyDescriptor,
"getOwnPropertyNames", ObjectGetOwnPropertyNames
"getOwnPropertyNames", ObjectGetOwnPropertyNames,
"isExtensible", ObjectIsExtensible,
"preventExtensions", ObjectPreventExtension
));
}

View File

@ -3335,6 +3335,42 @@ THREADED_TEST(UndetectableObject) {
}
THREADED_TEST(ExtensibleOnUndetectable) {
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> desc =
v8::FunctionTemplate::New(0, v8::Handle<Value>());
desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable
Local<v8::Object> obj = desc->GetFunction()->NewInstance();
env->Global()->Set(v8_str("undetectable"), obj);
Local<String> source = v8_str("undetectable.x = 42;"
"undetectable.x");
Local<Script> script = Script::Compile(source);
CHECK_EQ(v8::Integer::New(42), script->Run());
ExpectBoolean("Object.isExtensible(undetectable)", true);
source = v8_str("Object.preventExtensions(undetectable);");
script = Script::Compile(source);
script->Run();
ExpectBoolean("Object.isExtensible(undetectable)", false);
source = v8_str("undetectable.y = 2000;");
script = Script::Compile(source);
v8::TryCatch try_catch;
Local<Value> result = script->Run();
CHECK(result.IsEmpty());
CHECK(try_catch.HasCaught());
}
THREADED_TEST(UndetectableString) {
v8::HandleScope scope;
LocalContext env;

View File

@ -44,7 +44,6 @@ chapter11/11.1/11.1.5: UNIMPLEMENTED
chapter11/11.4/11.4.1//11.4.1-4.a-5: FAIL
chapter11/11.4/11.4.1//11.4.1-4.a-7: FAIL
# We do not have a global object called 'global' as required by tests.
chapter15/15.1: FAIL_OK
@ -52,14 +51,10 @@ chapter15/15.1: FAIL_OK
chapter15/15.2/15.2.3/15.2.3.8: UNIMPLEMENTED
# NOT IMPLEMENTED: freeze
chapter15/15.2/15.2.3/15.2.3.9: UNIMPLEMENTED
# NOT IMPLEMENTED: preventExtensions
chapter15/15.2/15.2.3/15.2.3.10: UNIMPLEMENTED
# NOT IMPLEMENTED: isSealed
chapter15/15.2/15.2.3/15.2.3.11: UNIMPLEMENTED
# NOT IMPLEMENTED: isFrozen
chapter15/15.2/15.2.3/15.2.3.12: UNIMPLEMENTED
# NOT IMPLEMENTED: isExtensible
chapter15/15.2/15.2.3/15.2.3.13: UNIMPLEMENTED
# NOT IMPLEMENTED: seal
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-20: UNIMPLEMENTED
@ -67,18 +62,12 @@ chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-20: UNIMPLEMENTED
# NOT IMPLEMENTED: freeze
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-21: UNIMPLEMENTED
# NOT IMPLEMENTED: preventExtensions
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-22: UNIMPLEMENTED
# NOT IMPLEMENTED: isSealed
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-23: UNIMPLEMENTED
# NOT IMPLEMENTED: isFrozen
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-24: UNIMPLEMENTED
# NOT IMPLEMENTED: isExtensible
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-25: UNIMPLEMENTED
# NOT IMPLEMENTED: bind
chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-38: UNIMPLEMENTED

View File

@ -0,0 +1,157 @@
// Copyright 2010 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.
// Tests the Object.preventExtensions method - ES 15.2.3.10
var obj1 = {};
// Extensible defaults to true.
assertTrue(Object.isExtensible(obj1));
Object.preventExtensions(obj1);
// Make sure the is_extensible flag is set.
assertFalse(Object.isExtensible(obj1));
// Try adding a new property.
try {
obj1.x = 42;
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
assertEquals(undefined, obj1.x);
// Try adding a new element.
try {
obj1[1] = 42;
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
assertEquals(undefined, obj1[1]);
// Try when the object has an existing property.
var obj2 = {};
assertTrue(Object.isExtensible(obj2));
obj2.x = 42;
assertEquals(42, obj2.x);
assertTrue(Object.isExtensible(obj2));
Object.preventExtensions(obj2);
assertEquals(42, obj2.x);
try {
obj2.y = 42;
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
// obj2.y should still be undefined.
assertEquals(undefined, obj2.y);
// Make sure we can still write values to obj.x.
obj2.x = 43;
assertEquals(43, obj2.x)
try {
obj2.y = new function() { return 42; };
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
// obj2.y should still be undefined.
assertEquals(undefined, obj2.y);
assertEquals(43, obj2.x)
try {
Object.defineProperty(obj2, "y", {value: 42});
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
// obj2.y should still be undefined.
assertEquals(undefined, obj2.y);
assertEquals(43, obj2.x);
try {
obj2[1] = 42;
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
assertEquals(undefined, obj2[1]);
var arr = new Array();
arr[1] = 10;
Object.preventExtensions(arr);
try {
arr[2] = 42;
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
assertEquals(10, arr[1]);
// We should still be able to change exiting elements.
arr[1]= 42;
assertEquals(42, arr[1]);
// Test the the extensible flag is not inherited.
var parent = {};
parent.x = 42;
Object.preventExtensions(parent);
var child = Object.create(parent);
// We should be able to add new properties to the child object.
child.y = 42;
// This should have no influence on the parent class.
try {
parent.y = 29;
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}
// Test that attributes on functions are also handled correctly.
function foo() {
return 42;
}
Object.preventExtensions(foo);
try {
foo.x = 29;
assertUnreachable();
} catch (e) {
assertTrue(/object is not extensible/.test(e));
}