From d26e6dd0b4b95b61598b2a66413ed422c94c2c04 Mon Sep 17 00:00:00 2001 From: "christian.plesner.hansen@gmail.com" Date: Tue, 21 Apr 2009 13:28:11 +0000 Subject: [PATCH] force-set git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1751 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 3 ++ src/api.cc | 20 +++++++++ src/handles.cc | 9 ++++ src/handles.h | 5 +++ src/objects.cc | 24 ++++++---- src/runtime.cc | 53 ++++++++++++++++++++++ src/runtime.h | 5 +++ test/cctest/test-api.cc | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 225 insertions(+), 9 deletions(-) diff --git a/include/v8.h b/include/v8.h index 892507b..ec10c05 100644 --- a/include/v8.h +++ b/include/v8.h @@ -1045,6 +1045,9 @@ class V8EXPORT Object : public Value { bool Set(Handle key, Handle value, PropertyAttribute attribs = None); + bool ForceSet(Handle key, + Handle value, + PropertyAttribute attribs = None); Local Get(Handle key); // TODO(1245389): Replace the type-specific versions of these diff --git a/src/api.cc b/src/api.cc index 1a55830..9fd1ee0 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1835,6 +1835,26 @@ bool v8::Object::Set(v8::Handle key, v8::Handle value, } +bool v8::Object::ForceSet(v8::Handle key, + v8::Handle value, + v8::PropertyAttribute attribs) { + ON_BAILOUT("v8::Object::ForceSet()", return false); + ENTER_V8; + i::Handle self = Utils::OpenHandle(this); + i::Handle key_obj = Utils::OpenHandle(*key); + i::Handle value_obj = Utils::OpenHandle(*value); + EXCEPTION_PREAMBLE(); + i::Handle obj = i::ForceSetProperty( + self, + key_obj, + value_obj, + static_cast(attribs)); + has_pending_exception = obj.is_null(); + EXCEPTION_BAILOUT_CHECK(false); + return true; +} + + Local v8::Object::Get(v8::Handle key) { ON_BAILOUT("v8::Object::Get()", return Local()); ENTER_V8; diff --git a/src/handles.cc b/src/handles.cc index b023201..99161ce 100644 --- a/src/handles.cc +++ b/src/handles.cc @@ -212,6 +212,15 @@ Handle SetProperty(Handle object, } +Handle ForceSetProperty(Handle object, + Handle key, + Handle value, + PropertyAttributes attributes) { + CALL_HEAP_FUNCTION( + Runtime::ForceSetObjectProperty(object, key, value, attributes), Object); +} + + Handle IgnoreAttributesAndSetLocalProperty( Handle object, Handle key, diff --git a/src/handles.h b/src/handles.h index c8e534e..9cc1db4 100644 --- a/src/handles.h +++ b/src/handles.h @@ -196,6 +196,11 @@ Handle SetProperty(Handle object, Handle value, PropertyAttributes attributes); +Handle ForceSetProperty(Handle object, + Handle key, + Handle value, + PropertyAttributes attributes); + Handle IgnoreAttributesAndSetLocalProperty(Handle object, Handle key, Handle value, diff --git a/src/objects.cc b/src/objects.cc index 82f93cc..4b05ef3 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -1783,6 +1783,17 @@ Object* JSObject::IgnoreAttributesAndSetLocalProperty( && !Top::MayNamedAccess(this, name, v8::ACCESS_SET)) { return SetPropertyWithFailedAccessCheck(result, name, value); } + + if (IsJSGlobalProxy()) { + Object* proto = GetPrototype(); + if (proto->IsNull()) return value; + ASSERT(proto->IsJSGlobalObject()); + return JSObject::cast(proto)->IgnoreAttributesAndSetLocalProperty( + name, + value, + attributes); + } + // Check for accessor in prototype chain removed here in clone. if (result->IsNotFound()) { return AddProperty(name, value, attributes); @@ -1803,20 +1814,15 @@ Object* JSObject::IgnoreAttributesAndSetLocalProperty( return AddFastPropertyUsingMap(result->GetTransitionMap(), name, value); - } else { - return ConvertDescriptorToField(name, value, attributes); } + return ConvertDescriptorToField(name, value, attributes); case CONSTANT_FUNCTION: if (value == result->GetConstantFunction()) return value; // Only replace the function if necessary. return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); - case CALLBACKS: - return SetPropertyWithCallback(result->GetCallbackObject(), - name, - value, - result->holder()); - case INTERCEPTOR: - return SetPropertyWithInterceptor(name, value, attributes); + case CALLBACKS: case INTERCEPTOR: + // Override callback in clone + return ConvertDescriptorToField(name, value, attributes); case CONSTANT_TRANSITION: // Replace with a MAP_TRANSITION to a new map with a FIELD, even // if the value is a function. diff --git a/src/runtime.cc b/src/runtime.cc index 309700e..2b350a5 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -2688,6 +2688,59 @@ Object* Runtime::SetObjectProperty(Handle object, } +Object* Runtime::ForceSetObjectProperty(Handle js_object, + Handle key, + Handle value, + PropertyAttributes attr) { + HandleScope scope; + + // Check if the given key is an array index. + uint32_t index; + if (Array::IndexFromObject(*key, &index)) { + ASSERT(attr == NONE); + + // In Firefox/SpiderMonkey, Safari and Opera you can access the characters + // of a string using [] notation. We need to support this too in + // JavaScript. + // In the case of a String object we just need to redirect the assignment to + // the underlying string if the index is in range. Since the underlying + // string does nothing with the assignment then we can ignore such + // assignments. + if (js_object->IsStringObjectWithCharacterAt(index)) { + return *value; + } + + return js_object->SetElement(index, *value); + } + + if (key->IsString()) { + if (Handle::cast(key)->AsArrayIndex(&index)) { + ASSERT(attr == NONE); + return js_object->SetElement(index, *value); + } else { + Handle key_string = Handle::cast(key); + key_string->TryFlattenIfNotFlat(); + return js_object->IgnoreAttributesAndSetLocalProperty(*key_string, + *value, + attr); + } + } + + // Call-back into JavaScript to convert the key to a string. + bool has_pending_exception = false; + Handle converted = Execution::ToString(key, &has_pending_exception); + if (has_pending_exception) return Failure::Exception(); + Handle name = Handle::cast(converted); + + if (name->AsArrayIndex(&index)) { + ASSERT(attr == NONE); + return js_object->SetElement(index, *value); + } else { + return js_object->IgnoreAttributesAndSetLocalProperty(*name, *value, attr); + } +} + + static Object* Runtime_SetProperty(Arguments args) { NoHandleAllocation ha; RUNTIME_ASSERT(args.length() == 3 || args.length() == 4); diff --git a/src/runtime.h b/src/runtime.h index 4d52b31..6bd19f6 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -373,6 +373,11 @@ class Runtime : public AllStatic { Handle value, PropertyAttributes attr); + static Object* ForceSetObjectProperty(Handle object, + Handle key, + Handle value, + PropertyAttributes attr); + static Object* GetObjectProperty(Handle object, Handle key); // This function is used in FunctionNameUsing* tests. diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index ccbb262..eaba627 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -6238,3 +6238,118 @@ TEST(ReadOnlyPropertyInGlobalProto) { res = CompileRun("function f() { with (this) { y = 42 }; return y; }; f()"); CHECK_EQ(v8::Integer::New(42), res); } + +static int force_set_set_count = 0; +static int force_set_get_count = 0; +bool pass_on_get = false; + +static v8::Handle ForceSetGetter(v8::Local name, + const v8::AccessorInfo& info) { + force_set_get_count++; + if (pass_on_get) { + return v8::Handle(); + } else { + return v8::Int32::New(3); + } +} + +static void ForceSetSetter(v8::Local name, + v8::Local value, + const v8::AccessorInfo& info) { + force_set_set_count++; +} + +static v8::Handle ForceSetInterceptSetter( + v8::Local name, + v8::Local value, + const v8::AccessorInfo& info) { + force_set_set_count++; + return v8::Undefined(); +} + +TEST(ForceSet) { + force_set_get_count = 0; + force_set_set_count = 0; + pass_on_get = false; + + v8::HandleScope scope; + v8::Handle templ = v8::ObjectTemplate::New(); + v8::Handle access_property = v8::String::New("a"); + templ->SetAccessor(access_property, ForceSetGetter, ForceSetSetter); + LocalContext context(NULL, templ); + v8::Handle global = context->Global(); + + // Ordinary properties + v8::Handle simple_property = v8::String::New("p"); + global->Set(simple_property, v8::Int32::New(4), v8::ReadOnly); + CHECK_EQ(4, global->Get(simple_property)->Int32Value()); + // This should fail because the property is read-only + global->Set(simple_property, v8::Int32::New(5)); + CHECK_EQ(4, global->Get(simple_property)->Int32Value()); + // This should succeed even though the property is read-only + global->ForceSet(simple_property, v8::Int32::New(6)); + CHECK_EQ(6, global->Get(simple_property)->Int32Value()); + + // Accessors + CHECK_EQ(0, force_set_set_count); + CHECK_EQ(0, force_set_get_count); + CHECK_EQ(3, global->Get(access_property)->Int32Value()); + // CHECK_EQ the property shouldn't override it, just call the setter + // which in this case does nothing. + global->Set(access_property, v8::Int32::New(7)); + CHECK_EQ(3, global->Get(access_property)->Int32Value()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(2, force_set_get_count); + // Forcing the property to be set should override the accessor without + // calling it + global->ForceSet(access_property, v8::Int32::New(8)); + CHECK_EQ(8, global->Get(access_property)->Int32Value()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(2, force_set_get_count); +} + +TEST(ForceSetWithInterceptor) { + force_set_get_count = 0; + force_set_set_count = 0; + pass_on_get = false; + + v8::HandleScope scope; + v8::Handle templ = v8::ObjectTemplate::New(); + templ->SetNamedPropertyHandler(ForceSetGetter, ForceSetInterceptSetter); + LocalContext context(NULL, templ); + v8::Handle global = context->Global(); + + v8::Handle some_property = v8::String::New("a"); + CHECK_EQ(0, force_set_set_count); + CHECK_EQ(0, force_set_get_count); + CHECK_EQ(3, global->Get(some_property)->Int32Value()); + // Setting the property shouldn't override it, just call the setter + // which in this case does nothing. + global->Set(some_property, v8::Int32::New(7)); + CHECK_EQ(3, global->Get(some_property)->Int32Value()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(2, force_set_get_count); + // Getting the property when the interceptor returns an empty handle + // should yield undefined, since the property isn't present on the + // object itself yet. + pass_on_get = true; + CHECK(global->Get(some_property)->IsUndefined()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(3, force_set_get_count); + // Forcing the property to be set should cause the value to be + // set locally without calling the interceptor. + global->ForceSet(some_property, v8::Int32::New(8)); + CHECK_EQ(8, global->Get(some_property)->Int32Value()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(4, force_set_get_count); + // Reenabling the interceptor should cause it to take precedence over + // the property + pass_on_get = false; + CHECK_EQ(3, global->Get(some_property)->Int32Value()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(5, force_set_get_count); + // The interceptor should also work for other properties + CHECK_EQ(3, global->Get(v8::String::New("b"))->Int32Value()); + CHECK_EQ(1, force_set_set_count); + CHECK_EQ(6, force_set_get_count); +} -- 2.7.4