From 22558609924b56484b1b9cd3c7c49f1ad2d68cc0 Mon Sep 17 00:00:00 2001 From: "rossberg@chromium.org" Date: Wed, 1 Jun 2011 17:44:08 +0000 Subject: [PATCH] Make instanceof and Object.getPrototypeOf work for proxies, plus a few other tweaks. R=kmillikin@chromium.org Review URL: http://codereview.chromium.org/7080053 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8150 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/messages.js | 4 + src/objects-inl.h | 2 +- src/objects.cc | 5 +- src/proxy.js | 9 +- src/runtime.cc | 27 ++++- src/runtime.h | 3 + src/v8natives.js | 76 +++++++++++++- src/x64/builtins-x64.cc | 1 + test/mjsunit/harmony/proxies.js | 219 +++++++++++++++++++++++++++++++++------- 9 files changed, 299 insertions(+), 47 deletions(-) diff --git a/src/messages.js b/src/messages.js index b564f35..e390f7d 100644 --- a/src/messages.js +++ b/src/messages.js @@ -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"], diff --git a/src/objects-inl.h b/src/objects-inl.h index 29a368c..9d1923c 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -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); } diff --git a/src/objects.cc b/src/objects.cc index bf881e9..47b964e 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -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(); @@ -3345,8 +3345,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; } diff --git a/src/proxy.js b/src/proxy.js index d90e5d4..cb9c020 100644 --- a/src/proxy.js +++ b/src/proxy.js @@ -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) +} diff --git a/src/runtime.cc b/src/runtime.cc index 061170b..55d5c92 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -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,15 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_GetPrototype) { + NoHandleAllocation ha; + ASSERT(args.length() == 1); + Object* obj = args[0]; + if (obj->IsJSGlobalProxy()) obj = obj->GetPrototype(); + return obj->GetPrototype(); +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_IsInPrototypeChain) { NoHandleAllocation ha; ASSERT(args.length() == 2); diff --git a/src/runtime.h b/src/runtime.h index 0a0afc0..aacf535 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -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) \ diff --git a/src/v8natives.js b/src/v8natives.js index 469059b..a5627c8 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -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. diff --git a/src/x64/builtins-x64.cc b/src/x64/builtins-x64.cc index c2bc5ac..0763989 100644 --- a/src/x64/builtins-x64.cc +++ b/src/x64/builtins-x64.cc @@ -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); diff --git a/test/mjsunit/harmony/proxies.js b/test/mjsunit/harmony/proxies.js index 3edb2a0..62bee87 100644 --- a/test/mjsunit/harmony/proxies.js +++ b/test/mjsunit/harmony/proxies.js @@ -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 [{}] } + } +}) -- 2.7.4