Reapply: "Make instanceof and Object.getPrototypeOf work for proxies,
authorager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 3 Jun 2011 10:15:49 +0000 (10:15 +0000)
committerager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 3 Jun 2011 10:15:49 +0000 (10:15 +0000)
plus a few other tweaks."

The problem with the original patch was that it did not take hidden
prototype objects into account in Runtime_GetPrototype.

R=kmillikin@chromium.org,rossberg@chromium.org
TEST=es5conform

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8164 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/messages.js
src/objects-inl.h
src/objects.cc
src/proxy.js
src/runtime.cc
src/runtime.h
src/v8natives.js
src/x64/builtins-x64.cc
test/mjsunit/harmony/proxies.js

index b564f35..e390f7d 100644 (file)
@@ -192,7 +192,11 @@ function FormatMessage(message) {
       redefine_disallowed:          ["Cannot redefine property: ", "%0"],
       define_disallowed:            ["Cannot define property, object is not extensible: ", "%0"],
       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"],
+      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"],
       // RangeError
       invalid_array_length:         ["Invalid array length"],
       stack_overflow:               ["Maximum call stack size exceeded"],
index 597e0df..2b5d18f 100644 (file)
@@ -2961,7 +2961,7 @@ Object* Map::prototype() {
 
 
 void Map::set_prototype(Object* value, WriteBarrierMode mode) {
-  ASSERT(value->IsNull() || value->IsJSObject());
+  ASSERT(value->IsNull() || value->IsJSReceiver());
   WRITE_FIELD(this, kPrototypeOffset, value);
   CONDITIONAL_WRITE_BARRIER(GetHeap(), this, kPrototypeOffset, mode);
 }
index 04cb528..ba3fb12 100644 (file)
@@ -638,7 +638,7 @@ Object* Object::GetPrototype() {
 
   // The object is either a number, a string, a boolean,
   // a real JS object, or a Harmony proxy.
-  if (heap_object->IsJSObject() || heap_object->IsJSProxy()) {
+  if (heap_object->IsJSReceiver()) {
     return heap_object->map()->prototype();
   }
   Heap* heap = heap_object->GetHeap();
@@ -3380,8 +3380,7 @@ void JSObject::LocalLookup(String* name, LookupResult* result) {
   }
 
   // Check __proto__ before interceptor.
-  if (name->Equals(heap->Proto_symbol()) &&
-      !IsJSContextExtensionObject()) {
+  if (name->Equals(heap->Proto_symbol()) && !IsJSContextExtensionObject()) {
     result->ConstantResult(this);
     return;
   }
index d90e5d4..cb9c020 100644 (file)
@@ -60,8 +60,9 @@ $Proxy.createFunction = function(handler, callTrap, constructTrap) {
 }
 
 $Proxy.create = function(handler, proto) {
-  if (!IS_SPEC_OBJECT(handler)) throw TypeError
-  if (!IS_SPEC_OBJECT(proto)) proto = $Object.prototype
+  if (!IS_SPEC_OBJECT(handler))
+    throw MakeTypeError("handler_non_object", ["create"])
+  if (!IS_SPEC_OBJECT(proto)) proto = null  // Mozilla does this...
   return %CreateJSProxy(handler, proto)
 }
 
@@ -130,3 +131,7 @@ function DerivedSetTrap(receiver, name, val) {
     configurable: true});
   return true;
 }
+
+function DerivedHasTrap(name) {
+  return !!this.getPropertyDescriptor(name)
+}
index 4997d56..ff1ad67 100644 (file)
@@ -594,12 +594,26 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateJSProxy) {
   Object* handler = args[0];
   Object* prototype = args[1];
   Object* used_prototype =
-      (prototype->IsJSObject() || prototype->IsJSProxy()) ? prototype
-          : isolate->heap()->null_value();
+      prototype->IsJSReceiver() ? prototype : isolate->heap()->null_value();
   return isolate->heap()->AllocateJSProxy(handler, used_prototype);
 }
 
 
+RUNTIME_FUNCTION(MaybeObject*, Runtime_IsJSProxy) {
+  ASSERT(args.length() == 1);
+  Object* obj = args[0];
+  return obj->IsJSProxy()
+      ? isolate->heap()->true_value() : isolate->heap()->false_value();
+}
+
+
+RUNTIME_FUNCTION(MaybeObject*, Runtime_GetHandler) {
+  ASSERT(args.length() == 1);
+  CONVERT_CHECKED(JSProxy, proxy, args[0]);
+  return proxy->handler();
+}
+
+
 RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateCatchExtensionObject) {
   ASSERT(args.length() == 2);
   CONVERT_CHECKED(String, key, args[0]);
@@ -634,6 +648,19 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) {
 }
 
 
+RUNTIME_FUNCTION(MaybeObject*, Runtime_GetPrototype) {
+  NoHandleAllocation ha;
+  ASSERT(args.length() == 1);
+  Object* obj = args[0];
+  obj = obj->GetPrototype();
+  while (obj->IsJSObject() &&
+         JSObject::cast(obj)->map()->is_hidden_prototype()) {
+    obj = obj->GetPrototype();
+  }
+  return obj;
+}
+
+
 RUNTIME_FUNCTION(MaybeObject*, Runtime_IsInPrototypeChain) {
   NoHandleAllocation ha;
   ASSERT(args.length() == 2);
index 0a0afc0..aacf535 100644 (file)
@@ -67,6 +67,7 @@ namespace internal {
   F(SpecialArrayFunctions, 1, 1) \
   F(GetGlobalReceiver, 0, 1) \
   \
+  F(GetPrototype, 1, 1) \
   F(IsInPrototypeChain, 2, 1) \
   F(SetHiddenPrototype, 2, 1) \
   \
@@ -278,6 +279,8 @@ namespace internal {
   \
   /* Harmony proxies */ \
   F(CreateJSProxy, 2, 1) \
+  F(IsJSProxy, 1, 1) \
+  F(GetHandler, 1, 1) \
   \
   /* Catch context extension objects */ \
   F(CreateCatchExtensionObject, 2, 1) \
index 469059b..a5627c8 100644 (file)
@@ -336,6 +336,7 @@ function IsInconsistentDescriptor(desc) {
   return IsAccessorDescriptor(desc) && IsDataDescriptor(desc);
 }
 
+
 // ES5 8.10.4
 function FromPropertyDescriptor(desc) {
   if (IS_UNDEFINED(desc)) return desc;
@@ -399,6 +400,23 @@ function ToPropertyDescriptor(obj) {
 }
 
 
+// For Harmony proxies.
+function ToCompletePropertyDescriptor(obj) {
+  var desc = ToPropertyDescriptor(obj)
+  if (IsGenericDescriptor(desc) || IsDataDescriptor(desc)) {
+    if (!("value" in desc)) desc.value = void 0;
+    if (!("writable" in desc)) desc.writable = false;
+  } else {
+    // Is accessor descriptor.
+    if (!("get" in desc)) desc.get = void 0;
+    if (!("set" in desc)) desc.set = void 0;
+  }
+  if (!("enumerable" in desc)) desc.enumerable = false;
+  if (!("configurable" in desc)) desc.configurable = false;
+  return desc;
+}
+
+
 function PropertyDescriptor() {
   // Initialize here so they are all in-object and have the same map.
   // Default values from ES5 8.6.1.
@@ -547,9 +565,25 @@ function ConvertDescriptorArrayToDescriptor(desc_array) {
 
 // ES5 section 8.12.2.
 function GetProperty(obj, p) {
+  if (%IsJSProxy(obj)) {
+    var handler = %GetHandler(obj);
+    var getProperty = handler.getPropertyDescriptor;
+    if (IS_UNDEFINED(getProperty)) {
+      throw MakeTypeError("handler_trap_missing",
+                          [handler, "getPropertyDescriptor"]);
+    }
+    var descriptor = getProperty.call(handler, p);
+    if (IS_UNDEFINED(descriptor)) return descriptor;
+    var desc = ToCompletePropertyDescriptor(descriptor);
+    if (!desc.configurable) {
+      throw MakeTypeError("proxy_prop_not_configurable",
+                          [handler, "getPropertyDescriptor", p, descriptor]);
+    }
+    return desc;
+  }
   var prop = GetOwnProperty(obj);
   if (!IS_UNDEFINED(prop)) return prop;
-  var proto = obj.__proto__;
+  var proto = %GetPrototype(obj);
   if (IS_NULL(proto)) return void 0;
   return GetProperty(proto, p);
 }
@@ -557,6 +591,12 @@ function GetProperty(obj, p) {
 
 // ES5 section 8.12.6
 function HasProperty(obj, p) {
+  if (%IsJSProxy(obj)) {
+    var handler = %GetHandler(obj)
+    var has = handler.has
+    if (IS_UNDEFINED(has)) has = DerivedHasTrap
+    return ToBoolean(has.call(handler, obj, p))
+  }
   var desc = GetProperty(obj, p);
   return IS_UNDEFINED(desc) ? false : true;
 }
@@ -745,7 +785,7 @@ function DefineOwnProperty(obj, p, desc, should_throw) {
 function ObjectGetPrototypeOf(obj) {
   if (!IS_SPEC_OBJECT(obj))
     throw MakeTypeError("obj_ctor_property_non_object", ["getPrototypeOf"]);
-  return obj.__proto__;
+  return %GetPrototype(obj);
 }
 
 
@@ -758,11 +798,43 @@ function ObjectGetOwnPropertyDescriptor(obj, p) {
 }
 
 
+// For Harmony proxies
+function ToStringArray(obj, trap) {
+  if (!IS_SPEC_OBJECT(obj)) {
+    throw MakeTypeError("proxy_non_object_prop_names", [obj, trap]);
+  }
+  var n = ToUint32(obj.length);
+  var array = new $Array(n);
+  var names = {}
+  for (var index = 0; index < n; index++) {
+    var s = ToString(obj[index]);
+    if (s in names) {
+      throw MakeTypeError("proxy_repeated_prop_name", [obj, trap, s])
+    }
+    array[index] = s;
+    names.s = 0;
+  }
+  return array;
+}
+
+
 // ES5 section 15.2.3.4.
 function ObjectGetOwnPropertyNames(obj) {
   if (!IS_SPEC_OBJECT(obj))
     throw MakeTypeError("obj_ctor_property_non_object", ["getOwnPropertyNames"]);
 
+  // Special handling for proxies.
+  if (%IsJSProxy(obj)) {
+    var handler = %GetHandler(obj);
+    var getOwnPropertyNames = handler.getOwnPropertyNames;
+    if (IS_UNDEFINED(getOwnPropertyNames)) {
+      throw MakeTypeError("handler_trap_missing",
+                          [handler, "getOwnPropertyNames"]);
+    }
+    var names = getOwnPropertyNames.call(handler);
+    return ToStringArray(names, "getOwnPropertyNames");
+  }
+
   // Find all the indexed properties.
 
   // Get the local element names.
index c2bc5ac..0763989 100644 (file)
@@ -363,6 +363,7 @@ static void Generate_JSConstructStubHelper(MacroAssembler* masm,
 
   // If the type of the result (stored in its map) is less than
   // FIRST_SPEC_OBJECT_TYPE, it is not an object in the ECMA sense.
+  STATIC_ASSERT(LAST_SPEC_OBJECT_TYPE == LAST_TYPE);
   __ CmpObjectType(rax, FIRST_SPEC_OBJECT_TYPE, rcx);
   __ j(above_equal, &exit);
 
index 3edb2a0..62bee87 100644 (file)
@@ -38,12 +38,35 @@ function TestGet(handler) {
 //  assertEquals(Object.getOwnPropertyDescriptor(o, "b").value, 42)
 }
 
-TestGet({get: function(r, k) { return 42 }})
-TestGet({getPropertyDescriptor: function(k) { return {value: 42} }})
-TestGet({getPropertyDescriptor: function(k) { return {get value() { return 42 }} }})
-TestGet({get: undefined, getPropertyDescriptor: function(k) { return {value: 42} }})
+TestGet({
+  get: function(r, k) { return 42 }
+})
+TestGet({
+  get: function(r, k) { return this.get2(r, k) },
+  get2: function(r, k) { return 42 }
+})
+TestGet({
+  getPropertyDescriptor: function(k) { return {value: 42} }
+})
+TestGet({
+  getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
+  getPropertyDescriptor2: function(k) { return {value: 42} }
+})
+TestGet({
+  getPropertyDescriptor: function(k) {
+    return {get value() { return 42 }}
+  }
+})
+TestGet({
+  get: undefined,
+  getPropertyDescriptor: function(k) { return {value: 42} }
+})
 
-TestGet(Proxy.create({get: function(pr, pk) { return function(r, k) { return 42 } }}))
+TestGet(Proxy.create({
+  get: function(pr, pk) {
+    return function(r, k) { return 42 }
+  }
+}))
 
 
 
@@ -64,46 +87,86 @@ function TestSet(handler) {
 //  assertEquals(44, val)
 }
 
-TestSet({set: function(r, k, v) { key = k; val = v; return true }})
-TestSet({getOwnPropertyDescriptor: function(k) { return {writable: true} },
-         defineProperty: function(k, desc) { key = k, val = desc.value }})
-TestSet({getOwnPropertyDescriptor: function(k) { return {get writable() { return true }} },
-         defineProperty: function(k, desc) { key = k, val = desc.value }})
-TestSet({getOwnPropertyDescriptor: function(k) { return {set: function(v) { key = k, val = v }} }})
-TestSet({getOwnPropertyDescriptor: function(k) { return null },
-         getPropertyDescriptor: function(k) { return {writable: true} },
-         defineProperty: function(k, desc) { key = k, val = desc.value }})
-TestSet({getOwnPropertyDescriptor: function(k) { return null },
-         getPropertyDescriptor: function(k) { return {get writable() { return true }} },
-         defineProperty: function(k, desc) { key = k, val = desc.value }})
-TestSet({getOwnPropertyDescriptor: function(k) { return null },
-         getPropertyDescriptor: function(k) { return {set: function(v) { key = k, val = v }} }})
-TestSet({getOwnPropertyDescriptor: function(k) { return null },
-         getPropertyDescriptor: function(k) { return null },
-         defineProperty: function(k, desc) { key = k, val = desc.value }})
+TestSet({
+  set: function(r, k, v) { key = k; val = v; return true }
+})
+TestSet({
+  set: function(r, k, v) { return this.set2(r, k, v) },
+  set2: function(r, k, v) { key = k; val = v; return true }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) { return {writable: true} },
+  defineProperty: function(k, desc) { key = k; val = desc.value }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) {
+    return this.getOwnPropertyDescriptor2(k)
+  },
+  getOwnPropertyDescriptor2: function(k) { return {writable: true} },
+  defineProperty: function(k, desc) { this.defineProperty2(k, desc) },
+  defineProperty2: function(k, desc) { key = k; val = desc.value }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) {
+    return {get writable() { return true }}
+  },
+  defineProperty: function(k, desc) { key = k; val = desc.value }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) {
+    return {set: function(v) { key = k; val = v }}
+  }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) { return null },
+  getPropertyDescriptor: function(k) { return {writable: true} },
+  defineProperty: function(k, desc) { key = k; val = desc.value }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) { return null },
+  getPropertyDescriptor: function(k) {
+    return {get writable() { return true }}
+  },
+  defineProperty: function(k, desc) { key = k; val = desc.value }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) { return null },
+  getPropertyDescriptor: function(k) {
+    return {set: function(v) { key = k; val = v }}
+  }
+})
+TestSet({
+  getOwnPropertyDescriptor: function(k) { return null },
+  getPropertyDescriptor: function(k) { return null },
+  defineProperty: function(k, desc) { key = k, val = desc.value }
+})
 
-TestSet(Proxy.create({get: function(pr, pk) { return function(r, k, v) { key = k; val = v; return true } }}))
+TestSet(Proxy.create({
+  get: function(pr, pk) {
+    return function(r, k, v) { key = k; val = v; return true }
+  }
+}))
 
 
 
 // Comparison.
 
-var o1 = Proxy.create({})
-var o2 = Proxy.create({})
+function TestComparison(eq) {
+  var o1 = Proxy.create({})
+  var o2 = Proxy.create({})
 
-assertTrue(o1 == o1)
-assertTrue(o2 == o2)
-assertTrue(!(o1 == o2))
-assertTrue(!(o1 == {}))
-assertTrue(!({} == o2))
-assertTrue(!({} == {}))
+  assertTrue(eq(o1, o1))
+  assertTrue(eq(o2, o2))
+  assertTrue(!eq(o1, o2))
+  assertTrue(!eq(o1, {}))
+  assertTrue(!eq({}, o2))
+  assertTrue(!eq({}, {}))
+}
 
-assertTrue(o1 === o1)
-assertTrue(o2 === o2)
-assertTrue(!(o1 === o2))
-assertTrue(!(o1 === {}))
-assertTrue(!({} === o2))
-assertTrue(!({} === {}))
+TestComparison(function(o1, o2) { return o1 == o2 })
+TestComparison(function(o1, o2) { return o1 === o2 })
+TestComparison(function(o1, o2) { return !(o1 != o2) })
+TestComparison(function(o1, o2) { return !(o1 !== o2) })
 
 
 
@@ -114,3 +177,85 @@ assertTrue(typeof Proxy.create({}) == "object")
 assertTrue("object" == typeof Proxy.create({}))
 
 // No function proxies yet.
+
+
+
+// Instanceof (instanceof).
+
+function TestInstanceof() {
+  var o = {}
+  var p1 = Proxy.create({})
+  var p2 = Proxy.create({}, o)
+  var p3 = Proxy.create({}, p2)
+
+  var f = function() {}
+  f.prototype = o
+  var f1 = function() {}
+  f1.prototype = p1
+  var f2 = function() {}
+  f2.prototype = p2
+
+  assertTrue(o instanceof Object)
+  assertFalse(o instanceof f)
+  assertFalse(o instanceof f1)
+  assertFalse(o instanceof f2)
+  assertFalse(p1 instanceof Object)
+  assertFalse(p1 instanceof f)
+  assertFalse(p1 instanceof f1)
+  assertFalse(p1 instanceof f2)
+  assertTrue(p2 instanceof Object)
+  assertTrue(p2 instanceof f)
+  assertFalse(p2 instanceof f1)
+  assertFalse(p2 instanceof f2)
+  assertTrue(p3 instanceof Object)
+  assertTrue(p3 instanceof f)
+  assertFalse(p3 instanceof f1)
+  assertTrue(p3 instanceof f2)
+}
+
+TestInstanceof()
+
+
+
+// Prototype (Object.getPrototypeOf).
+
+function TestPrototype() {
+  var o = {}
+  var p1 = Proxy.create({})
+  var p2 = Proxy.create({}, o)
+  var p3 = Proxy.create({}, p2)
+  var p4 = Proxy.create({}, 666)
+
+  assertSame(Object.getPrototypeOf(o), Object.prototype)
+  assertSame(Object.getPrototypeOf(p1), null)
+  assertSame(Object.getPrototypeOf(p2), o)
+  assertSame(Object.getPrototypeOf(p3), p2)
+  assertSame(Object.getPrototypeOf(p4), null)
+}
+
+TestPrototype()
+
+
+
+// Property names (Object.getOwnPropertyNames).
+
+function TestPropertyNames(names, handler) {
+  var p = Proxy.create(handler)
+  assertArrayEquals(names, Object.getOwnPropertyNames(p))
+}
+
+TestPropertyNames([], {
+  getOwnPropertyNames: function() { return [] }
+})
+TestPropertyNames(["a", "zz", " ", "0"], {
+  getOwnPropertyNames: function() { return ["a", "zz", " ", 0] }
+})
+TestPropertyNames(["throw", "function "], {
+  getOwnPropertyNames: function() { return this.getOwnPropertyNames2() },
+  getOwnPropertyNames2: function() { return ["throw", "function "] }
+})
+TestPropertyNames(["[object Object]"], {
+  get getOwnPropertyNames() {
+    return function() { return [{}] }
+  }
+})