Implement Object.prototype.{hasOwnProperty, propertyIsEnumerable} for proxies.

Refactor trap invocation.
Test other Object.prototype functionality for proxies.

R=ager@chromium.org
BUG=v8:1543
TEST=

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8707 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
rossberg@chromium.org 2011-07-21 11:20:27 +00:00
parent 5fba76891b
commit 3f5bc11c55
4 changed files with 269 additions and 40 deletions

View File

@ -195,6 +195,7 @@ 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_trap_must_be_callable: ["Proxy handler ", "%0", " has non-callable '", "%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"],

View File

@ -136,6 +136,10 @@ function DerivedHasTrap(name) {
return !!this.getPropertyDescriptor(name)
}
function DerivedHasOwnTrap(name) {
return !!this.getOwnPropertyDescriptor(name)
}
function DerivedKeysTrap() {
var names = this.getOwnPropertyNames()
var enumerableNames = []

View File

@ -232,6 +232,10 @@ function ObjectValueOf() {
// ECMA-262 - 15.2.4.5
function ObjectHasOwnProperty(V) {
if (%IsJSProxy(this)) {
var handler = %GetHandler(this);
return CallTrap1(handler, "hasOwn", DerivedHasOwnTrap, TO_STRING_INLINE(V));
}
return %HasLocalProperty(TO_OBJECT_INLINE(this), TO_STRING_INLINE(V));
}
@ -249,7 +253,12 @@ function ObjectIsPrototypeOf(V) {
// ECMA-262 - 15.2.4.6
function ObjectPropertyIsEnumerable(V) {
return %IsPropertyEnumerable(ToObject(this), ToString(V));
var P = ToString(V);
if (%IsJSProxy(this)) {
var desc = GetOwnProperty(this, P);
return IS_UNDEFINED(desc) ? false : desc.isEnumerable();
}
return %IsPropertyEnumerable(ToObject(this), P);
}
@ -310,9 +319,7 @@ function ObjectKeys(obj) {
throw MakeTypeError("obj_ctor_property_non_object", ["keys"]);
if (%IsJSProxy(obj)) {
var handler = %GetHandler(obj);
var keys = handler.keys;
if (IS_UNDEFINED(keys)) keys = DerivedKeysTrap;
var names = %_CallFunction(handler, keys);
var names = CallTrap0(handler, "keys", DerivedKeysTrap);
return ToStringArray(names);
}
return %LocalKeys(obj);
@ -583,16 +590,41 @@ function ConvertDescriptorArrayToDescriptor(desc_array) {
}
// For Harmony proxies.
function GetTrap(handler, name, defaultTrap) {
var trap = handler[name];
if (IS_UNDEFINED(trap)) {
if (IS_UNDEFINED(defaultTrap)) {
throw MakeTypeError("handler_trap_missing", [handler, name]);
}
trap = defaultTrap;
} else if (!IS_FUNCTION(trap)) {
throw MakeTypeError("handler_trap_must_be_callable", [handler, name]);
}
return trap;
}
function CallTrap0(handler, name, defaultTrap) {
return %_CallFunction(handler, GetTrap(handler, name, defaultTrap));
}
function CallTrap1(handler, name, defaultTrap, x) {
return %_CallFunction(handler, x, GetTrap(handler, name, defaultTrap));
}
function CallTrap2(handler, name, defaultTrap, x, y) {
return %_CallFunction(handler, x, y, GetTrap(handler, name, defaultTrap));
}
// 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 = %_CallFunction(handler, p, getProperty);
var descriptor = CallTrap1(obj, "getPropertyDescriptor", void 0, p);
if (IS_UNDEFINED(descriptor)) return descriptor;
var desc = ToCompletePropertyDescriptor(descriptor);
if (!desc.isConfigurable()) {
@ -613,9 +645,7 @@ function GetProperty(obj, p) {
function HasProperty(obj, p) {
if (%IsJSProxy(obj)) {
var handler = %GetHandler(obj);
var has = handler.has;
if (IS_UNDEFINED(has)) has = DerivedHasTrap;
return ToBoolean(%_CallFunction(handler, obj, p, has));
return ToBoolean(CallTrap1(handler, "has", DerivedHasTrap, p));
}
var desc = GetProperty(obj, p);
return IS_UNDEFINED(desc) ? false : true;
@ -623,15 +653,11 @@ function HasProperty(obj, p) {
// ES5 section 8.12.1.
function GetOwnProperty(obj, p) {
function GetOwnProperty(obj, v) {
var p = ToString(v);
if (%IsJSProxy(obj)) {
var handler = %GetHandler(obj);
var getOwnProperty = handler.getOwnPropertyDescriptor;
if (IS_UNDEFINED(getOwnProperty)) {
throw MakeTypeError("handler_trap_missing",
[handler, "getOwnPropertyDescriptor"]);
}
var descriptor = %_CallFunction(handler, p, getOwnProperty);
var descriptor = CallTrap1(handler, "getOwnPropertyDescriptor", void 0, p);
if (IS_UNDEFINED(descriptor)) return descriptor;
var desc = ToCompletePropertyDescriptor(descriptor);
if (!desc.isConfigurable()) {
@ -644,7 +670,7 @@ function GetOwnProperty(obj, p) {
// GetOwnProperty returns an array indexed by the constants
// defined in macros.py.
// If p is not a property on obj undefined is returned.
var props = %GetOwnProperty(ToObject(obj), ToString(p));
var props = %GetOwnProperty(ToObject(obj), ToString(v));
// A false value here means that access checks failed.
if (props === false) return void 0;
@ -656,11 +682,7 @@ function GetOwnProperty(obj, p) {
// Harmony proxies.
function DefineProxyProperty(obj, p, attributes, should_throw) {
var handler = %GetHandler(obj);
var defineProperty = handler.defineProperty;
if (IS_UNDEFINED(defineProperty)) {
throw MakeTypeError("handler_trap_missing", [handler, "defineProperty"]);
}
var result = %_CallFunction(handler, p, attributes, defineProperty);
var result = CallTrap2(handler, "defineProperty", void 0, p, attributes);
if (!ToBoolean(result)) {
if (should_throw) {
throw MakeTypeError("handler_returned_false",
@ -889,12 +911,7 @@ function ObjectGetOwnPropertyNames(obj) {
// 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 = %_CallFunction(handler, getOwnPropertyNames);
var names = CallTrap0(handler, "getOwnPropertyNames", void 0);
return ToStringArray(names, "getOwnPropertyNames");
}
@ -1024,11 +1041,7 @@ 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);
var props = CallTrap0(handler, "fix", void 0);
if (IS_UNDEFINED(props)) {
throw MakeTypeError("handler_returned_undefined", [handler, "fix"]);
}

View File

@ -42,22 +42,27 @@ function TestGet(handler) {
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} }
@ -83,32 +88,38 @@ function TestGetCall(handler) {
TestGetCall({
get: function(r, k) { return function() { return 55 } }
})
TestGetCall({
get: function(r, k) { return this.get2(r, k) },
get2: function(r, k) { return function() { return 55 } }
})
TestGetCall({
getPropertyDescriptor: function(k) {
return {value: function() { return 55 }}
}
})
TestGetCall({
getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
getPropertyDescriptor2: function(k) {
return {value: function() { return 55 }}
}
})
TestGetCall({
getPropertyDescriptor: function(k) {
return {get value() { return function() { return 55 } }}
}
})
TestGetCall({
get: undefined,
getPropertyDescriptor: function(k) {
return {value: function() { return 55 }}
}
})
TestGetCall({
get: function(r, k) {
if (k == "gg") {
@ -146,14 +157,17 @@ function TestSet(handler) {
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)
@ -162,22 +176,26 @@ TestSet({
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) {
@ -185,12 +203,14 @@ TestSet({
},
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 },
@ -279,10 +299,12 @@ function TestDefine(handler) {
TestDefine({
defineProperty: function(k, d) { key = k; desc = d; return true }
})
TestDefine({
defineProperty: function(k, d) { return this.defineProperty2(k, d) },
defineProperty2: function(k, d) { key = k; desc = d; return true }
})
TestDefine(Proxy.create({
get: function(pr, pk) {
return function(k, d) { key = k; desc = d; return true }
@ -323,10 +345,12 @@ function TestDelete(handler) {
TestDelete({
'delete': function(k) { key = k; return k < "z" }
})
TestDelete({
'delete': function(k) { return this.delete2(k) },
delete2: function(k) { key = k; return k < "z" }
})
TestDelete(Proxy.create({
get: function(pr, pk) {
return function(k) { key = k; return k < "z" }
@ -363,6 +387,7 @@ TestDescriptor({
defineProperty: function(k, d) { this["__" + k] = d; return true },
getOwnPropertyDescriptor: function(k) { return this["__" + k] }
})
TestDescriptor({
defineProperty: function(k, d) { this["__" + k] = d; return true },
getOwnPropertyDescriptor: function(k) {
@ -404,7 +429,7 @@ assertTrue("object" == typeof Proxy.create({}))
// Element (in).
// Membership test (in).
var key
function TestIn(handler) {
@ -442,26 +467,31 @@ function TestIn(handler) {
TestIn({
has: function(k) { key = k; return k < "z" }
})
TestIn({
has: function(k) { return this.has2(k) },
has2: function(k) { key = k; return k < "z" }
})
TestIn({
getPropertyDescriptor: function(k) {
key = k; return k < "z" ? {value: 42} : void 0
}
})
TestIn({
getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
getPropertyDescriptor2: function(k) {
key = k; return k < "z" ? {value: 42} : void 0
}
})
TestIn({
getPropertyDescriptor: function(k) {
key = k; return k < "z" ? {get value() { return 42 }} : void 0
}
})
TestIn({
get: undefined,
getPropertyDescriptor: function(k) {
@ -477,7 +507,65 @@ TestIn(Proxy.create({
// Instanceof (instanceof).
// Own Properties (Object.prototype.hasOwnProperty).
var key
function TestHasOwn(handler) {
var o = Proxy.create(handler)
assertTrue(Object.prototype.hasOwnProperty.call(o, "a"))
assertEquals("a", key)
assertTrue(Object.prototype.hasOwnProperty.call(o, 99))
assertEquals("99", key)
assertFalse(Object.prototype.hasOwnProperty.call(o, "z"))
assertEquals("z", key)
}
TestHasOwn({
hasOwn: function(k) { key = k; return k < "z" }
})
TestHasOwn({
hasOwn: function(k) { return this.hasOwn2(k) },
hasOwn2: function(k) { key = k; return k < "z" }
})
TestHasOwn({
getOwnPropertyDescriptor: function(k) {
key = k; return k < "z" ? {value: 42} : void 0
}
})
TestHasOwn({
getOwnPropertyDescriptor: function(k) {
return this.getOwnPropertyDescriptor2(k)
},
getOwnPropertyDescriptor2: function(k) {
key = k; return k < "z" ? {value: 42} : void 0
}
})
TestHasOwn({
getOwnPropertyDescriptor: function(k) {
key = k; return k < "z" ? {get value() { return 42 }} : void 0
}
})
TestHasOwn({
hasOwn: undefined,
getOwnPropertyDescriptor: function(k) {
key = k; return k < "z" ? {value: 42} : void 0
}
})
TestHasOwn(Proxy.create({
get: function(pr, pk) {
return function(k) { key = k; return k < "z" }
}
}))
// Instanceof (instanceof)
function TestInstanceof() {
var o = {}
@ -514,7 +602,7 @@ TestInstanceof()
// Prototype (Object.getPrototypeOf).
// Prototype (Object.getPrototypeOf, Object.prototype.isPrototypeOf).
function TestPrototype() {
var o = {}
@ -528,6 +616,32 @@ function TestPrototype() {
assertSame(Object.getPrototypeOf(p2), o)
assertSame(Object.getPrototypeOf(p3), p2)
assertSame(Object.getPrototypeOf(p4), null)
assertTrue(Object.prototype.isPrototypeOf(o))
assertFalse(Object.prototype.isPrototypeOf(p1))
assertTrue(Object.prototype.isPrototypeOf(p2))
assertTrue(Object.prototype.isPrototypeOf(p3))
assertFalse(Object.prototype.isPrototypeOf(p4))
assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, o))
assertFalse(Object.prototype.isPrototypeOf.call(Object.prototype, p1))
assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, p2))
assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, p3))
assertFalse(Object.prototype.isPrototypeOf.call(Object.prototype, p4))
assertFalse(Object.prototype.isPrototypeOf.call(o, o))
assertFalse(Object.prototype.isPrototypeOf.call(o, p1))
assertTrue(Object.prototype.isPrototypeOf.call(o, p2))
assertTrue(Object.prototype.isPrototypeOf.call(o, p3))
assertFalse(Object.prototype.isPrototypeOf.call(o, p4))
assertFalse(Object.prototype.isPrototypeOf.call(p1, p1))
assertFalse(Object.prototype.isPrototypeOf.call(p1, o))
assertFalse(Object.prototype.isPrototypeOf.call(p1, p2))
assertFalse(Object.prototype.isPrototypeOf.call(p1, p3))
assertFalse(Object.prototype.isPrototypeOf.call(p1, p4))
assertFalse(Object.prototype.isPrototypeOf.call(p2, p1))
assertFalse(Object.prototype.isPrototypeOf.call(p2, p2))
assertTrue(Object.prototype.isPrototypeOf.call(p2, p3))
assertFalse(Object.prototype.isPrototypeOf.call(p2, p4))
assertFalse(Object.prototype.isPrototypeOf.call(p3, p2))
}
TestPrototype()
@ -544,13 +658,16 @@ function TestPropertyNames(names, handler) {
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 [{}] }
@ -566,22 +683,27 @@ function TestKeys(names, handler) {
TestKeys([], {
keys: function() { return [] }
})
TestKeys(["a", "zz", " ", "0"], {
keys: function() { return ["a", "zz", " ", 0] }
})
TestKeys(["throw", "function "], {
keys: function() { return this.keys2() },
keys2: function() { return ["throw", "function "] }
})
TestKeys(["[object Object]"], {
get keys() {
return function() { return [{}] }
}
})
TestKeys(["a", "0"], {
getOwnPropertyNames: function() { return ["a", 23, "zz", "", 0] },
getOwnPropertyDescriptor: function(k) { return {enumerable: k.length == 1} }
})
TestKeys(["23", "zz", ""], {
getOwnPropertyNames: function() { return this.getOwnPropertyNames2() },
getOwnPropertyNames2: function() { return ["a", 23, "zz", "", 0] },
@ -590,6 +712,7 @@ TestKeys(["23", "zz", ""], {
},
getOwnPropertyDescriptor2: function(k) { return {enumerable: k.length != 1} }
})
TestKeys(["a", "b", "c", "5"], {
get getOwnPropertyNames() {
return function() { return ["0", 4, "a", "b", "c", 5] }
@ -598,6 +721,7 @@ TestKeys(["a", "b", "c", "5"], {
return function(k) { return {enumerable: k >= "44"} }
}
})
TestKeys([], {
get getOwnPropertyNames() {
return function() { return ["a", "b", "c"] }
@ -661,6 +785,7 @@ function TestFix(names, handler) {
TestFix([], {
fix: function() { return {} }
})
TestFix(["a", "b", "c", "d", "zz"], {
fix: function() {
return {
@ -672,12 +797,14 @@ TestFix(["a", "b", "c", "d", "zz"], {
}
}
})
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() {
@ -685,3 +812,87 @@ TestFix(["b"], {
}
}
})
// String conversion (Object.prototype.toString, Object.prototype.toLocaleString)
var key
function TestToString(handler) {
var o = Proxy.create(handler)
key = ""
assertEquals("[object Object]", Object.prototype.toString.call(o))
assertEquals("", key)
assertEquals("my_proxy", Object.prototype.toLocaleString.call(o))
assertEquals("toString", key)
}
TestToString({
get: function(r, k) { key = k; return function() { return "my_proxy" } }
})
TestToString({
get: function(r, k) { return this.get2(r, k) },
get2: function(r, k) { key = k; return function() { return "my_proxy" } }
})
TestToString(Proxy.create({
get: function(pr, pk) {
return function(r, k) { key = k; return function() { return "my_proxy" } }
}
}))
// Value conversion (Object.prototype.toValue)
function TestValueOf(handler) {
var o = Proxy.create(handler)
assertSame(o, Object.prototype.valueOf.call(o))
}
TestValueOf({})
// Enumerability (Object.prototype.propertyIsEnumerable)
var key
function TestIsEnumerable(handler) {
var o = Proxy.create(handler)
assertTrue(Object.prototype.propertyIsEnumerable.call(o, "a"))
assertEquals("a", key)
assertTrue(Object.prototype.propertyIsEnumerable.call(o, 2))
assertEquals("2", key)
assertFalse(Object.prototype.propertyIsEnumerable.call(o, "z"))
assertEquals("z", key)
}
TestIsEnumerable({
getOwnPropertyDescriptor: function(k) {
key = k; return {enumerable: k < "z", configurable: true}
},
})
TestIsEnumerable({
getOwnPropertyDescriptor: function(k) {
return this.getOwnPropertyDescriptor2(k)
},
getOwnPropertyDescriptor2: function(k) {
key = k; return {enumerable: k < "z", configurable: true}
},
})
TestIsEnumerable({
getOwnPropertyDescriptor: function(k) {
key = k; return {get enumerable() { return k < "z" }, configurable: true}
},
})
TestIsEnumerable(Proxy.create({
get: function(pr, pk) {
return function(k) {
key = k; return {enumerable: k < "z", configurable: true}
}
}
}))