Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1766)

Unified Diff: src/objects.cc

Issue 2123012: Allow to define accessors on objects. (Closed)
Patch Set: Last round of comments Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/objects.h ('k') | test/cctest/test-api.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
}
« no previous file with comments | « src/objects.h ('k') | test/cctest/test-api.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698