From 5e62e325aca4d9db0ab71a2967c1feeb8ac398fb Mon Sep 17 00:00:00 2001 From: "rossberg@chromium.org" Date: Mon, 18 Jul 2011 13:04:52 +0000 Subject: [PATCH] Implement sealing, freezing, and related functions for proxies. R=ager@chromium.org BUG=v8:1543 TEST= Review URL: http://codereview.chromium.org/7391001 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8673 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/factory.cc | 7 ++++ src/factory.h | 3 ++ src/heap.cc | 33 ++++++++++++++++- src/heap.h | 5 +++ src/messages.js | 3 +- src/objects-inl.h | 1 + src/objects.cc | 12 ++++++ src/objects.h | 15 +++++++- src/runtime.cc | 8 ++++ src/runtime.h | 1 + src/v8natives.js | 43 ++++++++++++++++++++-- test/mjsunit/harmony/proxies.js | 81 +++++++++++++++++++++++++++++++++++++++++ 12 files changed, 204 insertions(+), 8 deletions(-) diff --git a/src/factory.cc b/src/factory.cc index 9ba48ee..ac96668 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -892,6 +892,13 @@ Handle Factory::NewJSProxy(Handle handler, } +void Factory::BecomeJSObject(Handle object) { + CALL_HEAP_FUNCTION_VOID( + isolate(), + isolate()->heap()->ReinitializeJSProxyAsJSObject(*object)); +} + + Handle Factory::NewSharedFunctionInfo( Handle name, int number_of_literals, diff --git a/src/factory.h b/src/factory.h index 19f09a1..19f3827 100644 --- a/src/factory.h +++ b/src/factory.h @@ -253,6 +253,9 @@ class Factory { Handle NewJSProxy(Handle handler, Handle prototype); + // Change the type of the argument into a regular JS object and reinitialize. + void BecomeJSObject(Handle object); + Handle NewFunction(Handle name, Handle prototype); diff --git a/src/heap.cc b/src/heap.cc index 98a2d33..8dbda27 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -3267,14 +3267,13 @@ MaybeObject* Heap::AllocateJSProxy(Object* handler, Object* prototype) { MaybeObject* maybe_map_obj = AllocateMap(JS_PROXY_TYPE, JSProxy::kSize); if (!maybe_map_obj->To(&map)) return maybe_map_obj; map->set_prototype(prototype); - map->set_pre_allocated_property_fields(1); - map->set_inobject_properties(1); // Allocate the proxy object. Object* result; MaybeObject* maybe_result = Allocate(map, NEW_SPACE); if (!maybe_result->ToObject(&result)) return maybe_result; JSProxy::cast(result)->set_handler(handler); + JSProxy::cast(result)->set_padding(Smi::FromInt(0)); return result; } @@ -3414,6 +3413,36 @@ MaybeObject* Heap::CopyJSObject(JSObject* source) { } +MaybeObject* Heap::ReinitializeJSProxyAsJSObject(JSProxy* object) { + // Allocate fresh map. + // TODO(rossberg): Once we optimize proxies, cache these maps. + Map* map; + MaybeObject* maybe_map_obj = + AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize); + if (!maybe_map_obj->To(&map)) return maybe_map_obj; + + // Check that the receiver has the same size as a fresh object. + ASSERT(map->instance_size() == object->map()->instance_size()); + + map->set_prototype(object->map()->prototype()); + + // Allocate the backing storage for the properties. + int prop_size = map->unused_property_fields() - map->inobject_properties(); + Object* properties; + { MaybeObject* maybe_properties = AllocateFixedArray(prop_size, TENURED); + if (!maybe_properties->ToObject(&properties)) return maybe_properties; + } + + // Reset the map for the object. + object->set_map(map); + + // Reinitialize the object from the constructor map. + InitializeJSObjectFromMap(JSObject::cast(object), + FixedArray::cast(properties), map); + return object; +} + + MaybeObject* Heap::ReinitializeJSGlobalProxy(JSFunction* constructor, JSGlobalProxy* object) { ASSERT(constructor->has_initial_map()); diff --git a/src/heap.h b/src/heap.h index d90a681..6cd4f84 100644 --- a/src/heap.h +++ b/src/heap.h @@ -441,6 +441,11 @@ class Heap { MUST_USE_RESULT MaybeObject* AllocateJSProxy(Object* handler, Object* prototype); + // Reinitialize a JSProxy into an (empty) JSObject. The receiver + // must have the same size as an empty object. The object is reinitialized + // and behaves as an object that has been freshly allocated. + MUST_USE_RESULT MaybeObject* ReinitializeJSProxyAsJSObject(JSProxy* object); + // Reinitialize an JSGlobalProxy based on a constructor. The object // must have the same size as objects allocated using the // constructor. The object is reinitialized and behaves as an diff --git a/src/messages.js b/src/messages.js index b928107..98f2440 100644 --- a/src/messages.js +++ b/src/messages.js @@ -195,7 +195,8 @@ function FormatMessage(message) { non_extensible_proto: ["%0", " is not extensible"], handler_non_object: ["Proxy.", "%0", " called with non-object as handler"], handler_trap_missing: ["Proxy handler ", "%0", " has no '", "%1", "' trap"], - handler_failed: ["Proxy handler ", "%0", " returned false for '", "%1", "' trap"], + handler_returned_false: ["Proxy handler ", "%0", " returned false for '", "%1", "' trap"], + handler_returned_undefined: ["Proxy handler ", "%0", " returned undefined for '", "%1", "' trap"], proxy_prop_not_configurable: ["Trap ", "%1", " of proxy handler ", "%0", " returned non-configurable descriptor for property ", "%2"], proxy_non_object_prop_names: ["Trap ", "%1", " returned non-object ", "%0"], proxy_repeated_prop_name: ["Trap ", "%1", " returned repeated property name ", "%2"], diff --git a/src/objects-inl.h b/src/objects-inl.h index fdc5102..7fbf6c9 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -3761,6 +3761,7 @@ void JSBuiltinsObject::set_javascript_builtin_code(Builtins::JavaScript id, ACCESSORS(JSProxy, handler, Object, kHandlerOffset) +ACCESSORS(JSProxy, padding, Object, kPaddingOffset) Address Foreign::address() { diff --git a/src/objects.cc b/src/objects.cc index 26ab645..7b85703 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -2301,6 +2301,18 @@ MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler( } +void JSProxy::Fix() { + Isolate* isolate = GetIsolate(); + HandleScope scope(isolate); + Handle self(this); + + isolate->factory()->BecomeJSObject(self); + ASSERT(IsJSObject()); + // TODO(rossberg): recognize function proxies. +} + + + MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, String* name, Object* value, diff --git a/src/objects.h b/src/objects.h index 0eaeb36..fbb8315 100644 --- a/src/objects.h +++ b/src/objects.h @@ -6475,6 +6475,9 @@ class JSProxy: public JSReceiver { // [handler]: The handler property. DECL_ACCESSORS(handler, Object) + // [padding]: The padding slot (unused, see below). + DECL_ACCESSORS(padding, Object) + // Casting. static inline JSProxy* cast(Object* obj); @@ -6493,6 +6496,9 @@ class JSProxy: public JSReceiver { String* name, bool* has_exception); + // Turn this into an (empty) JSObject. + void Fix(); + // Dispatched behavior. #ifdef OBJECT_PRINT inline void JSProxyPrint() { @@ -6504,9 +6510,14 @@ class JSProxy: public JSReceiver { void JSProxyVerify(); #endif - // Layout description. + // Layout description. We add padding so that a proxy has the same + // size as a virgin JSObject. This is essential for becoming a JSObject + // upon freeze. static const int kHandlerOffset = HeapObject::kHeaderSize; - static const int kSize = kHandlerOffset + kPointerSize; + static const int kPaddingOffset = kHandlerOffset + kPointerSize; + static const int kSize = kPaddingOffset + kPointerSize; + + STATIC_CHECK(kSize == JSObject::kHeaderSize); typedef FixedBodyDescriptorFix(); + return proxy; +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) { NoHandleAllocation ha; ASSERT(args.length() == 1); diff --git a/src/runtime.h b/src/runtime.h index 7bd58c8..ac912d8 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -284,6 +284,7 @@ namespace internal { F(CreateJSProxy, 2, 1) \ F(IsJSProxy, 1, 1) \ F(GetHandler, 1, 1) \ + F(Fix, 1, 1) \ \ /* Statements */ \ F(NewClosure, 3, 1) \ diff --git a/src/v8natives.js b/src/v8natives.js index 53a0317..9843635 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -663,7 +663,8 @@ function DefineProxyProperty(obj, p, attributes, should_throw) { var result = %_CallFunction(handler, p, attributes, defineProperty); if (!ToBoolean(result)) { if (should_throw) { - throw MakeTypeError("handler_failed", [handler, "defineProperty"]); + throw MakeTypeError("handler_returned_false", + [handler, "defineProperty"]); } else { return false; } @@ -1020,11 +1021,30 @@ function ObjectDefineProperties(obj, properties) { } +// Harmony proxies. +function ProxyFix(obj) { + var handler = %GetHandler(obj); + var fix = handler.fix; + if (IS_UNDEFINED(fix)) { + throw MakeTypeError("handler_trap_missing", [handler, "fix"]); + } + var props = %_CallFunction(handler, fix); + if (IS_UNDEFINED(props)) { + throw MakeTypeError("handler_returned_undefined", [handler, "fix"]); + } + %Fix(obj); + ObjectDefineProperties(obj, props); +} + + // ES5 section 15.2.3.8. function ObjectSeal(obj) { if (!IS_SPEC_OBJECT(obj)) { throw MakeTypeError("obj_ctor_property_non_object", ["seal"]); } + if (%IsJSProxy(obj)) { + ProxyFix(obj); + } var names = ObjectGetOwnPropertyNames(obj); for (var i = 0; i < names.length; i++) { var name = names[i]; @@ -1034,7 +1054,8 @@ function ObjectSeal(obj) { DefineOwnProperty(obj, name, desc, true); } } - return ObjectPreventExtension(obj); + %PreventExtensions(obj); + return obj; } @@ -1043,6 +1064,9 @@ function ObjectFreeze(obj) { if (!IS_SPEC_OBJECT(obj)) { throw MakeTypeError("obj_ctor_property_non_object", ["freeze"]); } + if (%IsJSProxy(obj)) { + ProxyFix(obj); + } var names = ObjectGetOwnPropertyNames(obj); for (var i = 0; i < names.length; i++) { var name = names[i]; @@ -1053,7 +1077,8 @@ function ObjectFreeze(obj) { DefineOwnProperty(obj, name, desc, true); } } - return ObjectPreventExtension(obj); + %PreventExtensions(obj); + return obj; } @@ -1062,6 +1087,9 @@ function ObjectPreventExtension(obj) { if (!IS_SPEC_OBJECT(obj)) { throw MakeTypeError("obj_ctor_property_non_object", ["preventExtension"]); } + if (%IsJSProxy(obj)) { + ProxyFix(obj); + } %PreventExtensions(obj); return obj; } @@ -1072,6 +1100,9 @@ function ObjectIsSealed(obj) { if (!IS_SPEC_OBJECT(obj)) { throw MakeTypeError("obj_ctor_property_non_object", ["isSealed"]); } + if (%IsJSProxy(obj)) { + return false; + } var names = ObjectGetOwnPropertyNames(obj); for (var i = 0; i < names.length; i++) { var name = names[i]; @@ -1090,6 +1121,9 @@ function ObjectIsFrozen(obj) { if (!IS_SPEC_OBJECT(obj)) { throw MakeTypeError("obj_ctor_property_non_object", ["isFrozen"]); } + if (%IsJSProxy(obj)) { + return false; + } var names = ObjectGetOwnPropertyNames(obj); for (var i = 0; i < names.length; i++) { var name = names[i]; @@ -1109,6 +1143,9 @@ function ObjectIsExtensible(obj) { if (!IS_SPEC_OBJECT(obj)) { throw MakeTypeError("obj_ctor_property_non_object", ["isExtensible"]); } + if (%IsJSProxy(obj)) { + return true; + } return %IsExtensible(obj); } diff --git a/test/mjsunit/harmony/proxies.js b/test/mjsunit/harmony/proxies.js index f87d756..123ab83 100644 --- a/test/mjsunit/harmony/proxies.js +++ b/test/mjsunit/harmony/proxies.js @@ -529,3 +529,84 @@ TestKeys([], { }, getOwnPropertyDescriptor: function(k) { return {} } }) + + + +// Fixing (Object.freeze, Object.seal, Object.preventExtensions, +// Object.isFrozen, Object.isSealed, Object.isExtensible) + +function TestFix(names, handler) { + var proto = {p: 77} + var assertFixing = function(o, s, f, e) { + assertEquals(s, Object.isSealed(o)) + assertEquals(f, Object.isFrozen(o)) + assertEquals(e, Object.isExtensible(o)) + } + + var o1 = Proxy.create(handler, proto) + assertFixing(o1, false, false, true) + Object.seal(o1) + assertFixing(o1, true, names.length === 0, false) + assertArrayEquals(names.sort(), Object.getOwnPropertyNames(o1).sort()) + assertArrayEquals(names.filter(function(x) {return x < "z"}).sort(), + Object.keys(o1).sort()) + assertEquals(proto, Object.getPrototypeOf(o1)) + assertEquals(77, o1.p) + for (var n in o1) { + var desc = Object.getOwnPropertyDescriptor(o1, n) + if (desc !== undefined) assertFalse(desc.configurable) + } + + var o2 = Proxy.create(handler, proto) + assertFixing(o2, false, false, true) + Object.freeze(o2) + assertFixing(o2, true, true, false) + assertArrayEquals(names.sort(), Object.getOwnPropertyNames(o2).sort()) + assertArrayEquals(names.filter(function(x) {return x < "z"}).sort(), + Object.keys(o2).sort()) + assertEquals(proto, Object.getPrototypeOf(o2)) + assertEquals(77, o2.p) + for (var n in o2) { + var desc = Object.getOwnPropertyDescriptor(o2, n) + if (desc !== undefined) assertFalse(desc.writable) + if (desc !== undefined) assertFalse(desc.configurable) + } + + var o3 = Proxy.create(handler, proto) + assertFixing(o3, false, false, true) + Object.preventExtensions(o3) + assertFixing(o3, names.length === 0, names.length === 0, false) + assertArrayEquals(names.sort(), Object.getOwnPropertyNames(o3).sort()) + assertArrayEquals(names.filter(function(x) {return x < "z"}).sort(), + Object.keys(o3).sort()) + assertEquals(proto, Object.getPrototypeOf(o3)) + assertEquals(77, o3.p) +} + +TestFix([], { + fix: function() { return {} } +}) +TestFix(["a", "b", "c", "d", "zz"], { + fix: function() { + return { + a: {value: "a", writable: true, configurable: false, enumerable: true}, + b: {value: 33, writable: false, configurable: false, enumerable: true}, + c: {value: 0, writable: true, configurable: true, enumerable: true}, + d: {value: true, writable: false, configurable: true, enumerable: true}, + zz: {value: 0, enumerable: false} + } + } +}) +TestFix(["a"], { + fix: function() { return this.fix2() }, + fix2: function() { + return {a: {value: 4, writable: true, configurable: true, enumerable: true}} + } +}) +TestFix(["b"], { + get fix() { + return function() { + return {b: {configurable: true, writable: true, enumerable: true}} + } + } +}) -- 2.7.4