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.

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
This commit is contained in:
ager@chromium.org 2011-06-03 10:15:49 +00:00
parent 945bd3e70c
commit 22b5dfd395
9 changed files with 303 additions and 47 deletions

View 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"],

View 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);
}

View 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;
}

View 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)
}

View 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);

View 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) \

View 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.

View 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);

View 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 [{}] }
}
})