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:
parent
945bd3e70c
commit
22b5dfd395
@ -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"],
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) \
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 },
|
||||
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 },
|
||||
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 }})
|
||||
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 [{}] }
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user