Implement sealing, freezing, and related functions for proxies.
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 18 Jul 2011 13:04:52 +0000 (13:04 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 18 Jul 2011 13:04:52 +0000 (13:04 +0000)
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

12 files changed:
src/factory.cc
src/factory.h
src/heap.cc
src/heap.h
src/messages.js
src/objects-inl.h
src/objects.cc
src/objects.h
src/runtime.cc
src/runtime.h
src/v8natives.js
test/mjsunit/harmony/proxies.js

index 9ba48ee..ac96668 100644 (file)
@@ -892,6 +892,13 @@ Handle<JSProxy> Factory::NewJSProxy(Handle<Object> handler,
 }
 
 
+void Factory::BecomeJSObject(Handle<JSProxy> object) {
+  CALL_HEAP_FUNCTION_VOID(
+      isolate(),
+      isolate()->heap()->ReinitializeJSProxyAsJSObject(*object));
+}
+
+
 Handle<SharedFunctionInfo> Factory::NewSharedFunctionInfo(
     Handle<String> name,
     int number_of_literals,
index 19f09a1..19f3827 100644 (file)
@@ -253,6 +253,9 @@ class Factory {
 
   Handle<JSProxy> NewJSProxy(Handle<Object> handler, Handle<Object> prototype);
 
+  // Change the type of the argument into a regular JS object and reinitialize.
+  void BecomeJSObject(Handle<JSProxy> object);
+
   Handle<JSFunction> NewFunction(Handle<String> name,
                                  Handle<Object> prototype);
 
index 98a2d33..8dbda27 100644 (file)
@@ -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>(&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>(&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());
index d90a681..6cd4f84 100644 (file)
@@ -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
index b928107..98f2440 100644 (file)
@@ -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"],
index fdc5102..7fbf6c9 100644 (file)
@@ -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() {
index 26ab645..7b85703 100644 (file)
@@ -2301,6 +2301,18 @@ MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler(
 }
 
 
+void JSProxy::Fix() {
+  Isolate* isolate = GetIsolate();
+  HandleScope scope(isolate);
+  Handle<JSProxy> self(this);
+
+  isolate->factory()->BecomeJSObject(self);
+  ASSERT(IsJSObject());
+  // TODO(rossberg): recognize function proxies.
+}
+
+
+
 MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
                                             String* name,
                                             Object* value,
index 0eaeb36..fbb8315 100644 (file)
@@ -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 FixedBodyDescriptor<kHandlerOffset,
                               kHandlerOffset + kPointerSize,
index 0ce5836..89b70cc 100644 (file)
@@ -615,6 +615,14 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetHandler) {
 }
 
 
+RUNTIME_FUNCTION(MaybeObject*, Runtime_Fix) {
+  ASSERT(args.length() == 1);
+  CONVERT_CHECKED(JSProxy, proxy, args[0]);
+  proxy->Fix();
+  return proxy;
+}
+
+
 RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) {
   NoHandleAllocation ha;
   ASSERT(args.length() == 1);
index 7bd58c8..ac912d8 100644 (file)
@@ -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) \
index 53a0317..9843635 100644 (file)
@@ -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);
 }
 
index f87d756..123ab83 100644 (file)
@@ -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}}
+    }
+  }
+})