Revision: 4714
Author: [email protected]
Date: Tue May 25 05:14:49 2010
Log: Allow to define accessors on objects.
Currently one can only define accessors on object templates. This patch
allows to create accessors on the fly.
These accessors could control access to elements as well. This element
support is somewhat rudimentary and may require future work (for example,
we probably don't want to convert index into a string.)
Review URL: http://codereview.chromium.org/2123012
http://code.google.com/p/v8/source/detail?r=4714
Modified:
/branches/bleeding_edge/include/v8.h
/branches/bleeding_edge/src/api.cc
/branches/bleeding_edge/src/handles.cc
/branches/bleeding_edge/src/handles.h
/branches/bleeding_edge/src/objects.cc
/branches/bleeding_edge/src/objects.h
/branches/bleeding_edge/test/cctest/test-api.cc
=======================================
--- /branches/bleeding_edge/include/v8.h Mon May 24 23:38:19 2010
+++ /branches/bleeding_edge/include/v8.h Tue May 25 05:14:49 2010
@@ -126,6 +126,7 @@
class FunctionTemplate;
class ObjectTemplate;
class Data;
+class AccessorInfo;
class StackTrace;
class StackFrame;
@@ -1331,6 +1332,41 @@
kExternalFloatArray
};
+/**
+ * Accessor[Getter|Setter] are used as callback functions when
+ * setting|getting a particular property. See Object and ObjectTemplate's
+ * method SetAccessor.
+ */
+typedef Handle<Value> (*AccessorGetter)(Local<String> property,
+ const AccessorInfo& info);
+
+
+typedef void (*AccessorSetter)(Local<String> property,
+ Local<Value> value,
+ const AccessorInfo& info);
+
+
+/**
+ * Access control specifications.
+ *
+ * Some accessors should be accessible across contexts. These
+ * accessors have an explicit access control parameter which specifies
+ * the kind of cross-context access that should be allowed.
+ *
+ * Additionally, for security, accessors can prohibit overwriting by
+ * accessors defined in JavaScript. For objects that have such
+ * accessors either locally or in their prototype chain it is not
+ * possible to overwrite the accessor by using __defineGetter__ or
+ * __defineSetter__ from JavaScript code.
+ */
+enum AccessControl {
+ DEFAULT = 0,
+ ALL_CAN_READ = 1,
+ ALL_CAN_WRITE = 1 << 1,
+ PROHIBITS_OVERWRITING = 1 << 2
+};
+
+
/**
* A JavaScript object (ECMA-262, 4.3.3)
*/
@@ -1373,6 +1409,13 @@
bool Delete(uint32_t index);
+ bool SetAccessor(Handle<String> name,
+ AccessorGetter getter,
+ AccessorSetter setter = 0,
+ Handle<Value> data = Handle<Value>(),
+ AccessControl settings = DEFAULT,
+ PropertyAttribute attribute = None);
+
/**
* Returns an array containing the names of the enumerable properties
* of this object, including properties from prototype objects. The
@@ -1667,19 +1710,6 @@
typedef int (*LookupCallback)(Local<Object> self, Local<String> name);
-/**
- * Accessor[Getter|Setter] are used as callback functions when
- * setting|getting a particular property. See objectTemplate::SetAccessor.
- */
-typedef Handle<Value> (*AccessorGetter)(Local<String> property,
- const AccessorInfo& info);
-
-
-typedef void (*AccessorSetter)(Local<String> property,
- Local<Value> value,
- const AccessorInfo& info);
-
-
/**
* NamedProperty[Getter|Setter] are used as interceptors on object.
* See ObjectTemplate::SetNamedPropertyHandler.
@@ -1759,27 +1789,6 @@
typedef Handle<Array> (*IndexedPropertyEnumerator)(const AccessorInfo&
info);
-/**
- * Access control specifications.
- *
- * Some accessors should be accessible across contexts. These
- * accessors have an explicit access control parameter which specifies
- * the kind of cross-context access that should be allowed.
- *
- * Additionally, for security, accessors can prohibit overwriting by
- * accessors defined in JavaScript. For objects that have such
- * accessors either locally or in their prototype chain it is not
- * possible to overwrite the accessor by using __defineGetter__ or
- * __defineSetter__ from JavaScript code.
- */
-enum AccessControl {
- DEFAULT = 0,
- ALL_CAN_READ = 1,
- ALL_CAN_WRITE = 1 << 1,
- PROHIBITS_OVERWRITING = 1 << 2
-};
-
-
/**
* Access type specification.
*/
=======================================
--- /branches/bleeding_edge/src/api.cc Mon May 24 23:38:19 2010
+++ /branches/bleeding_edge/src/api.cc Tue May 25 05:14:49 2010
@@ -775,18 +775,13 @@
}
-void FunctionTemplate::AddInstancePropertyAccessor(
+static i::Handle<i::AccessorInfo> MakeAccessorInfo(
v8::Handle<String> name,
AccessorGetter getter,
AccessorSetter setter,
v8::Handle<Value> data,
v8::AccessControl settings,
v8::PropertyAttribute attributes) {
- if (IsDeadCheck("v8::FunctionTemplate::AddInstancePropertyAccessor()")) {
- return;
- }
- ENTER_V8;
- HandleScope scope;
i::Handle<i::AccessorInfo> obj = i::Factory::NewAccessorInfo();
ASSERT(getter != NULL);
obj->set_getter(*FromCData(getter));
@@ -798,7 +793,26 @@
if (settings & ALL_CAN_WRITE) obj->set_all_can_write(true);
if (settings & PROHIBITS_OVERWRITING)
obj->set_prohibits_overwriting(true);
obj->set_property_attributes(static_cast<PropertyAttributes>(attributes));
-
+ return obj;
+}
+
+
+void FunctionTemplate::AddInstancePropertyAccessor(
+ v8::Handle<String> name,
+ AccessorGetter getter,
+ AccessorSetter setter,
+ v8::Handle<Value> data,
+ v8::AccessControl settings,
+ v8::PropertyAttribute attributes) {
+ if (IsDeadCheck("v8::FunctionTemplate::AddInstancePropertyAccessor()")) {
+ return;
+ }
+ ENTER_V8;
+ HandleScope scope;
+
+ i::Handle<i::AccessorInfo> obj = MakeAccessorInfo(name,
+ getter, setter, data,
+ settings, attributes);
i::Handle<i::Object> list(Utils::OpenHandle(this)->property_accessors());
if (list->IsUndefined()) {
list = NeanderArray().value();
@@ -2362,6 +2376,23 @@
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
return self->HasElement(index);
}
+
+
+bool Object::SetAccessor(Handle<String> name,
+ AccessorGetter getter,
+ AccessorSetter setter,
+ v8::Handle<Value> data,
+ AccessControl settings,
+ PropertyAttribute attributes) {
+ ON_BAILOUT("v8::Object::SetAccessor()", return false);
+ ENTER_V8;
+ HandleScope scope;
+ i::Handle<i::AccessorInfo> info = MakeAccessorInfo(name,
+ getter, setter, data,
+ settings, attributes);
+ i::Handle<i::Object> result = i::SetAccessor(Utils::OpenHandle(this),
info);
+ return !result.is_null() && !result->IsUndefined();
+}
bool v8::Object::HasRealNamedProperty(Handle<String> key) {
=======================================
--- /branches/bleeding_edge/src/handles.cc Wed Apr 28 05:05:40 2010
+++ /branches/bleeding_edge/src/handles.cc Tue May 25 05:14:49 2010
@@ -397,6 +397,11 @@
Handle<JSObject> Copy(Handle<JSObject> obj) {
CALL_HEAP_FUNCTION(Heap::CopyJSObject(*obj), JSObject);
}
+
+
+Handle<Object> SetAccessor(Handle<JSObject> obj, Handle<AccessorInfo>
info) {
+ CALL_HEAP_FUNCTION(obj->DefineAccessor(*info), Object);
+}
// Wrappers for scripts are kept alive and cached in weak global
=======================================
--- /branches/bleeding_edge/src/handles.h Mon May 3 02:53:47 2010
+++ /branches/bleeding_edge/src/handles.h Tue May 25 05:14:49 2010
@@ -262,6 +262,8 @@
Handle<JSObject> Copy(Handle<JSObject> obj);
+Handle<Object> SetAccessor(Handle<JSObject> obj, Handle<AccessorInfo>
info);
+
Handle<FixedArray> AddKeysFromJSArray(Handle<FixedArray>,
Handle<JSArray> array);
=======================================
--- /branches/bleeding_edge/src/objects.cc Mon May 24 23:43:13 2010
+++ /branches/bleeding_edge/src/objects.cc Tue May 25 05:14:49 2010
@@ -189,7 +189,7 @@
}
UNREACHABLE();
- return 0;
+ return NULL;
}
@@ -1613,7 +1613,7 @@
}
UNREACHABLE();
- return 0;
+ return NULL;
}
@@ -1657,7 +1657,8 @@
}
-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* 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;
}
@@ -2691,31 +2692,12 @@
// Make sure that the top context does not change when doing callbacks or
// 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 @@
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 @@
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 @@
// 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;
-
- // 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;
-
- // 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.
- return SetNormalizedProperty(name, structure, details);
- }
+ return SetElementCallback(index, structure, attributes);
+ } else {
+ return SetPropertyCallback(name, structure, attributes);
+ }
+}
+
+
+bool JSObject::CanSetCallback(String* name) {
+ ASSERT(!IsAccessCheckNeeded()
+ || Top::MayNamedAccess(this, name, v8::ACCESS_SET));
+
+ // 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;
+ }
+ }
+
+ 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();
}
@@ -2842,6 +2870,78 @@
FixedArray::cast(array)->set(is_getter ? 0 : 1, fun);
return this;
}
+
+
+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) {
@@ -2871,8 +2971,9 @@
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);
+ }
}
}
}
@@ -5852,6 +5953,108 @@
RETURN_IF_SCHEDULED_EXCEPTION();
return raw_result;
}
+
+
+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).
@@ -5864,9 +6067,8 @@
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;
}
}
@@ -5984,18 +6186,7 @@
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);
@@ -6003,10 +6194,8 @@
} 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);
@@ -6109,16 +6298,10 @@
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;
}
@@ -6266,16 +6449,10 @@
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;
}
=======================================
--- /branches/bleeding_edge/src/objects.h Fri May 21 10:37:47 2010
+++ /branches/bleeding_edge/src/objects.h Tue May 25 05:14:49 2010
@@ -1248,6 +1248,8 @@
PropertyAttributes attributes);
Object* LookupAccessor(String* name, bool is_getter);
+ Object* DefineAccessor(AccessorInfo* info);
+
// Used from Object::GetProperty().
Object* GetPropertyWithFailedAccessCheck(Object* receiver,
LookupResult* result,
@@ -1370,7 +1372,7 @@
void LookupRealNamedProperty(String* name, LookupResult* result);
void LookupRealNamedPropertyInPrototypes(String* name, LookupResult*
result);
void LookupCallbackSetterInPrototypes(String* name, LookupResult*
result);
- Object* LookupCallbackSetterInPrototypes(uint32_t index);
+ bool SetElementWithCallbackSetterInPrototypes(uint32_t index, Object*
value);
void LookupCallback(String* name, LookupResult* result);
// Returns the number of properties on this object filtering out
properties
@@ -1539,6 +1541,14 @@
Object* GetElementWithInterceptor(JSObject* receiver, uint32_t index);
private:
+ Object* GetElementWithCallback(Object* receiver,
+ Object* structure,
+ uint32_t index,
+ Object* holder);
+ Object* SetElementWithCallback(Object* structure,
+ uint32_t index,
+ Object* value,
+ JSObject* holder);
Object* SetElementWithInterceptor(uint32_t index, Object* value);
Object* SetElementWithoutInterceptor(uint32_t index, Object* value);
@@ -1569,6 +1579,13 @@
// Returns true if most of the elements backing storage is used.
bool HasDenseElements();
+ bool CanSetCallback(String* name);
+ Object* SetElementCallback(uint32_t index,
+ Object* structure,
+ PropertyAttributes attributes);
+ Object* SetPropertyCallback(String* name,
+ Object* structure,
+ PropertyAttributes attributes);
Object* DefineGetterSetter(String* name, PropertyAttributes attributes);
void LookupInDescriptor(String* name, LookupResult* result);
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc Mon May 24 23:38:19 2010
+++ /branches/bleeding_edge/test/cctest/test-api.cc Tue May 25 05:14:49 2010
@@ -74,6 +74,11 @@
CHECK(result->IsBoolean());
CHECK_EQ(expected, result->BooleanValue());
}
+
+
+static void ExpectTrue(const char* code) {
+ ExpectBoolean(code, true);
+}
static void ExpectObject(const char* code, Local<Value> expected) {
@@ -2506,7 +2511,7 @@
// Uses getOwnPropertyDescriptor to check the configurable status
Local<Script> script_desc
- = Script::Compile(v8_str("var prop =Object.getOwnPropertyDescriptor( "
+ = Script::Compile(v8_str("var prop = Object.getOwnPropertyDescriptor( "
"obj, 'x');"
"prop.configurable;"));
Local<Value> result = script_desc->Run();
@@ -2592,8 +2597,167 @@
}
+static v8::Handle<v8::Object> GetGlobalProperty(LocalContext* context,
+ char const* name) {
+ return
v8::Handle<v8::Object>::Cast((*context)->Global()->Get(v8_str(name)));
+}
+THREADED_TEST(DefineAPIAccessorOnObject) {
+ v8::HandleScope scope;
+ Local<ObjectTemplate> templ = ObjectTemplate::New();
+ LocalContext context;
+
+ context->Global()->Set(v8_str("obj1"), templ->NewInstance());
+ CompileRun("var obj2 = {};");
+
+ CHECK(CompileRun("obj1.x")->IsUndefined());
+ CHECK(CompileRun("obj2.x")->IsUndefined());
+
+ CHECK(GetGlobalProperty(&context, "obj1")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+
+ ExpectString("obj1.x", "x");
+ CHECK(CompileRun("obj2.x")->IsUndefined());
+
+ CHECK(GetGlobalProperty(&context, "obj2")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+
+ ExpectString("obj1.x", "x");
+ ExpectString("obj2.x", "x");
+
+ ExpectTrue("Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
+ ExpectTrue("Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
+
+ CompileRun("Object.defineProperty(obj1, 'x',"
+ "{ get: function() { return 'y'; }, configurable: true })");
+
+ ExpectString("obj1.x", "y");
+ ExpectString("obj2.x", "x");
+
+ CompileRun("Object.defineProperty(obj2, 'x',"
+ "{ get: function() { return 'y'; }, configurable: true })");
+
+ ExpectString("obj1.x", "y");
+ ExpectString("obj2.x", "y");
+
+ ExpectTrue("Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
+ ExpectTrue("Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
+
+ CHECK(GetGlobalProperty(&context, "obj1")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+ CHECK(GetGlobalProperty(&context, "obj2")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+
+ ExpectString("obj1.x", "x");
+ ExpectString("obj2.x", "x");
+
+ ExpectTrue("Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
+ ExpectTrue("Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
+
+ // Define getters/setters, but now make them not configurable.
+ CompileRun("Object.defineProperty(obj1, 'x',"
+ "{ get: function() { return 'z'; }, configurable: false })");
+ CompileRun("Object.defineProperty(obj2, 'x',"
+ "{ get: function() { return 'z'; }, configurable: false })");
+
+ ExpectTrue("!Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
+ ExpectTrue("!Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
+
+ ExpectString("obj1.x", "z");
+ ExpectString("obj2.x", "z");
+
+ CHECK(!GetGlobalProperty(&context, "obj1")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+ CHECK(!GetGlobalProperty(&context, "obj2")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+
+ ExpectString("obj1.x", "z");
+ ExpectString("obj2.x", "z");
+}
+
+
+THREADED_TEST(DontDeleteAPIAccessorsCannotBeOverriden) {
+ v8::HandleScope scope;
+ Local<ObjectTemplate> templ = ObjectTemplate::New();
+ LocalContext context;
+
+ context->Global()->Set(v8_str("obj1"), templ->NewInstance());
+ CompileRun("var obj2 = {};");
+
+ CHECK(GetGlobalProperty(&context, "obj1")->SetAccessor(
+ v8_str("x"),
+ GetXValue, NULL,
+ v8_str("donut"), v8::DEFAULT, v8::DontDelete));
+ CHECK(GetGlobalProperty(&context, "obj2")->SetAccessor(
+ v8_str("x"),
+ GetXValue, NULL,
+ v8_str("donut"), v8::DEFAULT, v8::DontDelete));
+
+ ExpectString("obj1.x", "x");
+ ExpectString("obj2.x", "x");
+
+ ExpectTrue("!Object.getOwnPropertyDescriptor(obj1, 'x').configurable");
+ ExpectTrue("!Object.getOwnPropertyDescriptor(obj2, 'x').configurable");
+
+ CHECK(!GetGlobalProperty(&context, "obj1")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+ CHECK(!GetGlobalProperty(&context, "obj2")->
+ SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")));
+
+ {
+ v8::TryCatch try_catch;
+ CompileRun("Object.defineProperty(obj1, 'x',"
+ "{get: function() { return 'func'; }})");
+ CHECK(try_catch.HasCaught());
+ String::AsciiValue exception_value(try_catch.Exception());
+ CHECK_EQ(*exception_value,
+ "TypeError: Cannot redefine property: defineProperty");
+ }
+ {
+ v8::TryCatch try_catch;
+ CompileRun("Object.defineProperty(obj2, 'x',"
+ "{get: function() { return 'func'; }})");
+ CHECK(try_catch.HasCaught());
+ String::AsciiValue exception_value(try_catch.Exception());
+ CHECK_EQ(*exception_value,
+ "TypeError: Cannot redefine property: defineProperty");
+ }
+}
+
+
+static v8::Handle<Value> Get239Value(Local<String> name,
+ const AccessorInfo& info) {
+ ApiTestFuzzer::Fuzz();
+ CHECK_EQ(info.Data(), v8_str("donut"));
+ CHECK_EQ(name, v8_str("239"));
+ return name;
+}
+
+
+THREADED_TEST(ElementAPIAccessor) {
+ v8::HandleScope scope;
+ Local<ObjectTemplate> templ = ObjectTemplate::New();
+ LocalContext context;
+
+ context->Global()->Set(v8_str("obj1"), templ->NewInstance());
+ CompileRun("var obj2 = {};");
+
+ CHECK(GetGlobalProperty(&context, "obj1")->SetAccessor(
+ v8_str("239"),
+ Get239Value, NULL,
+ v8_str("donut")));
+ CHECK(GetGlobalProperty(&context, "obj2")->SetAccessor(
+ v8_str("239"),
+ Get239Value, NULL,
+ v8_str("donut")));
+
+ ExpectString("obj1[239]", "239");
+ ExpectString("obj2[239]", "239");
+ ExpectString("obj1['239']", "239");
+ ExpectString("obj2['239']", "239");
+}
+
v8::Persistent<Value> xValue;
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev