Search prototypes for accessor setters if interceptor returns empty value.

Extract the part of SetPropertyForResult that searches the prototype chain
for accessor setters into a separate function SetPropertyInPrototypes.
Call this function in SetPropertyPostInterceptor.

This should fix both optimized and unoptimized cases because
the cache stub for storing with interceptor calls the runtime system.

BUG=v8:1636

TEST=cctest/test-api.cc/EmptyInterceptorDoesNotShadowAccessors

Review URL: http://codereview.chromium.org/7991007

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9431 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
ulan@chromium.org 2011-09-26 14:54:57 +00:00
parent c05354ecc4
commit 16ae2551ae
3 changed files with 278 additions and 28 deletions

View File

@ -1664,6 +1664,14 @@ MaybeObject* JSObject::SetPropertyPostInterceptor(
// found. Use set property to handle all these cases.
return SetProperty(&result, name, value, attributes, strict_mode);
}
bool found = false;
MaybeObject* result_object;
result_object = SetPropertyWithCallbackSetterInPrototypes(name,
value,
attributes,
&found,
strict_mode);
if (found) return result_object;
// Add a new real property.
return AddProperty(name, value, attributes, strict_mode);
}
@ -1991,6 +1999,60 @@ MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes(
return heap->the_hole_value();
}
MaybeObject* JSObject::SetPropertyWithCallbackSetterInPrototypes(
String* name,
Object* value,
PropertyAttributes attributes,
bool* found,
StrictModeFlag strict_mode) {
LookupResult result;
LookupCallbackSetterInPrototypes(name, &result);
Heap* heap = GetHeap();
if (result.IsFound()) {
*found = true;
if (result.type() == CALLBACKS) {
return SetPropertyWithCallback(result.GetCallbackObject(),
name,
value,
result.holder(),
strict_mode);
} else if (result.type() == HANDLER) {
// We could not find a local property so let's check whether there is an
// accessor that wants to handle the property.
LookupResult accessor_result;
LookupCallbackSetterInPrototypes(name, &accessor_result);
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.
bool found = false;
// SetPropertyWithHandlerIfDefiningSetter can cause GC,
// make sure to use the handlified references after calling
// the function.
Handle<JSObject> self(this);
Handle<String> hname(name);
Handle<Object> hvalue(value);
MaybeObject* result =
accessor_result.proxy()->SetPropertyWithHandlerIfDefiningSetter(
name, value, attributes, strict_mode, &found);
if (found) return result;
// 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);
}
}
}
}
*found = false;
return heap->the_hole_value();
}
void JSObject::LookupInDescriptor(String* name, LookupResult* result) {
DescriptorArray* descriptors = map()->instance_descriptors();
@ -2623,34 +2685,14 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
}
if (!result->IsProperty() && !IsJSContextExtensionObject()) {
// We could not find a local property so let's check whether there is an
// accessor that wants to handle the property.
LookupResult accessor_result;
LookupCallbackSetterInPrototypes(name, &accessor_result);
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.
bool found = false;
Handle<JSObject> self(this);
Handle<String> hname(name);
Handle<Object> hvalue(value);
MaybeObject* result =
accessor_result.proxy()->SetPropertyWithHandlerIfDefiningSetter(
name, value, attributes, strict_mode, &found);
if (found) return result;
// The proxy does not define the property as an accessor.
// Consequently, it has no effect on setting the receiver.
// Make sure to use the handlified references at this point!
return self->AddProperty(*hname, *hvalue, attributes, strict_mode);
}
}
bool found = false;
MaybeObject* result_object;
result_object = SetPropertyWithCallbackSetterInPrototypes(name,
value,
attributes,
&found,
strict_mode);
if (found) return result_object;
}
// At this point, no GC should have happened, as this would invalidate

View File

@ -1987,6 +1987,18 @@ class JSObject: public JSReceiver {
StrictModeFlag strict_mode,
bool check_prototype);
// Searches the prototype chain for a callback setter and sets the property
// with the setter if it finds one. The '*found' flag indicates whether
// a setter was found or not.
// This function can cause GC and can return a failure result with
// '*found==true'.
MUST_USE_RESULT MaybeObject* SetPropertyWithCallbackSetterInPrototypes(
String* name,
Object* value,
PropertyAttributes attributes,
bool* found,
StrictModeFlag strict_mode);
MUST_USE_RESULT MaybeObject* DeletePropertyPostInterceptor(String* name,
DeleteMode mode);
MUST_USE_RESULT MaybeObject* DeletePropertyWithInterceptor(String* name);

View File

@ -81,6 +81,11 @@ static void ExpectString(const char* code, const char* expected) {
CHECK_EQ(expected, *ascii);
}
static void ExpectInt32(const char* code, int expected) {
Local<Value> result = CompileRun(code);
CHECK(result->IsInt32());
CHECK_EQ(expected, result->Int32Value());
}
static void ExpectBoolean(const char* code, bool expected) {
Local<Value> result = CompileRun(code);
@ -1297,6 +1302,197 @@ static v8::Handle<Value> EchoNamedProperty(Local<String> name,
return name;
}
// Helper functions for Interceptor/Accessor interaction tests
Handle<Value> SimpleAccessorGetter(Local<String> name,
const AccessorInfo& info) {
Handle<Object> self = info.This();
return self->Get(String::Concat(v8_str("accessor_"), name));
}
void SimpleAccessorSetter(Local<String> name, Local<Value> value,
const AccessorInfo& info) {
Handle<Object> self = info.This();
self->Set(String::Concat(v8_str("accessor_"), name), value);
}
Handle<Value> EmptyInterceptorGetter(Local<String> name,
const AccessorInfo& info) {
return Handle<Value>();
}
Handle<Value> EmptyInterceptorSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
return Handle<Value>();
}
Handle<Value> InterceptorGetter(Local<String> name,
const AccessorInfo& info) {
// Intercept names that start with 'interceptor_'.
String::AsciiValue ascii(name);
char* name_str = *ascii;
char prefix[] = "interceptor_";
int i;
for (i = 0; name_str[i] && prefix[i]; ++i) {
if (name_str[i] != prefix[i]) return Handle<Value>();
}
Handle<Object> self = info.This();
return self->GetHiddenValue(v8_str(name_str + i));
}
Handle<Value> InterceptorSetter(Local<String> name,
Local<Value> value,
const AccessorInfo& info) {
// Intercept accesses that set certain integer values.
if (value->IsInt32() && value->Int32Value() < 10000) {
Handle<Object> self = info.This();
self->SetHiddenValue(name, value);
return value;
}
return Handle<Value>();
}
void AddAccessor(Handle<FunctionTemplate> templ,
Handle<String> name,
v8::AccessorGetter getter,
v8::AccessorSetter setter) {
templ->PrototypeTemplate()->SetAccessor(name, getter, setter);
}
void AddInterceptor(Handle<FunctionTemplate> templ,
v8::NamedPropertyGetter getter,
v8::NamedPropertySetter setter) {
templ->InstanceTemplate()->SetNamedPropertyHandler(getter, setter);
}
THREADED_TEST(EmptyInterceptorDoesNotShadowAccessors) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddAccessor(parent, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"child.age = 10;");
ExpectBoolean("child.hasOwnProperty('age')", false);
ExpectInt32("child.age", 10);
ExpectInt32("child.accessor_age", 10);
}
THREADED_TEST(EmptyInterceptorDoesNotShadowJSAccessors) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"var parent = child.__proto__;"
"Object.defineProperty(parent, 'age', "
" {get: function(){ return this.accessor_age; }, "
" set: function(v){ this.accessor_age = v; }, "
" enumerable: true, configurable: true});"
"child.age = 10;");
ExpectBoolean("child.hasOwnProperty('age')", false);
ExpectInt32("child.age", 10);
ExpectInt32("child.accessor_age", 10);
}
THREADED_TEST(EmptyInterceptorDoesNotAffectJSProperties) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"var parent = child.__proto__;"
"parent.name = 'Alice';");
ExpectBoolean("child.hasOwnProperty('name')", false);
ExpectString("child.name", "Alice");
CompileRun("child.name = 'Bob';");
ExpectString("child.name", "Bob");
ExpectBoolean("child.hasOwnProperty('name')", true);
ExpectString("parent.name", "Alice");
}
THREADED_TEST(SwitchFromInterceptorToAccessor) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddAccessor(parent, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 0; i <= 10000; i++) setAge(i);");
// All i < 10000 go to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
// The last i goes to the accessor.
ExpectInt32("child.accessor_age", 10000);
}
THREADED_TEST(SwitchFromAccessorToInterceptor) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddAccessor(parent, v8_str("age"),
SimpleAccessorGetter, SimpleAccessorSetter);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 20000; i >= 9999; i--) setAge(i);");
// All i >= 10000 go to the accessor.
ExpectInt32("child.accessor_age", 10000);
// The last i goes to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
}
THREADED_TEST(SwitchFromInterceptorToProperty) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 0; i <= 10000; i++) setAge(i);");
// All i < 10000 go to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
// The last i goes to child's own property.
ExpectInt32("child.age", 10000);
}
THREADED_TEST(SwitchFromPropertyToInterceptor) {
v8::HandleScope scope;
Handle<FunctionTemplate> parent = FunctionTemplate::New();
Handle<FunctionTemplate> child = FunctionTemplate::New();
child->Inherit(parent);
AddInterceptor(child, InterceptorGetter, InterceptorSetter);
LocalContext env;
env->Global()->Set(v8_str("Child"), child->GetFunction());
CompileRun("var child = new Child;"
"function setAge(i){ child.age = i; };"
"for(var i = 20000; i >= 9999; i--) setAge(i);");
// All i >= 10000 go to child's own property.
ExpectInt32("child.age", 10000);
// The last i goes to the interceptor.
ExpectInt32("child.interceptor_age", 9999);
}
THREADED_TEST(NamedPropertyHandlerGetter) {
echo_named_call_count = 0;