Commit 42f0a73a authored by rossberg@chromium.org's avatar rossberg@chromium.org

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
parent ff5e1c98
......@@ -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> object,
Handle<String> name,
Handle<Object> 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<JSObject> receiver = Handle<JSObject>::cast(object);
// Check if the given name is an array index.
......
......@@ -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<Object> receiver(receiver_raw);
Handle<Object> name(name_raw);
Handle<Object> handler(handler_raw);
// Extract trap function.
Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol("get");
Handle<Object> trap(v8::internal::GetProperty(handler, trap_name));
Handle<Object> args[] = { receiver, name };
Handle<Object> 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<Object> 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<Object> value_handle(value, isolate);
Handle<JSFunction> fun(JSFunction::cast(setter), isolate);
Handle<JSObject> self(this, isolate);
Handle<JSReceiver> 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<Object> receiver(this);
Handle<Object> name(name_raw);
Handle<Object> handler(this->handler());
// Extract trap function.
Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol("has");
Handle<Object> trap(v8::internal::GetProperty(handler, trap_name));
Handle<Object> args[] = { name };
Handle<Object> 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<Object> 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<Object> receiver(this);
Handle<Object> name(name_raw);
Handle<Object> value(value_raw);
Handle<Object> handler(this->handler());
// Extract trap function.
Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol("set");
Handle<Object> trap(v8::internal::GetProperty(handler, trap_name));
Handle<Object> 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<Object> receiver(this);
Handle<Object> name(name_raw);
Handle<Object> handler(this->handler());
// Extract trap function.
Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol("delete");
Handle<Object> trap(v8::internal::GetProperty(handler, trap_name));
Handle<Object> args[] = { name };
Handle<Object> result = CallTrap(
"delete", Handle<Object>(), ARRAY_SIZE(args), args);
if (isolate->has_pending_exception()) return Failure::Exception();
if (trap->IsUndefined()) {
Handle<Object> args[] = { handler, trap_name };
Handle<Object> 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<Object> 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<Object> args[] = { handler, trap_name };
if (mode == STRICT_DELETION && bool_result == GetHeap()->false_value()) {
Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol("delete");
Handle<Object> args[] = { Handle<Object>(handler()), trap_name };
Handle<Object> 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<JSReceiver> receiver(receiver_raw);
Handle<Object> name(name_raw);
Handle<Object> handler(this->handler());
// Extract trap function.
Handle<String> trap_name =
isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor");
Handle<Object> trap(v8::internal::GetProperty(handler, trap_name));
Handle<Object> args[] = { name };
Handle<Object> result = CallTrap(
"getPropertyDescriptor", Handle<Object>(), ARRAY_SIZE(args), args);
if (isolate->has_pending_exception()) return NONE;
if (trap->IsUndefined()) {
Handle<Object> args[] = { handler, trap_name };
Handle<Object> 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<Object> 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<Object> JSProxy::CallTrap(
const char* name,
Handle<Object> derived,
int argc,
Handle<Object> args[]) {
Isolate* isolate = GetIsolate();
Handle<Object> handler(this->handler());
Handle<String> trap_name = isolate->factory()->LookupAsciiSymbol(name);
Handle<Object> trap(v8::internal::GetProperty(handler, trap_name));
if (isolate->has_pending_exception()) return trap;
if (trap->IsUndefined()) {
if (*derived == NULL) {
Handle<Object> args[] = { handler, trap_name };
Handle<Object> error = isolate->factory()->NewTypeError(
"handler_trap_missing", HandleVector(args, ARRAY_SIZE(args)));
isolate->Throw(*error);
return Handle<Object>();
}
trap = Handle<Object>(derived);
}
Object*** argv = reinterpret_cast<Object***>(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<JSObject> self(this);
Handle<String> hname(name);
Handle<Object> hvalue(value);
Handle<JSProxy> proxy(accessor_result.proxy());
Handle<Object> args[] = { hname };
Handle<Object> result = proxy->CallTrap(
"getOwnPropertyDescriptor", Handle<Object>(), 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<String> getter_name =
isolate->factory()->LookupAsciiSymbol("get");
Handle<Object> getter(
v8::internal::GetProperty(result, getter_name));
if (isolate->has_pending_exception()) return Failure::Exception();
Handle<String> setter_name =
isolate->factory()->LookupAsciiSymbol("set");
Handle<Object> 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<Object> args[] = { setter };
Handle<Object> 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<Object> args[] = { hname, proxy };
Handle<Object> 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<String> key(name);
Handle<Object> holder(this);
Handle<Object> args[2] = { key, holder };
Handle<JSObject> self(this);
Handle<String> hname(name);
Handle<Object> 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();
}
}
......
......@@ -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<Object> CallTrap(const char* name,
Handle<Object> derived_trap,
int argc,
Handle<Object> args[]);
// Dispatched behavior.
#ifdef OBJECT_PRINT
inline void JSProxyPrint() {
......
......@@ -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_;
......
......@@ -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();
}
......
......@@ -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
assertTrue(o instanceof Object)
assertFalse(o instanceof f0)
assertFalse(o instanceof f1)
assertFalse(o instanceof f2)
var f3 = function() {}
f3.prototype = o2
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({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment