Revision: 8164
Author: [email protected]
Date: Fri Jun 3 03:15:49 2011
Log: Reapply: "Make instanceof and Object.getPrototypeOf work for
proxies,
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.
[email protected],[email protected]
TEST=es5conform
Review URL: http://codereview.chromium.org/7056041
http://code.google.com/p/v8/source/detail?r=8164
Modified:
/branches/bleeding_edge/src/messages.js
/branches/bleeding_edge/src/objects-inl.h
/branches/bleeding_edge/src/objects.cc
/branches/bleeding_edge/src/proxy.js
/branches/bleeding_edge/src/runtime.cc
/branches/bleeding_edge/src/runtime.h
/branches/bleeding_edge/src/v8natives.js
/branches/bleeding_edge/src/x64/builtins-x64.cc
/branches/bleeding_edge/test/mjsunit/harmony/proxies.js
=======================================
--- /branches/bleeding_edge/src/messages.js Thu Jun 2 03:12:00 2011
+++ /branches/bleeding_edge/src/messages.js Fri Jun 3 03:15:49 2011
@@ -192,7 +192,11 @@
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"],
=======================================
--- /branches/bleeding_edge/src/objects-inl.h Fri Jun 3 00:41:37 2011
+++ /branches/bleeding_edge/src/objects-inl.h Fri Jun 3 03:15:49 2011
@@ -2961,7 +2961,7 @@
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);
}
=======================================
--- /branches/bleeding_edge/src/objects.cc Fri Jun 3 00:41:37 2011
+++ /branches/bleeding_edge/src/objects.cc Fri Jun 3 03:15:49 2011
@@ -638,7 +638,7 @@
// 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 @@
}
// Check __proto__ before interceptor.
- if (name->Equals(heap->Proto_symbol()) &&
- !IsJSContextExtensionObject()) {
+ if (name->Equals(heap->Proto_symbol()) && !IsJSContextExtensionObject())
{
result->ConstantResult(this);
return;
}
=======================================
--- /branches/bleeding_edge/src/proxy.js Thu Jun 2 03:12:00 2011
+++ /branches/bleeding_edge/src/proxy.js Fri Jun 3 03:15:49 2011
@@ -60,8 +60,9 @@
}
$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 @@
configurable: true});
return true;
}
+
+function DerivedHasTrap(name) {
+ return !!this.getPropertyDescriptor(name)
+}
=======================================
--- /branches/bleeding_edge/src/runtime.cc Thu Jun 2 03:12:00 2011
+++ /branches/bleeding_edge/src/runtime.cc Fri Jun 3 03:15:49 2011
@@ -594,10 +594,24 @@
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) {
@@ -632,6 +646,19 @@
if (!obj->IsJSObject()) return isolate->heap()->null_value();
return JSObject::cast(obj)->class_name();
}
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/runtime.h Thu Jun 2 03:12:00 2011
+++ /branches/bleeding_edge/src/runtime.h Fri Jun 3 03:15:49 2011
@@ -67,6 +67,7 @@
F(SpecialArrayFunctions, 1, 1) \
F(GetGlobalReceiver, 0, 1) \
\
+ F(GetPrototype, 1, 1) \
F(IsInPrototypeChain, 2, 1) \
F(SetHiddenPrototype, 2, 1) \
\
@@ -278,6 +279,8 @@
\
/* Harmony proxies */ \
F(CreateJSProxy, 2, 1) \
+ F(IsJSProxy, 1, 1) \
+ F(GetHandler, 1, 1) \
\
/* Catch context extension objects */ \
F(CreateCatchExtensionObject, 2, 1) \
=======================================
--- /branches/bleeding_edge/src/v8natives.js Thu Jun 2 03:12:00 2011
+++ /branches/bleeding_edge/src/v8natives.js Fri Jun 3 03:15:49 2011
@@ -335,6 +335,7 @@
function IsInconsistentDescriptor(desc) {
return IsAccessorDescriptor(desc) && IsDataDescriptor(desc);
}
+
// ES5 8.10.4
function FromPropertyDescriptor(desc) {
@@ -397,6 +398,23 @@
}
return desc;
}
+
+
+// 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() {
@@ -547,9 +565,25 @@
// 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 @@
// 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 ObjectGetPrototypeOf(obj) {
if (!IS_SPEC_OBJECT(obj))
throw MakeTypeError("obj_ctor_property_non_object",
["getPrototypeOf"]);
- return obj.__proto__;
+ return %GetPrototype(obj);
}
@@ -756,12 +796,44 @@
var desc = GetOwnProperty(obj, p);
return FromPropertyDescriptor(desc);
}
+
+
+// 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.
=======================================
--- /branches/bleeding_edge/src/x64/builtins-x64.cc Thu Jun 2 03:12:00 2011
+++ /branches/bleeding_edge/src/x64/builtins-x64.cc Fri Jun 3 03:15:49 2011
@@ -363,6 +363,7 @@
// 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);
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/proxies.js Thu Jun 2
03:12:00 2011
+++ /branches/bleeding_edge/test/mjsunit/harmony/proxies.js Fri Jun 3
03:15:49 2011
@@ -38,12 +38,35 @@
// 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(Proxy.create({get: function(pr, pk) { return function(r, k) {
return 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 }
+ }
+}))
@@ -64,46 +87,86 @@
// 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(Proxy.create({get: function(pr, pk) { return function(r, k, v) {
key = k; val = v; return true } }}))
+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 }
+ }
+}))
// Comparison.
-var o1 = Proxy.create({})
-var o2 = Proxy.create({})
-
-assertTrue(o1 == o1)
-assertTrue(o2 == o2)
-assertTrue(!(o1 == o2))
-assertTrue(!(o1 == {}))
-assertTrue(!({} == o2))
-assertTrue(!({} == {}))
-
-assertTrue(o1 === o1)
-assertTrue(o2 === o2)
-assertTrue(!(o1 === o2))
-assertTrue(!(o1 === {}))
-assertTrue(!({} === o2))
-assertTrue(!({} === {}))
+function TestComparison(eq) {
+ var o1 = Proxy.create({})
+ var o2 = Proxy.create({})
+
+ assertTrue(eq(o1, o1))
+ assertTrue(eq(o2, o2))
+ assertTrue(!eq(o1, o2))
+ assertTrue(!eq(o1, {}))
+ assertTrue(!eq({}, o2))
+ assertTrue(!eq({}, {}))
+}
+
+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("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 [{}] }
+ }
+})
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev