Index: src/objects.cc |
diff --git a/src/objects.cc b/src/objects.cc |
index c8acb4707181a7c7f8ab1ad7e2a32d92ef4eb8e1..0df922efa05d40df799b3956daa7ead10eb0d0c2 100644 |
--- a/src/objects.cc |
+++ b/src/objects.cc |
@@ -189,7 +189,7 @@ Object* Object::GetPropertyWithCallback(Object* receiver, |
} |
UNREACHABLE(); |
- return 0; |
+ return NULL; |
} |
@@ -1613,7 +1613,7 @@ Object* JSObject::SetPropertyWithCallback(Object* structure, |
} |
UNREACHABLE(); |
- return 0; |
+ return NULL; |
} |
@@ -1657,7 +1657,8 @@ void JSObject::LookupCallbackSetterInPrototypes(String* name, |
} |
-Object* JSObject::LookupCallbackSetterInPrototypes(uint32_t index) { |
+bool JSObject::SetElementWithCallbackSetterInPrototypes(uint32_t index, |
+ Object* value) { |
for (Object* pt = GetPrototype(); |
pt != Heap::null_value(); |
pt = pt->GetPrototype()) { |
@@ -1670,12 +1671,12 @@ Object* JSObject::LookupCallbackSetterInPrototypes(uint32_t index) { |
Object* element = dictionary->ValueAt(entry); |
PropertyDetails details = dictionary->DetailsAt(entry); |
if (details.type() == CALLBACKS) { |
- // Only accessors allowed as elements. |
- return FixedArray::cast(element)->get(kSetterIndex); |
+ SetElementWithCallback(element, index, value, JSObject::cast(pt)); |
+ return true; |
} |
} |
} |
- return Heap::undefined_value(); |
+ return false; |
} |
@@ -2692,30 +2693,11 @@ Object* JSObject::DefineGetterSetter(String* name, |
// interceptor calls. |
AssertNoContextChange ncc; |
- // Check access rights if needed. |
- if (IsAccessCheckNeeded() && |
- !Top::MayNamedAccess(this, name, v8::ACCESS_SET)) { |
- Top::ReportFailedAccessCheck(this, v8::ACCESS_SET); |
- return Heap::undefined_value(); |
- } |
- |
// Try to flatten before operating on the string. |
name->TryFlatten(); |
- // Check if there is an API defined callback object which prohibits |
- // callback overwriting in this object or it's prototype chain. |
- // This mechanism is needed for instance in a browser setting, where |
- // certain accessors such as window.location should not be allowed |
- // to be overwritten because allowing overwriting could potentially |
- // cause security problems. |
- LookupResult callback_result; |
- LookupCallback(name, &callback_result); |
- if (callback_result.IsFound()) { |
- Object* obj = callback_result.GetCallbackObject(); |
- if (obj->IsAccessorInfo() && |
- AccessorInfo::cast(obj)->prohibits_overwriting()) { |
- return Heap::undefined_value(); |
- } |
+ if (!CanSetCallback(name)) { |
+ return Heap::undefined_value(); |
} |
uint32_t index; |
@@ -2746,9 +2728,10 @@ Object* JSObject::DefineGetterSetter(String* name, |
PropertyDetails details = dictionary->DetailsAt(entry); |
if (details.IsReadOnly()) return Heap::undefined_value(); |
if (details.type() == CALLBACKS) { |
- // Only accessors allowed as elements. |
- ASSERT(result->IsFixedArray()); |
- return result; |
+ if (result->IsFixedArray()) { |
+ return result; |
+ } |
+ // Otherwise allow to override it. |
} |
} |
break; |
@@ -2765,15 +2748,10 @@ Object* JSObject::DefineGetterSetter(String* name, |
if (result.IsReadOnly()) return Heap::undefined_value(); |
if (result.type() == CALLBACKS) { |
Object* obj = result.GetCallbackObject(); |
+ // Need to preserve old getters/setters. |
if (obj->IsFixedArray()) { |
- // The object might be in fast mode even though it has |
- // a getter/setter. |
- Object* ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); |
- if (ok->IsFailure()) return ok; |
- |
- PropertyDetails details = PropertyDetails(attributes, CALLBACKS); |
- SetNormalizedProperty(name, obj, details); |
- return obj; |
+ // Use set to update attributes. |
+ return SetPropertyCallback(name, obj, attributes); |
} |
} |
} |
@@ -2782,50 +2760,100 @@ Object* JSObject::DefineGetterSetter(String* name, |
// Allocate the fixed array to hold getter and setter. |
Object* structure = Heap::AllocateFixedArray(2, TENURED); |
if (structure->IsFailure()) return structure; |
- PropertyDetails details = PropertyDetails(attributes, CALLBACKS); |
if (is_element) { |
- // Normalize object to make this operation simple. |
- Object* ok = NormalizeElements(); |
- if (ok->IsFailure()) return ok; |
+ return SetElementCallback(index, structure, attributes); |
+ } else { |
+ return SetPropertyCallback(name, structure, attributes); |
+ } |
+} |
- // Update the dictionary with the new CALLBACKS property. |
- Object* dict = |
- element_dictionary()->Set(index, structure, details); |
- if (dict->IsFailure()) return dict; |
- // If name is an index we need to stay in slow case. |
- NumberDictionary* elements = NumberDictionary::cast(dict); |
- elements->set_requires_slow_elements(); |
- // Set the potential new dictionary on the object. |
- set_elements(NumberDictionary::cast(dict)); |
- } else { |
- // Normalize object to make this operation simple. |
- Object* ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); |
- if (ok->IsFailure()) return ok; |
+bool JSObject::CanSetCallback(String* name) { |
+ ASSERT(!IsAccessCheckNeeded() |
+ || Top::MayNamedAccess(this, name, v8::ACCESS_SET)); |
- // For the global object allocate a new map to invalidate the global inline |
- // caches which have a global property cell reference directly in the code. |
- if (IsGlobalObject()) { |
- Object* new_map = map()->CopyDropDescriptors(); |
- if (new_map->IsFailure()) return new_map; |
- set_map(Map::cast(new_map)); |
+ // Check if there is an API defined callback object which prohibits |
+ // callback overwriting in this object or it's prototype chain. |
+ // This mechanism is needed for instance in a browser setting, where |
+ // certain accessors such as window.location should not be allowed |
+ // to be overwritten because allowing overwriting could potentially |
+ // cause security problems. |
+ LookupResult callback_result; |
+ LookupCallback(name, &callback_result); |
+ if (callback_result.IsProperty()) { |
+ Object* obj = callback_result.GetCallbackObject(); |
+ if (obj->IsAccessorInfo() && |
+ AccessorInfo::cast(obj)->prohibits_overwriting()) { |
+ return false; |
} |
- |
- // Update the dictionary with the new CALLBACKS property. |
- return SetNormalizedProperty(name, structure, details); |
} |
+ return true; |
+} |
+ |
+ |
+Object* JSObject::SetElementCallback(uint32_t index, |
+ Object* structure, |
+ PropertyAttributes attributes) { |
+ PropertyDetails details = PropertyDetails(attributes, CALLBACKS); |
+ |
+ // Normalize elements to make this operation simple. |
+ Object* ok = NormalizeElements(); |
+ if (ok->IsFailure()) return ok; |
+ |
+ // Update the dictionary with the new CALLBACKS property. |
+ Object* dict = |
+ element_dictionary()->Set(index, structure, details); |
+ if (dict->IsFailure()) return dict; |
+ |
+ NumberDictionary* elements = NumberDictionary::cast(dict); |
+ elements->set_requires_slow_elements(); |
+ // Set the potential new dictionary on the object. |
+ set_elements(elements); |
+ |
return structure; |
} |
+Object* JSObject::SetPropertyCallback(String* name, |
+ Object* structure, |
+ PropertyAttributes attributes) { |
+ PropertyDetails details = PropertyDetails(attributes, CALLBACKS); |
+ |
+ bool convert_back_to_fast = HasFastProperties() && |
+ (map()->instance_descriptors()->number_of_descriptors() |
+ < DescriptorArray::kMaxNumberOfDescriptors); |
+ |
+ // Normalize object to make this operation simple. |
+ Object* ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); |
+ if (ok->IsFailure()) return ok; |
+ |
+ // For the global object allocate a new map to invalidate the global inline |
+ // caches which have a global property cell reference directly in the code. |
+ if (IsGlobalObject()) { |
+ Object* new_map = map()->CopyDropDescriptors(); |
+ if (new_map->IsFailure()) return new_map; |
+ set_map(Map::cast(new_map)); |
+ } |
+ |
+ // Update the dictionary with the new CALLBACKS property. |
+ Object* result = SetNormalizedProperty(name, structure, details); |
+ if (result->IsFailure()) return result; |
+ |
+ if (convert_back_to_fast) { |
+ ok = TransformToFastProperties(0); |
+ if (ok->IsFailure()) return ok; |
+ } |
+ return result; |
+} |
+ |
Object* JSObject::DefineAccessor(String* name, bool is_getter, JSFunction* fun, |
PropertyAttributes attributes) { |
// Check access rights if needed. |
if (IsAccessCheckNeeded() && |
- !Top::MayNamedAccess(this, name, v8::ACCESS_HAS)) { |
- Top::ReportFailedAccessCheck(this, v8::ACCESS_HAS); |
+ !Top::MayNamedAccess(this, name, v8::ACCESS_SET)) { |
+ Top::ReportFailedAccessCheck(this, v8::ACCESS_SET); |
return Heap::undefined_value(); |
} |
@@ -2844,6 +2872,78 @@ Object* JSObject::DefineAccessor(String* name, bool is_getter, JSFunction* fun, |
} |
+Object* JSObject::DefineAccessor(AccessorInfo* info) { |
+ String* name = String::cast(info->name()); |
+ // Check access rights if needed. |
+ if (IsAccessCheckNeeded() && |
+ !Top::MayNamedAccess(this, name, v8::ACCESS_SET)) { |
+ Top::ReportFailedAccessCheck(this, v8::ACCESS_SET); |
+ return Heap::undefined_value(); |
+ } |
+ |
+ if (IsJSGlobalProxy()) { |
+ Object* proto = GetPrototype(); |
+ if (proto->IsNull()) return this; |
+ ASSERT(proto->IsJSGlobalObject()); |
+ return JSObject::cast(proto)->DefineAccessor(info); |
+ } |
+ |
+ // Make sure that the top context does not change when doing callbacks or |
+ // interceptor calls. |
+ AssertNoContextChange ncc; |
+ |
+ // Try to flatten before operating on the string. |
+ name->TryFlatten(); |
+ |
+ if (!CanSetCallback(name)) { |
+ return Heap::undefined_value(); |
+ } |
+ |
+ uint32_t index = 0; |
+ bool is_element = name->AsArrayIndex(&index); |
+ |
+ if (is_element) { |
+ if (IsJSArray()) return Heap::undefined_value(); |
+ |
+ // Accessors overwrite previous callbacks (cf. with getters/setters). |
+ switch (GetElementsKind()) { |
+ case FAST_ELEMENTS: |
+ break; |
+ case PIXEL_ELEMENTS: |
+ case EXTERNAL_BYTE_ELEMENTS: |
+ case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: |
+ case EXTERNAL_SHORT_ELEMENTS: |
+ case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: |
+ case EXTERNAL_INT_ELEMENTS: |
+ case EXTERNAL_UNSIGNED_INT_ELEMENTS: |
+ case EXTERNAL_FLOAT_ELEMENTS: |
+ // Ignore getters and setters on pixel and external array |
+ // elements. |
+ return Heap::undefined_value(); |
+ case DICTIONARY_ELEMENTS: |
+ break; |
+ default: |
+ UNREACHABLE(); |
+ break; |
+ } |
+ |
+ SetElementCallback(index, info, info->property_attributes()); |
+ } else { |
+ // Lookup the name. |
+ LookupResult result; |
+ LocalLookup(name, &result); |
+ // ES5 forbids turning a property into an accessor if it's not |
+ // configurable (that is IsDontDelete in ES3 and v8), see 8.6.1 (Table 5). |
+ if (result.IsProperty() && (result.IsReadOnly() || result.IsDontDelete())) { |
+ return Heap::undefined_value(); |
+ } |
+ SetPropertyCallback(name, info, info->property_attributes()); |
+ } |
+ |
+ return this; |
+} |
+ |
+ |
Object* JSObject::LookupAccessor(String* name, bool is_getter) { |
// Make sure that the top context does not change when doing callbacks or |
// interceptor calls. |
@@ -2871,8 +2971,9 @@ Object* JSObject::LookupAccessor(String* name, bool is_getter) { |
Object* element = dictionary->ValueAt(entry); |
PropertyDetails details = dictionary->DetailsAt(entry); |
if (details.type() == CALLBACKS) { |
- // Only accessors allowed as elements. |
- return FixedArray::cast(element)->get(accessor_index); |
+ if (element->IsFixedArray()) { |
+ return FixedArray::cast(element)->get(accessor_index); |
+ } |
} |
} |
} |
@@ -5847,6 +5948,108 @@ Object* JSObject::SetElementWithInterceptor(uint32_t index, Object* value) { |
} |
+Object* JSObject::GetElementWithCallback(Object* receiver, |
+ Object* structure, |
+ uint32_t index, |
+ Object* holder) { |
+ ASSERT(!structure->IsProxy()); |
+ |
+ // api style callbacks. |
+ if (structure->IsAccessorInfo()) { |
+ AccessorInfo* data = AccessorInfo::cast(structure); |
+ Object* fun_obj = data->getter(); |
+ v8::AccessorGetter call_fun = v8::ToCData<v8::AccessorGetter>(fun_obj); |
+ HandleScope scope; |
+ Handle<JSObject> self(JSObject::cast(receiver)); |
+ Handle<JSObject> holder_handle(JSObject::cast(holder)); |
+ Handle<Object> number = Factory::NewNumberFromUint(index); |
+ Handle<String> key(Factory::NumberToString(number)); |
+ LOG(ApiNamedPropertyAccess("load", *self, *key)); |
+ CustomArguments args(data->data(), *self, *holder_handle); |
+ v8::AccessorInfo info(args.end()); |
+ v8::Handle<v8::Value> result; |
+ { |
+ // Leaving JavaScript. |
+ VMState state(EXTERNAL); |
+ result = call_fun(v8::Utils::ToLocal(key), info); |
+ } |
+ RETURN_IF_SCHEDULED_EXCEPTION(); |
+ if (result.IsEmpty()) return Heap::undefined_value(); |
+ return *v8::Utils::OpenHandle(*result); |
+ } |
+ |
+ // __defineGetter__ callback |
+ if (structure->IsFixedArray()) { |
+ Object* getter = FixedArray::cast(structure)->get(kGetterIndex); |
+ if (getter->IsJSFunction()) { |
+ return Object::GetPropertyWithDefinedGetter(receiver, |
+ JSFunction::cast(getter)); |
+ } |
+ // Getter is not a function. |
+ return Heap::undefined_value(); |
+ } |
+ |
+ UNREACHABLE(); |
+ return NULL; |
+} |
+ |
+ |
+Object* JSObject::SetElementWithCallback(Object* structure, |
+ uint32_t index, |
+ Object* value, |
+ JSObject* holder) { |
+ HandleScope scope; |
+ |
+ // We should never get here to initialize a const with the hole |
+ // value since a const declaration would conflict with the setter. |
+ ASSERT(!value->IsTheHole()); |
+ Handle<Object> value_handle(value); |
+ |
+ // To accommodate both the old and the new api we switch on the |
+ // data structure used to store the callbacks. Eventually proxy |
+ // callbacks should be phased out. |
+ ASSERT(!structure->IsProxy()); |
+ |
+ if (structure->IsAccessorInfo()) { |
+ // api style callbacks |
+ AccessorInfo* data = AccessorInfo::cast(structure); |
+ Object* call_obj = data->setter(); |
+ v8::AccessorSetter call_fun = v8::ToCData<v8::AccessorSetter>(call_obj); |
+ if (call_fun == NULL) return value; |
+ Handle<Object> number = Factory::NewNumberFromUint(index); |
+ Handle<String> key(Factory::NumberToString(number)); |
+ LOG(ApiNamedPropertyAccess("store", this, *key)); |
+ CustomArguments args(data->data(), this, JSObject::cast(holder)); |
+ v8::AccessorInfo info(args.end()); |
+ { |
+ // Leaving JavaScript. |
+ VMState state(EXTERNAL); |
+ call_fun(v8::Utils::ToLocal(key), |
+ v8::Utils::ToLocal(value_handle), |
+ info); |
+ } |
+ RETURN_IF_SCHEDULED_EXCEPTION(); |
+ return *value_handle; |
+ } |
+ |
+ if (structure->IsFixedArray()) { |
+ Object* setter = FixedArray::cast(structure)->get(kSetterIndex); |
+ if (setter->IsJSFunction()) { |
+ return SetPropertyWithDefinedSetter(JSFunction::cast(setter), value); |
+ } else { |
+ Handle<Object> holder_handle(holder); |
+ Handle<Object> key(Factory::NewNumberFromUint(index)); |
+ Handle<Object> args[2] = { key, holder_handle }; |
+ return Top::Throw(*Factory::NewTypeError("no_setter_in_callback", |
+ HandleVector(args, 2))); |
+ } |
+ } |
+ |
+ UNREACHABLE(); |
+ return NULL; |
+} |
+ |
+ |
// Adding n elements in fast case is O(n*n). |
// Note: revisit design to have dual undefined values to capture absent |
// elements. |
@@ -5857,9 +6060,8 @@ Object* JSObject::SetFastElement(uint32_t index, Object* value) { |
uint32_t elms_length = static_cast<uint32_t>(elms->length()); |
if (!IsJSArray() && (index >= elms_length || elms->get(index)->IsTheHole())) { |
- Object* setter = LookupCallbackSetterInPrototypes(index); |
- if (setter->IsJSFunction()) { |
- return SetPropertyWithDefinedSetter(JSFunction::cast(setter), value); |
+ if (SetElementWithCallbackSetterInPrototypes(index, value)) { |
+ return value; |
} |
} |
@@ -5977,18 +6179,7 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) { |
Object* element = dictionary->ValueAt(entry); |
PropertyDetails details = dictionary->DetailsAt(entry); |
if (details.type() == CALLBACKS) { |
- // Only accessors allowed as elements. |
- FixedArray* structure = FixedArray::cast(element); |
- if (structure->get(kSetterIndex)->IsJSFunction()) { |
- JSFunction* setter = JSFunction::cast(structure->get(kSetterIndex)); |
- return SetPropertyWithDefinedSetter(setter, value); |
- } else { |
- Handle<Object> self(this); |
- Handle<Object> key(Factory::NewNumberFromUint(index)); |
- Handle<Object> args[2] = { key, self }; |
- return Top::Throw(*Factory::NewTypeError("no_setter_in_callback", |
- HandleVector(args, 2))); |
- } |
+ return SetElementWithCallback(element, index, value, this); |
} else { |
dictionary->UpdateMaxNumberKey(index); |
dictionary->ValueAtPut(entry, value); |
@@ -5996,10 +6187,8 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) { |
} else { |
// Index not already used. Look for an accessor in the prototype chain. |
if (!IsJSArray()) { |
- Object* setter = LookupCallbackSetterInPrototypes(index); |
- if (setter->IsJSFunction()) { |
- return SetPropertyWithDefinedSetter(JSFunction::cast(setter), |
- value); |
+ if (SetElementWithCallbackSetterInPrototypes(index, value)) { |
+ return value; |
} |
} |
Object* result = dictionary->AtNumberPut(index, value); |
@@ -6102,16 +6291,10 @@ Object* JSObject::GetElementPostInterceptor(JSObject* receiver, |
Object* element = dictionary->ValueAt(entry); |
PropertyDetails details = dictionary->DetailsAt(entry); |
if (details.type() == CALLBACKS) { |
- // Only accessors allowed as elements. |
- FixedArray* structure = FixedArray::cast(element); |
- Object* getter = structure->get(kGetterIndex); |
- if (getter->IsJSFunction()) { |
- return GetPropertyWithDefinedGetter(receiver, |
- JSFunction::cast(getter)); |
- } else { |
- // Getter is not a function. |
- return Heap::undefined_value(); |
- } |
+ return GetElementWithCallback(receiver, |
+ element, |
+ index, |
+ this); |
} |
return element; |
} |
@@ -6259,16 +6442,10 @@ Object* JSObject::GetElementWithReceiver(JSObject* receiver, uint32_t index) { |
Object* element = dictionary->ValueAt(entry); |
PropertyDetails details = dictionary->DetailsAt(entry); |
if (details.type() == CALLBACKS) { |
- // Only accessors allowed as elements. |
- FixedArray* structure = FixedArray::cast(element); |
- Object* getter = structure->get(kGetterIndex); |
- if (getter->IsJSFunction()) { |
- return GetPropertyWithDefinedGetter(receiver, |
- JSFunction::cast(getter)); |
- } else { |
- // Getter is not a function. |
- return Heap::undefined_value(); |
- } |
+ return GetElementWithCallback(receiver, |
+ element, |
+ index, |
+ this); |
} |
return element; |
} |