From 42f0a73a96818540d4debf802d094cbc05624f82 Mon Sep 17 00:00:00 2001 From: "rossberg@chromium.org" Date: Fri, 16 Sep 2011 13:38:30 +0000 Subject: [PATCH] Make proxies work as prototypes. Fix a couple of other proxy bugs along the way. Refactor trap invocation in native code. R=kmillikin@chromium.org BUG=v8:1543 TEST= Review URL: http://codereview.chromium.org/7799026 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9312 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/ic.cc | 38 ++- src/objects.cc | 327 +++++++++++++------------- src/objects.h | 26 ++- src/property.h | 13 +- src/runtime.cc | 4 +- test/mjsunit/harmony/proxies.js | 398 +++++++++++++++++++++++++++++--- 6 files changed, 572 insertions(+), 234 deletions(-) diff --git a/src/ic.cc b/src/ic.cc index 0f76a9a06c..84a62cd98f 100644 --- a/src/ic.cc +++ b/src/ic.cc @@ -1351,7 +1351,7 @@ static bool StoreICableLookup(LookupResult* lookup) { } -static bool LookupForWrite(JSReceiver* receiver, +static bool LookupForWrite(JSObject* receiver, String* name, LookupResult* lookup) { receiver->LocalLookup(name, lookup); @@ -1359,12 +1359,10 @@ static bool LookupForWrite(JSReceiver* receiver, return false; } - if (lookup->type() == INTERCEPTOR) { - JSObject* object = JSObject::cast(receiver); - if (object->GetNamedInterceptor()->setter()->IsUndefined()) { - object->LocalLookupRealNamedProperty(name, lookup); - return StoreICableLookup(lookup); - } + if (lookup->type() == INTERCEPTOR && + receiver->GetNamedInterceptor()->setter()->IsUndefined()) { + receiver->LocalLookupRealNamedProperty(name, lookup); + return StoreICableLookup(lookup); } return true; @@ -1376,28 +1374,28 @@ MaybeObject* StoreIC::Store(State state, Handle object, Handle name, Handle value) { - // If the object is undefined or null it's illegal to try to set any - // properties on it; throw a TypeError in that case. - if (object->IsUndefined() || object->IsNull()) { - return TypeError("non_object_property_store", object, name); - } + if (!object->IsJSObject()) { + // Handle proxies. + if (object->IsJSProxy()) { + return JSProxy::cast(*object)-> + SetProperty(*name, *value, NONE, strict_mode); + } + + // If the object is undefined or null it's illegal to try to set any + // properties on it; throw a TypeError in that case. + if (object->IsUndefined() || object->IsNull()) { + return TypeError("non_object_property_store", object, name); + } - if (!object->IsJSReceiver()) { // The length property of string values is read-only. Throw in strict mode. if (strict_mode == kStrictMode && object->IsString() && name->Equals(isolate()->heap()->length_symbol())) { return TypeError("strict_read_only_property", object, name); } - // Ignore stores where the receiver is not a JSObject. + // Ignore other stores where the receiver is not a JSObject. return *value; } - // Handle proxies. - if (object->IsJSProxy()) { - return JSReceiver::cast(*object)-> - SetProperty(*name, *value, NONE, strict_mode); - } - Handle receiver = Handle::cast(object); // Check if the given name is an array index. diff --git a/src/objects.cc b/src/objects.cc index 89d8c42e0a..4c36f9dd9a 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -132,27 +132,20 @@ Object* Object::ToBoolean() { void Object::Lookup(String* name, LookupResult* result) { Object* holder = NULL; - if (IsSmi()) { - Context* global_context = Isolate::Current()->context()->global_context(); - holder = global_context->number_function()->instance_prototype(); + if (IsJSReceiver()) { + holder = this; } else { - HeapObject* heap_object = HeapObject::cast(this); - if (heap_object->IsJSObject()) { - return JSObject::cast(this)->Lookup(name, result); - } else if (heap_object->IsJSProxy()) { - return result->HandlerResult(); - } Context* global_context = Isolate::Current()->context()->global_context(); - if (heap_object->IsString()) { - holder = global_context->string_function()->instance_prototype(); - } else if (heap_object->IsHeapNumber()) { + if (IsNumber()) { holder = global_context->number_function()->instance_prototype(); - } else if (heap_object->IsBoolean()) { + } else if (IsString()) { + holder = global_context->string_function()->instance_prototype(); + } else if (IsBoolean()) { holder = global_context->boolean_function()->instance_prototype(); } } ASSERT(holder != NULL); // Cannot handle null or undefined. - JSObject::cast(holder)->Lookup(name, result); + JSReceiver::cast(holder)->Lookup(name, result); } @@ -225,30 +218,17 @@ MaybeObject* Object::GetPropertyWithCallback(Object* receiver, } -MaybeObject* Object::GetPropertyWithHandler(Object* receiver_raw, - String* name_raw, - Object* handler_raw) { - Isolate* isolate = name_raw->GetIsolate(); +MaybeObject* JSProxy::GetPropertyWithHandler(Object* receiver_raw, + String* name_raw) { + Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(receiver_raw); Handle name(name_raw); - Handle handler(handler_raw); - // Extract trap function. - Handle trap_name = isolate->factory()->LookupAsciiSymbol("get"); - Handle trap(v8::internal::GetProperty(handler, trap_name)); + Handle args[] = { receiver, name }; + Handle result = CallTrap( + "get", isolate->derived_get_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); - if (trap->IsUndefined()) { - // Get the derived `get' property. - trap = isolate->derived_get_trap(); - } - - // Call trap function. - Object** args[] = { receiver.location(), name.location() }; - bool has_exception; - Handle result = - Execution::Call(trap, handler, ARRAY_SIZE(args), args, &has_exception); - if (has_exception) return Failure::Exception(); return *result; } @@ -566,14 +546,13 @@ MaybeObject* Object::GetProperty(Object* receiver, } *attributes = result->GetAttributes(); Object* value; - JSObject* holder = result->holder(); switch (result->type()) { case NORMAL: - value = holder->GetNormalizedProperty(result); + value = result->holder()->GetNormalizedProperty(result); ASSERT(!value->IsTheHole() || result->IsReadOnly()); return value->IsTheHole() ? heap->undefined_value() : value; case FIELD: - value = holder->FastPropertyAt(result->GetFieldIndex()); + value = result->holder()->FastPropertyAt(result->GetFieldIndex()); ASSERT(!value->IsTheHole() || result->IsReadOnly()); return value->IsTheHole() ? heap->undefined_value() : value; case CONSTANT_FUNCTION: @@ -582,14 +561,13 @@ MaybeObject* Object::GetProperty(Object* receiver, return GetPropertyWithCallback(receiver, result->GetCallbackObject(), name, - holder); - case HANDLER: { - JSProxy* proxy = JSProxy::cast(this); - return GetPropertyWithHandler(receiver, name, proxy->handler()); - } + result->holder()); + case HANDLER: + return result->proxy()->GetPropertyWithHandler(receiver, name); case INTERCEPTOR: { JSObject* recvr = JSObject::cast(receiver); - return holder->GetPropertyWithInterceptor(recvr, name, attributes); + return result->holder()->GetPropertyWithInterceptor( + recvr, name, attributes); } case MAP_TRANSITION: case ELEMENTS_TRANSITION: @@ -1900,12 +1878,12 @@ MaybeObject* JSObject::SetPropertyWithCallback(Object* structure, } -MaybeObject* JSObject::SetPropertyWithDefinedSetter(JSFunction* setter, - Object* value) { +MaybeObject* JSReceiver::SetPropertyWithDefinedSetter(JSFunction* setter, + Object* value) { Isolate* isolate = GetIsolate(); Handle value_handle(value, isolate); Handle fun(JSFunction::cast(setter), isolate); - Handle self(this, isolate); + Handle self(this, isolate); #ifdef ENABLE_DEBUGGER_SUPPORT Debug* debug = isolate->debug(); // Handle stepping into a setter if step into is active. @@ -1928,6 +1906,9 @@ void JSObject::LookupCallbackSetterInPrototypes(String* name, for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = pt->GetPrototype()) { + if (pt->IsJSProxy()) { + return result->HandlerResult(JSProxy::cast(pt)); + } JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); if (result->IsProperty()) { if (result->type() == CALLBACKS && !result->IsReadOnly()) return; @@ -2092,6 +2073,7 @@ void JSObject::LocalLookupRealNamedProperty(String* name, Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); + // A GlobalProxy's prototype should always be a proper JSObject. return JSObject::cast(proto)->LocalLookupRealNamedProperty(name, result); } @@ -2218,7 +2200,7 @@ MaybeObject* JSReceiver::SetProperty(LookupResult* result, PropertyAttributes attributes, StrictModeFlag strict_mode) { if (result->IsFound() && result->type() == HANDLER) { - return JSProxy::cast(this)->SetPropertyWithHandler( + return result->proxy()->SetPropertyWithHandler( key, value, attributes, strict_mode); } else { return JSObject::cast(this)->SetPropertyForResult( @@ -2232,22 +2214,11 @@ bool JSProxy::HasPropertyWithHandler(String* name_raw) { HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); - Handle handler(this->handler()); - // Extract trap function. - Handle trap_name = isolate->factory()->LookupAsciiSymbol("has"); - Handle trap(v8::internal::GetProperty(handler, trap_name)); + Handle args[] = { name }; + Handle result = CallTrap( + "has", isolate->derived_has_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); - if (trap->IsUndefined()) { - trap = isolate->derived_has_trap(); - } - - // Call trap function. - Object** args[] = { name.location() }; - bool has_exception; - Handle result = - Execution::Call(trap, handler, ARRAY_SIZE(args), args, &has_exception); - if (has_exception) return Failure::Exception(); return result->ToBoolean()->IsTrue(); } @@ -2263,23 +2234,10 @@ MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandler( Handle receiver(this); Handle name(name_raw); Handle value(value_raw); - Handle handler(this->handler()); - // Extract trap function. - Handle trap_name = isolate->factory()->LookupAsciiSymbol("set"); - Handle trap(v8::internal::GetProperty(handler, trap_name)); + Handle args[] = { receiver, name, value }; + CallTrap("set", isolate->derived_set_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); - if (trap->IsUndefined()) { - trap = isolate->derived_set_trap(); - } - - // Call trap function. - Object** args[] = { - receiver.location(), name.location(), value.location() - }; - bool has_exception; - Execution::Call(trap, handler, ARRAY_SIZE(args), args, &has_exception); - if (has_exception) return Failure::Exception(); return *value; } @@ -2291,31 +2249,16 @@ MUST_USE_RESULT MaybeObject* JSProxy::DeletePropertyWithHandler( HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); - Handle handler(this->handler()); - // Extract trap function. - Handle trap_name = isolate->factory()->LookupAsciiSymbol("delete"); - Handle trap(v8::internal::GetProperty(handler, trap_name)); + Handle args[] = { name }; + Handle result = CallTrap( + "delete", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); - if (trap->IsUndefined()) { - Handle args[] = { handler, trap_name }; - Handle error = isolate->factory()->NewTypeError( - "handler_trap_missing", HandleVector(args, ARRAY_SIZE(args))); - isolate->Throw(*error); - return Failure::Exception(); - } - - // Call trap function. - Object** args[] = { name.location() }; - bool has_exception; - Handle result = - Execution::Call(trap, handler, ARRAY_SIZE(args), args, &has_exception); - if (has_exception) return Failure::Exception(); Object* bool_result = result->ToBoolean(); - if (mode == STRICT_DELETION && - bool_result == isolate->heap()->false_value()) { - Handle args[] = { handler, trap_name }; + if (mode == STRICT_DELETION && bool_result == GetHeap()->false_value()) { + Handle trap_name = isolate->factory()->LookupAsciiSymbol("delete"); + Handle args[] = { Handle(handler()), trap_name }; Handle error = isolate->factory()->NewTypeError( "handler_failed", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); @@ -2327,36 +2270,20 @@ MUST_USE_RESULT MaybeObject* JSProxy::DeletePropertyWithHandler( MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler( JSReceiver* receiver_raw, - String* name_raw, - bool* has_exception) { + String* name_raw) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(receiver_raw); Handle name(name_raw); - Handle handler(this->handler()); - // Extract trap function. - Handle trap_name = - isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); - Handle trap(v8::internal::GetProperty(handler, trap_name)); + Handle args[] = { name }; + Handle result = CallTrap( + "getPropertyDescriptor", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return NONE; - if (trap->IsUndefined()) { - Handle args[] = { handler, trap_name }; - Handle error = isolate->factory()->NewTypeError( - "handler_trap_missing", HandleVector(args, ARRAY_SIZE(args))); - isolate->Throw(*error); - *has_exception = true; - return NONE; - } - // Call trap function. - Object** args[] = { name.location() }; - Handle result = - Execution::Call(trap, handler, ARRAY_SIZE(args), args, has_exception); - if (has_exception) return NONE; + if (result->IsUndefined()) return ABSENT; // TODO(rossberg): convert result to PropertyAttributes - USE(result); return NONE; } @@ -2376,6 +2303,34 @@ void JSProxy::Fix() { } +MUST_USE_RESULT Handle JSProxy::CallTrap( + const char* name, + Handle derived, + int argc, + Handle args[]) { + Isolate* isolate = GetIsolate(); + Handle handler(this->handler()); + + Handle trap_name = isolate->factory()->LookupAsciiSymbol(name); + Handle trap(v8::internal::GetProperty(handler, trap_name)); + if (isolate->has_pending_exception()) return trap; + + if (trap->IsUndefined()) { + if (*derived == NULL) { + Handle args[] = { handler, trap_name }; + Handle error = isolate->factory()->NewTypeError( + "handler_trap_missing", HandleVector(args, ARRAY_SIZE(args))); + isolate->Throw(*error); + return Handle(); + } + trap = Handle(derived); + } + + Object*** argv = reinterpret_cast(args); + bool threw = false; + return Execution::Call(trap, handler, argc, argv, &threw); +} + MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, String* name, @@ -2400,20 +2355,18 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, } // Check access rights if needed. - if (IsAccessCheckNeeded() - && !heap->isolate()->MayNamedAccess(this, name, v8::ACCESS_SET)) { - return SetPropertyWithFailedAccessCheck(result, - name, - value, - true, - strict_mode); + if (IsAccessCheckNeeded()) { + if (!heap->isolate()->MayNamedAccess(this, name, v8::ACCESS_SET)) { + return SetPropertyWithFailedAccessCheck( + result, name, value, true, strict_mode); + } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return value; ASSERT(proto->IsJSGlobalObject()); - return JSObject::cast(proto)->SetProperty( + return JSObject::cast(proto)->SetPropertyForResult( result, name, value, attributes, strict_mode); } @@ -2422,26 +2375,81 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, // accessor that wants to handle the property. LookupResult accessor_result; LookupCallbackSetterInPrototypes(name, &accessor_result); - if (accessor_result.IsProperty()) { - return SetPropertyWithCallback(accessor_result.GetCallbackObject(), - name, - value, - accessor_result.holder(), - strict_mode); + if (accessor_result.IsFound()) { + if (accessor_result.type() == CALLBACKS) { + return SetPropertyWithCallback(accessor_result.GetCallbackObject(), + name, + value, + accessor_result.holder(), + strict_mode); + } else if (accessor_result.type() == HANDLER) { + // There is a proxy in the prototype chain. Invoke its + // getOwnPropertyDescriptor trap. + Isolate* isolate = heap->isolate(); + Handle self(this); + Handle hname(name); + Handle hvalue(value); + Handle proxy(accessor_result.proxy()); + Handle args[] = { hname }; + Handle result = proxy->CallTrap( + "getOwnPropertyDescriptor", Handle(), ARRAY_SIZE(args), args); + if (isolate->has_pending_exception()) return Failure::Exception(); + + if (!result->IsUndefined()) { + // The proxy handler cares about this property. + // Check whether it is virtualized as an accessor. + Handle getter_name = + isolate->factory()->LookupAsciiSymbol("get"); + Handle getter( + v8::internal::GetProperty(result, getter_name)); + if (isolate->has_pending_exception()) return Failure::Exception(); + Handle setter_name = + isolate->factory()->LookupAsciiSymbol("set"); + Handle setter( + v8::internal::GetProperty(result, setter_name)); + if (isolate->has_pending_exception()) return Failure::Exception(); + + if (!setter->IsUndefined()) { + // We have a setter -- invoke it. + if (setter->IsJSFunction()) { + return proxy->SetPropertyWithDefinedSetter( + JSFunction::cast(*setter), *hvalue); + } + Handle args[] = { setter }; + Handle error = isolate->factory()->NewTypeError( + "setter_must_be_callable", HandleVector(args, ARRAY_SIZE(args))); + return isolate->Throw(*error); + } else if (!getter->IsUndefined()) { + // We have a getter but no setter -- the property may not be + // written. In strict mode, throw an error. + if (strict_mode == kNonStrictMode) return *hvalue; + Handle args[] = { hname, proxy }; + Handle error = isolate->factory()->NewTypeError( + "no_setter_in_callback", HandleVector(args, ARRAY_SIZE(args))); + return isolate->Throw(*error); + } + // The proxy does not define the property as an accessor. + // Consequently, it has no effect on setting the receiver. + return self->AddProperty(*hname, *hvalue, attributes, strict_mode); + } + } } } + + // At this point, no GC should have happened, as this would invalidate + // 'result', which we cannot handlify! + if (!result->IsFound()) { // Neither properties nor transitions found. return AddProperty(name, value, attributes, strict_mode); } if (result->IsReadOnly() && result->IsProperty()) { if (strict_mode == kStrictMode) { - HandleScope scope(heap->isolate()); - Handle key(name); - Handle holder(this); - Handle args[2] = { key, holder }; + Handle self(this); + Handle hname(name); + Handle args[] = { hname, self }; return heap->isolate()->Throw(*heap->isolate()->factory()->NewTypeError( - "strict_read_only_property", HandleVector(args, 2))); + "strict_read_only_property", HandleVector(args, ARRAY_SIZE(args)))); } else { return value; } @@ -2702,10 +2710,8 @@ PropertyAttributes JSReceiver::GetPropertyAttribute(JSReceiver* receiver, case CALLBACKS: return result->GetAttributes(); case HANDLER: { - // TODO(rossberg): propagate exceptions properly. - bool has_exception = false; - return JSProxy::cast(this)->GetPropertyAttributeWithHandler( - receiver, name, &has_exception); + return JSProxy::cast(result->proxy())->GetPropertyAttributeWithHandler( + receiver, name); } case INTERCEPTOR: return result->holder()->GetPropertyAttributeWithInterceptor( @@ -3516,15 +3522,6 @@ AccessorDescriptor* Map::FindAccessor(String* name) { void JSReceiver::LocalLookup(String* name, LookupResult* result) { - if (IsJSProxy()) { - result->HandlerResult(); - } else { - JSObject::cast(this)->LocalLookup(name, result); - } -} - - -void JSObject::LocalLookup(String* name, LookupResult* result) { ASSERT(name->IsString()); Heap* heap = GetHeap(); @@ -3533,28 +3530,36 @@ void JSObject::LocalLookup(String* name, LookupResult* result) { Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); - return JSObject::cast(proto)->LocalLookup(name, result); + return JSReceiver::cast(proto)->LocalLookup(name, result); + } + + if (IsJSProxy()) { + result->HandlerResult(JSProxy::cast(this)); + return; } // Do not use inline caching if the object is a non-global object // that requires access checks. - if (!IsJSGlobalProxy() && IsAccessCheckNeeded()) { + if (IsAccessCheckNeeded()) { result->DisallowCaching(); } + JSObject* js_object = JSObject::cast(this); + // Check __proto__ before interceptor. if (name->Equals(heap->Proto_symbol()) && !IsJSContextExtensionObject()) { - result->ConstantResult(this); + result->ConstantResult(js_object); return; } // Check for lookup interceptor except when bootstrapping. - if (HasNamedInterceptor() && !heap->isolate()->bootstrapper()->IsActive()) { - result->InterceptorResult(this); + if (js_object->HasNamedInterceptor() && + !heap->isolate()->bootstrapper()->IsActive()) { + result->InterceptorResult(js_object); return; } - LocalLookupRealNamedProperty(name, result); + js_object->LocalLookupRealNamedProperty(name, result); } @@ -3564,7 +3569,7 @@ void JSReceiver::Lookup(String* name, LookupResult* result) { for (Object* current = this; current != heap->null_value(); current = JSObject::cast(current)->GetPrototype()) { - JSObject::cast(current)->LocalLookup(name, result); + JSReceiver::cast(current)->LocalLookup(name, result); if (result->IsProperty()) return; } result->NotFound(); @@ -6217,10 +6222,10 @@ void Map::CreateBackPointers() { // Verify target. Object* source_prototype = prototype(); Object* target_prototype = target->prototype(); - ASSERT(source_prototype->IsJSObject() || + ASSERT(source_prototype->IsJSReceiver() || source_prototype->IsMap() || source_prototype->IsNull()); - ASSERT(target_prototype->IsJSObject() || + ASSERT(target_prototype->IsJSReceiver() || target_prototype->IsNull()); ASSERT(source_prototype->IsMap() || source_prototype == target_prototype); @@ -7754,7 +7759,7 @@ MaybeObject* JSReceiver::SetPrototype(Object* value, // It is sufficient to validate that the receiver is not in the new prototype // chain. for (Object* pt = value; pt != heap->null_value(); pt = pt->GetPrototype()) { - if (JSObject::cast(pt) == this) { + if (JSReceiver::cast(pt) == this) { // Cycle detected. HandleScope scope(heap->isolate()); return heap->isolate()->Throw( @@ -7769,8 +7774,8 @@ MaybeObject* JSReceiver::SetPrototype(Object* value, // hidden and set the new prototype on that object. Object* current_proto = real_receiver->GetPrototype(); while (current_proto->IsJSObject() && - JSObject::cast(current_proto)->map()->is_hidden_prototype()) { - real_receiver = JSObject::cast(current_proto); + JSReceiver::cast(current_proto)->map()->is_hidden_prototype()) { + real_receiver = JSReceiver::cast(current_proto); current_proto = current_proto->GetPrototype(); } } diff --git a/src/objects.h b/src/objects.h index 7efddfaa8f..85ef5d4897 100644 --- a/src/objects.h +++ b/src/objects.h @@ -907,9 +907,6 @@ class Object : public MaybeObject { Object* structure, String* name, Object* holder); - MUST_USE_RESULT MaybeObject* GetPropertyWithHandler(Object* receiver, - String* name, - Object* handler); MUST_USE_RESULT MaybeObject* GetPropertyWithDefinedGetter(Object* receiver, JSFunction* getter); @@ -1448,6 +1445,8 @@ class JSReceiver: public HeapObject { Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode); + MUST_USE_RESULT MaybeObject* SetPropertyWithDefinedSetter(JSFunction* setter, + Object* value); MUST_USE_RESULT MaybeObject* DeleteProperty(String* name, DeleteMode mode); @@ -1554,6 +1553,7 @@ class JSObject: public JSReceiver { // a dictionary, and it will stay a dictionary. MUST_USE_RESULT MaybeObject* PrepareSlowElementsForSort(uint32_t limit); + // Can cause GC. MUST_USE_RESULT MaybeObject* SetPropertyForResult(LookupResult* result, String* key, Object* value, @@ -1571,8 +1571,6 @@ class JSObject: public JSReceiver { Object* value, JSObject* holder, StrictModeFlag strict_mode); - MUST_USE_RESULT MaybeObject* SetPropertyWithDefinedSetter(JSFunction* setter, - Object* value); MUST_USE_RESULT MaybeObject* SetPropertyWithInterceptor( String* name, Object* value, @@ -1801,10 +1799,6 @@ class JSObject: public JSReceiver { inline Object* GetInternalField(int index); inline void SetInternalField(int index, Object* value); - // Lookup a property. If found, the result is valid and has - // detailed information. - void LocalLookup(String* name, LookupResult* result); - // The following lookup functions skip interceptors. void LocalLookupRealNamedProperty(String* name, LookupResult* result); void LookupRealNamedProperty(String* name, LookupResult* result); @@ -6708,6 +6702,10 @@ class JSProxy: public JSReceiver { bool HasPropertyWithHandler(String* name); + MUST_USE_RESULT MaybeObject* GetPropertyWithHandler( + Object* receiver, + String* name); + MUST_USE_RESULT MaybeObject* SetPropertyWithHandler( String* name, Object* value, @@ -6720,8 +6718,7 @@ class JSProxy: public JSReceiver { MUST_USE_RESULT PropertyAttributes GetPropertyAttributeWithHandler( JSReceiver* receiver, - String* name, - bool* has_exception); + String* name); // Turn this into an (empty) JSObject. void Fix(); @@ -6729,6 +6726,13 @@ class JSProxy: public JSReceiver { // Initializes the body after the handler slot. inline void InitializeBody(int object_size, Object* value); + // Invoke a trap by name. If the trap does not exist on this's handler, + // but derived_trap is non-NULL, invoke that instead. May cause GC. + Handle CallTrap(const char* name, + Handle derived_trap, + int argc, + Handle args[]); + // Dispatched behavior. #ifdef OBJECT_PRINT inline void JSProxyPrint() { diff --git a/src/property.h b/src/property.h index e7d9fc5345..857f907343 100644 --- a/src/property.h +++ b/src/property.h @@ -202,9 +202,9 @@ class LookupResult BASE_EMBEDDED { number_ = entry; } - void HandlerResult() { + void HandlerResult(JSProxy* proxy) { lookup_type_ = HANDLER_TYPE; - holder_ = NULL; + holder_ = proxy; details_ = PropertyDetails(NONE, HANDLER); cacheable_ = false; } @@ -221,7 +221,12 @@ class LookupResult BASE_EMBEDDED { JSObject* holder() { ASSERT(IsFound()); - return holder_; + return JSObject::cast(holder_); + } + + JSProxy* proxy() { + ASSERT(IsFound()); + return JSProxy::cast(holder_); } PropertyType type() { @@ -354,7 +359,7 @@ class LookupResult BASE_EMBEDDED { CONSTANT_TYPE } lookup_type_; - JSObject* holder_; + JSReceiver* holder_; int number_; bool cacheable_; PropertyDetails details_; diff --git a/src/runtime.cc b/src/runtime.cc index 7463952a9f..09b9556ddf 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -4645,7 +4645,9 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_HasProperty) { if (args[0]->IsJSReceiver()) { JSReceiver* receiver = JSReceiver::cast(args[0]); CONVERT_CHECKED(String, key, args[1]); - if (receiver->HasProperty(key)) return isolate->heap()->true_value(); + bool result = receiver->HasProperty(key); + if (isolate->has_pending_exception()) return Failure::Exception(); + return isolate->heap()->ToBoolean(result); } return isolate->heap()->false_value(); } diff --git a/test/mjsunit/harmony/proxies.js b/test/mjsunit/harmony/proxies.js index af00b44198..6ec686deb4 100644 --- a/test/mjsunit/harmony/proxies.js +++ b/test/mjsunit/harmony/proxies.js @@ -29,7 +29,8 @@ // TODO(rossberg): for-in for proxies not implemented. -// TODO(rossberg): inheritance from proxies not implemented. +// TODO(rossberg): integer-index properties not implemented properly. + // Helper. @@ -39,7 +40,89 @@ function TestWithProxies(test, handler) { } -// Getters. + +// Getting property descriptors (Object.getOwnPropertyDescriptor). + +var key + +function TestGetOwnProperty(handler) { + TestWithProxies(TestGetOwnProperty2, handler) +} + +function TestGetOwnProperty2(handler, create) { + var p = create(handler) + assertEquals(42, Object.getOwnPropertyDescriptor(p, "a").value) + assertEquals("a", key) +} + +TestGetOwnProperty({ + getOwnPropertyDescriptor: function(k) { + key = k + return {value: 42, configurable: true} + } +}) + +TestGetOwnProperty({ + getOwnPropertyDescriptor: function(k) { + return this.getOwnPropertyDescriptor2(k) + }, + getOwnPropertyDescriptor2: function(k) { + key = k + return {value: 42, configurable: true} + } +}) + +TestGetOwnProperty({ + getOwnPropertyDescriptor: function(k) { + key = k + return {get value() { return 42 }, get configurable() { return true }} + } +}) + +TestGetOwnProperty(Proxy.create({ + get: function(pr, pk) { + return function(k) { key = k; return {value: 42, configurable: true} } + } +})) + + +function TestGetOwnPropertyThrow(handler) { + TestWithProxies(TestGetOwnPropertyThrow2, handler) +} + +function TestGetOwnPropertyThrow2(handler, create) { + var p = create(handler) + assertThrows(function(){ Object.getOwnPropertyDescriptor(p, "a") }, "myexn") +} + +TestGetOwnPropertyThrow({ + getOwnPropertyDescriptor: function(k) { throw "myexn" } +}) + +TestGetOwnPropertyThrow({ + getOwnPropertyDescriptor: function(k) { + return this.getPropertyDescriptor2(k) + }, + getOwnPropertyDescriptor2: function(k) { throw "myexn" } +}) + +TestGetOwnPropertyThrow({ + getOwnPropertyDescriptor: function(k) { + return {get value() { throw "myexn" }} + } +}) + +TestGetOwnPropertyThrow(Proxy.create({ + get: function(pr, pk) { + return function(k) { throw "myexn" } + } +})) + + + +// Getters (dot, brackets). + +var key function TestGet(handler) { TestWithProxies(TestGet2, handler) @@ -48,48 +131,52 @@ function TestGet(handler) { function TestGet2(handler, create) { var p = create(handler) assertEquals(42, p.a) + assertEquals("a", key) assertEquals(42, p["b"]) + assertEquals("b", key) - // TODO(rossberg): inheritance from proxies not yet implemented. - // var o = Object.create(p, {x: {value: 88}}) - // assertEquals(42, o.a) - // assertEquals(42, o["b"]) - // assertEquals(88, o.x) - // assertEquals(88, o["x"]) + var o = Object.create(p, {x: {value: 88}}) + assertEquals(42, o.a) + assertEquals("a", key) + assertEquals(42, o["b"]) + assertEquals("b", key) + assertEquals(88, o.x) + assertEquals(88, o["x"]) } TestGet({ - get: function(r, k) { return 42 } + get: function(r, k) { key = k; return 42 } }) TestGet({ get: function(r, k) { return this.get2(r, k) }, - get2: function(r, k) { return 42 } + get2: function(r, k) { key = k; return 42 } }) TestGet({ - getPropertyDescriptor: function(k) { return {value: 42} } + getPropertyDescriptor: function(k) { key = k; return {value: 42} } }) TestGet({ getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) }, - getPropertyDescriptor2: function(k) { return {value: 42} } + getPropertyDescriptor2: function(k) { key = k; return {value: 42} } }) TestGet({ getPropertyDescriptor: function(k) { + key = k; return {get value() { return 42 }} } }) TestGet({ get: undefined, - getPropertyDescriptor: function(k) { return {value: 42} } + getPropertyDescriptor: function(k) { key = k; return {value: 42} } }) TestGet(Proxy.create({ get: function(pr, pk) { - return function(r, k) { return 42 } + return function(r, k) { key = k; return 42 } } })) @@ -101,11 +188,27 @@ function TestGetCall(handler) { function TestGetCall2(handler, create) { var p = create(handler) assertEquals(55, p.f()) + assertEquals(55, p["f"]()) assertEquals(55, p.f("unused", "arguments")) assertEquals(55, p.f.call(p)) + assertEquals(55, p["f"].call(p)) assertEquals(55, p.withargs(45, 5)) assertEquals(55, p.withargs.call(p, 11, 22)) assertEquals("6655", "66" + p) // calls p.toString + + var o = Object.create(p, {g: {value: function(x) { return x + 88 }}}) + assertEquals(55, o.f()) + assertEquals(55, o["f"]()) + assertEquals(55, o.f("unused", "arguments")) + assertEquals(55, o.f.call(o)) + assertEquals(55, o.f.call(p)) + assertEquals(55, o["f"].call(p)) + assertEquals(55, o.withargs(45, 5)) + assertEquals(55, o.withargs.call(p, 11, 22)) + assertEquals(90, o.g(2)) + assertEquals(91, o.g.call(o, 3)) + assertEquals(92, o.g.call(p, 4)) + assertEquals("6655", "66" + o) // calls o.toString } TestGetCall({ @@ -170,6 +273,12 @@ function TestGetThrow2(handler, create) { var p = create(handler) assertThrows(function(){ p.a }, "myexn") assertThrows(function(){ p["b"] }, "myexn") + + var o = Object.create(p, {x: {value: 88}}) + assertThrows(function(){ o.a }, "myexn") + assertThrows(function(){ o["b"] }, "myexn") + assertEquals(88, o.x) + assertEquals(88, o["x"]) } TestGetThrow({ @@ -302,7 +411,6 @@ TestSet(Proxy.create({ })) - function TestSetThrow(handler, create) { TestWithProxies(TestSetThrow2, handler) } @@ -422,6 +530,77 @@ TestSetThrow(Proxy.create({ })) +var key +var val + +function TestSetForDerived(handler, create) { + TestWithProxies(TestSetForDerived2, handler) +} + +function TestSetForDerived2(handler, create) { + var p = create(handler) + var o = Object.create(p, {x: {value: 88, writable: true}}) + + key = "" + assertEquals(48, o.x = 48) + assertEquals("", key) // trap not invoked + assertEquals(48, o.x) + + assertEquals(49, o.y = 49) + assertEquals("y", key) + assertEquals(49, o.y) + + assertEquals(44, o.p_writable = 44) + assertEquals("p_writable", key) + assertEquals(44, o.p_writable) + + assertEquals(45, o.p_nonwritable = 45) + assertEquals("p_nonwritable", key) + assertEquals(45, o.p_nonwritable) + + assertEquals(46, o.p_setter = 46) + assertEquals("p_setter", key) + assertEquals(46, val) // written to parent + assertFalse(Object.prototype.hasOwnProperty.call(o, "p_setter")) + + val = "" + assertEquals(47, o.p_nosetter = 47) + assertEquals("p_nosetter", key) + assertEquals("", val) // not written at all + assertFalse(Object.prototype.hasOwnProperty.call(o, "p_nosetter")); + + key = "" + assertThrows(function(){ "use strict"; o.p_nosetter = 50 }, TypeError) + assertEquals("p_nosetter", key) + assertEquals("", val) // not written at all + + assertThrows(function(){ o.p_throw = 51 }, "myexn") + assertEquals("p_throw", key) + + assertThrows(function(){ o.p_setterthrow = 52 }, "myexn") + assertEquals("p_setterthrow", key) +} + +TestSetForDerived({ + getOwnPropertyDescriptor: function(k) { + key = k; + switch (k) { + case "p_writable": return {writable: true} + case "p_nonwritable": return {writable: false} + case "p_setter":return {set: function(x) { val = x }} + case "p_nosetter": return {get: function() { return 1 }} + case "p_throw": throw "myexn" + case "p_setterthrow": return {set: function(x) { throw "myexn" }} + default: return undefined + } + } +}) + + +// TODO(rossberg): TestSetReject, returning false +// TODO(rossberg): TestGetProperty, TestSetProperty + + // Property definition (Object.defineProperty and Object.defineProperties). @@ -828,7 +1007,7 @@ TestIn({ }) TestIn({ - get: undefined, + has: undefined, getPropertyDescriptor: function(k) { key = k; return k < "z" ? {value: 42} : void 0 } @@ -874,7 +1053,7 @@ TestInThrow({ }) TestInThrow({ - get: undefined, + has: undefined, getPropertyDescriptor: function(k) { throw "myexn" } }) @@ -889,6 +1068,101 @@ TestInThrow(Proxy.create({ })) +/* TODO(rossberg): does not work yet, JSProxy::GetPropertyAttributeWithHandler + * is not fully implemented.*/ +function TestInForDerived(handler) { + TestWithProxies(TestInForDerived2, handler) +} + +function TestInForDerived2(handler, create) { + var p = create(handler) + var o = Object.create(p) + assertTrue("a" in o) + assertEquals("a", key) +// TODO(rossberg): integer indexes not correctly imlemeted yet +// assertTrue(99 in o) +// assertEquals("99", key) + assertFalse("z" in o) + assertEquals("z", key) + + assertEquals(2, ("a" in o) ? 2 : 0) + assertEquals(0, !("a" in o) ? 2 : 0) + assertEquals(0, ("zzz" in o) ? 2 : 0) + assertEquals(2, !("zzz" in o) ? 2 : 0) + + if ("b" in o) { + } else { + assertTrue(false) + } + assertEquals("b", key) + + if ("zz" in o) { + assertTrue(false) + } + assertEquals("zz", key) + + if (!("c" in o)) { + assertTrue(false) + } + assertEquals("c", key) + + if (!("zzz" in o)) { + } else { + assertTrue(false) + } + assertEquals("zzz", key) +} + +TestInForDerived({ + getPropertyDescriptor: function(k) { + key = k; return k < "z" ? {value: 42} : void 0 + } +}) + +TestInForDerived({ + getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) }, + getPropertyDescriptor2: function(k) { + key = k; return k < "z" ? {value: 42} : void 0 + } +}) + +TestInForDerived({ + getPropertyDescriptor: function(k) { + key = k; return k < "z" ? {get value() { return 42 }} : void 0 + } +}) + +/* TODO(rossberg): this will work once we implement the newest proposal + * regarding default traps for getPropertyDescriptor. +TestInForDerived({ + getOwnPropertyDescriptor: function(k) { + key = k; return k < "z" ? {value: 42} : void 0 + } +}) + +TestInForDerived({ + getOwnPropertyDescriptor: function(k) { + return this.getOwnPropertyDescriptor2(k) + }, + getOwnPropertyDescriptor2: function(k) { + key = k; return k < "z" ? {value: 42} : void 0 + } +}) + +TestInForDerived({ + getOwnPropertyDescriptor: function(k) { + key = k; return k < "z" ? {get value() { return 42 }} : void 0 + } +}) +*/ + +TestInForDerived(Proxy.create({ + get: function(pr, pk) { + return function(k) { key = k; return k < "z" ? {value: 42} : void 0 } + } +})) + + // Own Properties (Object.prototype.hasOwnProperty). @@ -1004,34 +1278,46 @@ TestHasOwnThrow(Proxy.create({ // Instanceof (instanceof) function TestInstanceof() { - var o = {} + var o1 = {} var p1 = Proxy.create({}) - var p2 = Proxy.create({}, o) + var p2 = Proxy.create({}, o1) var p3 = Proxy.create({}, p2) + var o2 = Object.create(p2) var f0 = function() {} - f0.prototype = o + f0.prototype = o1 var f1 = function() {} f1.prototype = p1 var f2 = function() {} f2.prototype = p2 + var f3 = function() {} + f3.prototype = o2 - assertTrue(o instanceof Object) - assertFalse(o instanceof f0) - assertFalse(o instanceof f1) - assertFalse(o instanceof f2) + assertTrue(o1 instanceof Object) + assertFalse(o1 instanceof f0) + assertFalse(o1 instanceof f1) + assertFalse(o1 instanceof f2) + assertFalse(o1 instanceof f3) assertFalse(p1 instanceof Object) assertFalse(p1 instanceof f0) assertFalse(p1 instanceof f1) assertFalse(p1 instanceof f2) + assertFalse(p1 instanceof f3) assertTrue(p2 instanceof Object) assertTrue(p2 instanceof f0) assertFalse(p2 instanceof f1) assertFalse(p2 instanceof f2) + assertFalse(p2 instanceof f3) assertTrue(p3 instanceof Object) assertTrue(p3 instanceof f0) assertFalse(p3 instanceof f1) assertTrue(p3 instanceof f2) + assertFalse(p3 instanceof f3) + assertTrue(o2 instanceof Object) + assertTrue(o2 instanceof f0) + assertFalse(o2 instanceof f1) + assertTrue(o2 instanceof f2) + assertFalse(o2 instanceof f3) var f = Proxy.createFunction({}, function() {}) assertTrue(f instanceof Function) @@ -1044,43 +1330,57 @@ TestInstanceof() // Prototype (Object.getPrototypeOf, Object.prototype.isPrototypeOf). function TestPrototype() { - var o = {} + var o1 = {} var p1 = Proxy.create({}) - var p2 = Proxy.create({}, o) + var p2 = Proxy.create({}, o1) var p3 = Proxy.create({}, p2) var p4 = Proxy.create({}, 666) + var o2 = Object.create(p3) - assertSame(Object.getPrototypeOf(o), Object.prototype) + assertSame(Object.getPrototypeOf(o1), Object.prototype) assertSame(Object.getPrototypeOf(p1), null) - assertSame(Object.getPrototypeOf(p2), o) + assertSame(Object.getPrototypeOf(p2), o1) assertSame(Object.getPrototypeOf(p3), p2) assertSame(Object.getPrototypeOf(p4), null) + assertSame(Object.getPrototypeOf(o2), p3) - assertTrue(Object.prototype.isPrototypeOf(o)) + assertTrue(Object.prototype.isPrototypeOf(o1)) 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)) + assertTrue(Object.prototype.isPrototypeOf(o2)) + assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, o1)) 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)) + assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, o2)) + assertFalse(Object.prototype.isPrototypeOf.call(o1, o1)) + assertFalse(Object.prototype.isPrototypeOf.call(o1, p1)) + assertTrue(Object.prototype.isPrototypeOf.call(o1, p2)) + assertTrue(Object.prototype.isPrototypeOf.call(o1, p3)) + assertFalse(Object.prototype.isPrototypeOf.call(o1, p4)) + assertTrue(Object.prototype.isPrototypeOf.call(o1, o2)) assertFalse(Object.prototype.isPrototypeOf.call(p1, p1)) - assertFalse(Object.prototype.isPrototypeOf.call(p1, o)) + assertFalse(Object.prototype.isPrototypeOf.call(p1, o1)) 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(p1, o2)) 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)) + assertTrue(Object.prototype.isPrototypeOf.call(p2, o2)) assertFalse(Object.prototype.isPrototypeOf.call(p3, p2)) + assertTrue(Object.prototype.isPrototypeOf.call(p3, o2)) + assertFalse(Object.prototype.isPrototypeOf.call(o2, o1)) + assertFalse(Object.prototype.isPrototypeOf.call(o2, p1)) + assertFalse(Object.prototype.isPrototypeOf.call(o2, p2)) + assertFalse(Object.prototype.isPrototypeOf.call(o2, p3)) + assertFalse(Object.prototype.isPrototypeOf.call(o2, p4)) + assertFalse(Object.prototype.isPrototypeOf.call(o2, o2)) var f = Proxy.createFunction({}, function() {}) assertSame(Object.getPrototypeOf(f), Function.prototype) @@ -1265,7 +1565,7 @@ TestKeysThrow([], { // Fixing (Object.freeze, Object.seal, Object.preventExtensions, // Object.isFrozen, Object.isSealed, Object.isExtensible) -// TODO(rossberg): use TestWithProxies to include funciton proxies +// TODO(rossberg): use TestWithProxies to include function proxies function TestFix(names, handler) { var proto = {p: 77} var assertFixing = function(o, s, f, e) { @@ -1312,6 +1612,14 @@ function TestFix(names, handler) { Object.keys(p3).sort()) assertEquals(proto, Object.getPrototypeOf(p3)) assertEquals(77, p3.p) + + var p = Proxy.create(handler, proto) + var o = Object.create(p) + assertFixing(p, false, false, true) + assertFixing(o, false, false, true) + Object.freeze(o) + assertFixing(p, false, false, true) + assertFixing(o, true, true, false) } TestFix([], { @@ -1424,6 +1732,13 @@ function TestToString(handler) { assertEquals("my_proxy", Object.prototype.toLocaleString.call(f)) assertEquals("toString", key) assertDoesNotThrow(function(){ Function.prototype.toString.call(f) }) + + var o = Object.create(p) + key = "" + assertEquals("[object Object]", Object.prototype.toString.call(o)) + assertEquals("", key) + assertEquals("my_proxy", Object.prototype.toLocaleString.call(o)) + assertEquals("toString", key) } TestToString({ @@ -1450,6 +1765,10 @@ function TestToStringThrow(handler) { var f = Proxy.createFunction(handler, function() {}) assertEquals("[object Function]", Object.prototype.toString.call(f)) assertThrows(function(){ Object.prototype.toLocaleString.call(f) }, "myexn") + + var o = Object.create(p) + assertEquals("[object Object]", Object.prototype.toString.call(o)) + assertThrows(function(){ Object.prototype.toLocaleString.call(o) }, "myexn") } TestToStringThrow({ @@ -1508,6 +1827,11 @@ function TestIsEnumerable2(handler, create) { assertEquals("2", key) assertFalse(Object.prototype.propertyIsEnumerable.call(p, "z")) assertEquals("z", key) + + var o = Object.create(p) + key = "" + assertFalse(Object.prototype.propertyIsEnumerable.call(o, "a")) + assertEquals("", key) // trap not invoked } TestIsEnumerable({