Revision: 5011
Author: [email protected]
Date: Fri Jul 2 07:36:34 2010
Log: Add ES5 Object.isExtensible and Object.preventExtensions.
Review URL: http://codereview.chromium.org/2819034
http://code.google.com/p/v8/source/detail?r=5011
Added:
/branches/bleeding_edge/test/mjsunit/object-prevent-extensions.js
Modified:
/branches/bleeding_edge/src/messages.js
/branches/bleeding_edge/src/objects-inl.h
/branches/bleeding_edge/src/objects.cc
/branches/bleeding_edge/src/objects.h
/branches/bleeding_edge/src/runtime.cc
/branches/bleeding_edge/src/runtime.h
/branches/bleeding_edge/src/v8natives.js
/branches/bleeding_edge/test/cctest/test-api.cc
/branches/bleeding_edge/test/es5conform/es5conform.status
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/object-prevent-extensions.js Fri
Jul 2 07:36:34 2010
@@ -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));
+}
=======================================
--- /branches/bleeding_edge/src/messages.js Thu May 6 00:32:44 2010
+++ /branches/bleeding_edge/src/messages.js Fri Jul 2 07:36:34 2010
@@ -196,6 +196,7 @@
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"
};
}
=======================================
--- /branches/bleeding_edge/src/objects-inl.h Fri Jul 2 07:15:04 2010
+++ /branches/bleeding_edge/src/objects-inl.h Fri Jul 2 07:36:34 2010
@@ -2199,6 +2199,20 @@
}
+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));
}
=======================================
--- /branches/bleeding_edge/src/objects.cc Fri Jul 2 07:15:04 2010
+++ /branches/bleeding_edge/src/objects.cc Fri Jul 2 07:36:34 2010
@@ -1386,6 +1386,11 @@
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() <
@@ -2572,6 +2577,25 @@
// No references to object.
return false;
}
+
+
+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:
@@ -3074,7 +3098,7 @@
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 @@
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)) {
=======================================
--- /branches/bleeding_edge/src/objects.h Fri Jul 2 07:15:04 2010
+++ /branches/bleeding_edge/src/objects.h Fri Jul 2 07:36:34 2010
@@ -1517,6 +1517,10 @@
// 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 @@
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) {
=======================================
--- /branches/bleeding_edge/src/runtime.cc Wed Jun 30 00:40:40 2010
+++ /branches/bleeding_edge/src/runtime.cc Fri Jul 2 07:36:34 2010
@@ -678,6 +678,12 @@
}
+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]);
=======================================
--- /branches/bleeding_edge/src/runtime.h Fri Jun 4 03:19:19 2010
+++ /branches/bleeding_edge/src/runtime.h Fri Jul 2 07:36:34 2010
@@ -72,6 +72,7 @@
F(GetOwnProperty, 2, 1) \
\
F(IsExtensible, 1, 1) \
+ F(PreventExtensions, 1, 1)\
\
/* Utilities */ \
F(GetFunctionDelegate, 1, 1) \
=======================================
--- /branches/bleeding_edge/src/v8natives.js Mon Jun 28 02:25:09 2010
+++ /branches/bleeding_edge/src/v8natives.js Fri Jul 2 07:36:34 2010
@@ -743,6 +743,27 @@
}
return obj;
}
+
+
+// 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) {
@@ -780,7 +801,9 @@
"defineProperties", ObjectDefineProperties,
"getPrototypeOf", ObjectGetPrototypeOf,
"getOwnPropertyDescriptor", ObjectGetOwnPropertyDescriptor,
- "getOwnPropertyNames", ObjectGetOwnPropertyNames
+ "getOwnPropertyNames", ObjectGetOwnPropertyNames,
+ "isExtensible", ObjectIsExtensible,
+ "preventExtensions", ObjectPreventExtension
));
}
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc Wed Jun 30 00:40:40 2010
+++ /branches/bleeding_edge/test/cctest/test-api.cc Fri Jul 2 07:36:34 2010
@@ -3333,6 +3333,42 @@
ExpectBoolean("undefined===undetectable", false);
ExpectBoolean("undetectable===undetectable", true);
}
+
+
+
+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) {
=======================================
--- /branches/bleeding_edge/test/es5conform/es5conform.status Tue Jun 29
02:00:20 2010
+++ /branches/bleeding_edge/test/es5conform/es5conform.status Fri Jul 2
07:36:34 2010
@@ -44,7 +44,6 @@
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.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 @@
# 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
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev