Commit 897f7ded authored by antonm@chromium.org's avatar antonm@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4714 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 7bbfc8fc
...@@ -126,6 +126,7 @@ template <class T> class Persistent; ...@@ -126,6 +126,7 @@ template <class T> class Persistent;
class FunctionTemplate; class FunctionTemplate;
class ObjectTemplate; class ObjectTemplate;
class Data; class Data;
class AccessorInfo;
class StackTrace; class StackTrace;
class StackFrame; class StackFrame;
...@@ -1331,6 +1332,41 @@ enum ExternalArrayType { ...@@ -1331,6 +1332,41 @@ enum ExternalArrayType {
kExternalFloatArray 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) * A JavaScript object (ECMA-262, 4.3.3)
*/ */
...@@ -1373,6 +1409,13 @@ class V8EXPORT Object : public Value { ...@@ -1373,6 +1409,13 @@ class V8EXPORT Object : public Value {
bool Delete(uint32_t index); 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 * Returns an array containing the names of the enumerable properties
* of this object, including properties from prototype objects. The * of this object, including properties from prototype objects. The
...@@ -1667,19 +1710,6 @@ typedef Handle<Value> (*InvocationCallback)(const Arguments& args); ...@@ -1667,19 +1710,6 @@ typedef Handle<Value> (*InvocationCallback)(const Arguments& args);
typedef int (*LookupCallback)(Local<Object> self, Local<String> name); 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. * NamedProperty[Getter|Setter] are used as interceptors on object.
* See ObjectTemplate::SetNamedPropertyHandler. * See ObjectTemplate::SetNamedPropertyHandler.
...@@ -1759,27 +1789,6 @@ typedef Handle<Boolean> (*IndexedPropertyDeleter)(uint32_t index, ...@@ -1759,27 +1789,6 @@ typedef Handle<Boolean> (*IndexedPropertyDeleter)(uint32_t index,
typedef Handle<Array> (*IndexedPropertyEnumerator)(const AccessorInfo& info); 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. * Access type specification.
*/ */
......
...@@ -775,18 +775,13 @@ void FunctionTemplate::SetCallHandler(InvocationCallback callback, ...@@ -775,18 +775,13 @@ void FunctionTemplate::SetCallHandler(InvocationCallback callback,
} }
void FunctionTemplate::AddInstancePropertyAccessor( static i::Handle<i::AccessorInfo> MakeAccessorInfo(
v8::Handle<String> name, v8::Handle<String> name,
AccessorGetter getter, AccessorGetter getter,
AccessorSetter setter, AccessorSetter setter,
v8::Handle<Value> data, v8::Handle<Value> data,
v8::AccessControl settings, v8::AccessControl settings,
v8::PropertyAttribute attributes) { v8::PropertyAttribute attributes) {
if (IsDeadCheck("v8::FunctionTemplate::AddInstancePropertyAccessor()")) {
return;
}
ENTER_V8;
HandleScope scope;
i::Handle<i::AccessorInfo> obj = i::Factory::NewAccessorInfo(); i::Handle<i::AccessorInfo> obj = i::Factory::NewAccessorInfo();
ASSERT(getter != NULL); ASSERT(getter != NULL);
obj->set_getter(*FromCData(getter)); obj->set_getter(*FromCData(getter));
...@@ -798,7 +793,26 @@ void FunctionTemplate::AddInstancePropertyAccessor( ...@@ -798,7 +793,26 @@ void FunctionTemplate::AddInstancePropertyAccessor(
if (settings & ALL_CAN_WRITE) obj->set_all_can_write(true); if (settings & ALL_CAN_WRITE) obj->set_all_can_write(true);
if (settings & PROHIBITS_OVERWRITING) obj->set_prohibits_overwriting(true); if (settings & PROHIBITS_OVERWRITING) obj->set_prohibits_overwriting(true);
obj->set_property_attributes(static_cast<PropertyAttributes>(attributes)); 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()); i::Handle<i::Object> list(Utils::OpenHandle(this)->property_accessors());
if (list->IsUndefined()) { if (list->IsUndefined()) {
list = NeanderArray().value(); list = NeanderArray().value();
...@@ -2364,6 +2378,23 @@ bool v8::Object::Has(uint32_t index) { ...@@ -2364,6 +2378,23 @@ bool v8::Object::Has(uint32_t 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) { bool v8::Object::HasRealNamedProperty(Handle<String> key) {
ON_BAILOUT("v8::Object::HasRealNamedProperty()", return false); ON_BAILOUT("v8::Object::HasRealNamedProperty()", return false);
return Utils::OpenHandle(this)->HasRealNamedProperty( return Utils::OpenHandle(this)->HasRealNamedProperty(
......
...@@ -399,6 +399,11 @@ Handle<JSObject> Copy(Handle<JSObject> obj) { ...@@ -399,6 +399,11 @@ Handle<JSObject> Copy(Handle<JSObject> obj) {
} }
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 // Wrappers for scripts are kept alive and cached in weak global
// handles referred from proxy objects held by the scripts as long as // handles referred from proxy objects held by the scripts as long as
// they are used. When they are not used anymore, the garbage // they are used. When they are not used anymore, the garbage
......
...@@ -262,6 +262,8 @@ Handle<Object> LookupSingleCharacterStringFromCode(uint32_t index); ...@@ -262,6 +262,8 @@ Handle<Object> LookupSingleCharacterStringFromCode(uint32_t index);
Handle<JSObject> Copy(Handle<JSObject> obj); Handle<JSObject> Copy(Handle<JSObject> obj);
Handle<Object> SetAccessor(Handle<JSObject> obj, Handle<AccessorInfo> info);
Handle<FixedArray> AddKeysFromJSArray(Handle<FixedArray>, Handle<FixedArray> AddKeysFromJSArray(Handle<FixedArray>,
Handle<JSArray> array); Handle<JSArray> array);
......
...@@ -189,7 +189,7 @@ Object* Object::GetPropertyWithCallback(Object* receiver, ...@@ -189,7 +189,7 @@ Object* Object::GetPropertyWithCallback(Object* receiver,
} }
UNREACHABLE(); UNREACHABLE();
return 0; return NULL;
} }
...@@ -1613,7 +1613,7 @@ Object* JSObject::SetPropertyWithCallback(Object* structure, ...@@ -1613,7 +1613,7 @@ Object* JSObject::SetPropertyWithCallback(Object* structure,
} }
UNREACHABLE(); UNREACHABLE();
return 0; return NULL;
} }
...@@ -1657,7 +1657,8 @@ void JSObject::LookupCallbackSetterInPrototypes(String* name, ...@@ -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(); for (Object* pt = GetPrototype();
pt != Heap::null_value(); pt != Heap::null_value();
pt = pt->GetPrototype()) { pt = pt->GetPrototype()) {
...@@ -1670,12 +1671,12 @@ Object* JSObject::LookupCallbackSetterInPrototypes(uint32_t index) { ...@@ -1670,12 +1671,12 @@ Object* JSObject::LookupCallbackSetterInPrototypes(uint32_t index) {
Object* element = dictionary->ValueAt(entry); Object* element = dictionary->ValueAt(entry);
PropertyDetails details = dictionary->DetailsAt(entry); PropertyDetails details = dictionary->DetailsAt(entry);
if (details.type() == CALLBACKS) { if (details.type() == CALLBACKS) {
// Only accessors allowed as elements. SetElementWithCallback(element, index, value, JSObject::cast(pt));
return FixedArray::cast(element)->get(kSetterIndex); return true;
} }
} }
} }
return Heap::undefined_value(); return false;
} }
...@@ -2692,30 +2693,11 @@ Object* JSObject::DefineGetterSetter(String* name, ...@@ -2692,30 +2693,11 @@ Object* JSObject::DefineGetterSetter(String* name,
// interceptor calls. // interceptor calls.
AssertNoContextChange ncc; 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. // Try to flatten before operating on the string.
name->TryFlatten(); name->TryFlatten();
// Check if there is an API defined callback object which prohibits if (!CanSetCallback(name)) {
// callback overwriting in this object or it's prototype chain. return Heap::undefined_value();
// 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();
}
} }
uint32_t index; uint32_t index;
...@@ -2746,9 +2728,10 @@ Object* JSObject::DefineGetterSetter(String* name, ...@@ -2746,9 +2728,10 @@ Object* JSObject::DefineGetterSetter(String* name,
PropertyDetails details = dictionary->DetailsAt(entry); PropertyDetails details = dictionary->DetailsAt(entry);
if (details.IsReadOnly()) return Heap::undefined_value(); if (details.IsReadOnly()) return Heap::undefined_value();
if (details.type() == CALLBACKS) { if (details.type() == CALLBACKS) {
// Only accessors allowed as elements. if (result->IsFixedArray()) {
ASSERT(result->IsFixedArray()); return result;
return result; }
// Otherwise allow to override it.
} }
} }
break; break;
...@@ -2765,15 +2748,10 @@ Object* JSObject::DefineGetterSetter(String* name, ...@@ -2765,15 +2748,10 @@ Object* JSObject::DefineGetterSetter(String* name,
if (result.IsReadOnly()) return Heap::undefined_value(); if (result.IsReadOnly()) return Heap::undefined_value();
if (result.type() == CALLBACKS) { if (result.type() == CALLBACKS) {
Object* obj = result.GetCallbackObject(); Object* obj = result.GetCallbackObject();
// Need to preserve old getters/setters.
if (obj->IsFixedArray()) { if (obj->IsFixedArray()) {
// The object might be in fast mode even though it has // Use set to update attributes.
// a getter/setter. return SetPropertyCallback(name, obj, attributes);
Object* ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
if (ok->IsFailure()) return ok;
PropertyDetails details = PropertyDetails(attributes, CALLBACKS);
SetNormalizedProperty(name, obj, details);
return obj;
} }
} }
} }
...@@ -2782,50 +2760,100 @@ Object* JSObject::DefineGetterSetter(String* name, ...@@ -2782,50 +2760,100 @@ Object* JSObject::DefineGetterSetter(String* name,
// Allocate the fixed array to hold getter and setter. // Allocate the fixed array to hold getter and setter.
Object* structure = Heap::AllocateFixedArray(2, TENURED); Object* structure = Heap::AllocateFixedArray(2, TENURED);
if (structure->IsFailure()) return structure; if (structure->IsFailure()) return structure;
PropertyDetails details = PropertyDetails(attributes, CALLBACKS);
if (is_element) { if (is_element) {
// Normalize object to make this operation simple. return SetElementCallback(index, structure, attributes);
Object* ok = NormalizeElements(); } else {
if (ok->IsFailure()) return ok; 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. bool JSObject::CanSetCallback(String* name) {
NumberDictionary* elements = NumberDictionary::cast(dict); ASSERT(!IsAccessCheckNeeded()
elements->set_requires_slow_elements(); || Top::MayNamedAccess(this, name, v8::ACCESS_SET));
// 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 // Check if there is an API defined callback object which prohibits
// caches which have a global property cell reference directly in the code. // callback overwriting in this object or it's prototype chain.
if (IsGlobalObject()) { // This mechanism is needed for instance in a browser setting, where
Object* new_map = map()->CopyDropDescriptors(); // certain accessors such as window.location should not be allowed
if (new_map->IsFailure()) return new_map; // to be overwritten because allowing overwriting could potentially
set_map(Map::cast(new_map)); // 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; 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, Object* JSObject::DefineAccessor(String* name, bool is_getter, JSFunction* fun,
PropertyAttributes attributes) { PropertyAttributes attributes) {
// Check access rights if needed. // Check access rights if needed.
if (IsAccessCheckNeeded() && if (IsAccessCheckNeeded() &&
!Top::MayNamedAccess(this, name, v8::ACCESS_HAS)) { !Top::MayNamedAccess(this, name, v8::ACCESS_SET)) {
Top::ReportFailedAccessCheck(this, v8::ACCESS_HAS); Top::ReportFailedAccessCheck(this, v8::ACCESS_SET);
return Heap::undefined_value(); return Heap::undefined_value();
} }
...@@ -2844,6 +2872,78 @@ Object* JSObject::DefineAccessor(String* name, bool is_getter, JSFunction* fun, ...@@ -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) { Object* JSObject::LookupAccessor(String* name, bool is_getter) {
// Make sure that the top context does not change when doing callbacks or // Make sure that the top context does not change when doing callbacks or
// interceptor calls. // interceptor calls.
...@@ -2871,8 +2971,9 @@ Object* JSObject::LookupAccessor(String* name, bool is_getter) { ...@@ -2871,8 +2971,9 @@ Object* JSObject::LookupAccessor(String* name, bool is_getter) {
Object* element = dictionary->ValueAt(entry); Object* element = dictionary->ValueAt(entry);
PropertyDetails details = dictionary->DetailsAt(entry); PropertyDetails details = dictionary->DetailsAt(entry);
if (details.type() == CALLBACKS) { if (details.type() == CALLBACKS) {
// Only accessors allowed as elements. if (element->IsFixedArray()) {
return FixedArray::cast(element)->get(accessor_index); return FixedArray::cast(element)->get(accessor_index);
}
} }
} }
} }
...@@ -5854,6 +5955,108 @@ Object* JSObject::SetElementWithInterceptor(uint32_t index, Object* value) { ...@@ -5854,6 +5955,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). // Adding n elements in fast case is O(n*n).
// Note: revisit design to have dual undefined values to capture absent // Note: revisit design to have dual undefined values to capture absent
// elements. // elements.
...@@ -5864,9 +6067,8 @@ Object* JSObject::SetFastElement(uint32_t index, Object* value) { ...@@ -5864,9 +6067,8 @@ Object* JSObject::SetFastElement(uint32_t index, Object* value) {
uint32_t elms_length = static_cast<uint32_t>(elms->length()); uint32_t elms_length = static_cast<uint32_t>(elms->length());
if (!IsJSArray() && (index >= elms_length || elms->get(index)->IsTheHole())) { if (!IsJSArray() && (index >= elms_length || elms->get(index)->IsTheHole())) {
Object* setter = LookupCallbackSetterInPrototypes(index); if (SetElementWithCallbackSetterInPrototypes(index, value)) {
if (setter->IsJSFunction()) { return value;
return SetPropertyWithDefinedSetter(JSFunction::cast(setter), value);
} }
} }
...@@ -5984,18 +6186,7 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) { ...@@ -5984,18 +6186,7 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) {
Object* element = dictionary->ValueAt(entry); Object* element = dictionary->ValueAt(entry);
PropertyDetails details = dictionary->DetailsAt(entry); PropertyDetails details = dictionary->DetailsAt(entry);
if (details.type() == CALLBACKS) { if (details.type() == CALLBACKS) {
// Only accessors allowed as elements. return SetElementWithCallback(element, index, value, this);
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)));
}
} else { } else {
dictionary->UpdateMaxNumberKey(index); dictionary->UpdateMaxNumberKey(index);
dictionary->ValueAtPut(entry, value); dictionary->ValueAtPut(entry, value);
...@@ -6003,10 +6194,8 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) { ...@@ -6003,10 +6194,8 @@ Object* JSObject::SetElementWithoutInterceptor(uint32_t index, Object* value) {
} else { } else {
// Index not already used. Look for an accessor in the prototype chain. // Index not already used. Look for an accessor in the prototype chain.
if (!IsJSArray()) { if (!IsJSArray()) {
Object* setter = LookupCallbackSetterInPrototypes(index); if (SetElementWithCallbackSetterInPrototypes(index, value)) {
if (setter->IsJSFunction()) { return value;
return SetPropertyWithDefinedSetter(JSFunction::cast(setter),
value);
} }
} }
Object* result = dictionary->AtNumberPut(index, value); Object* result = dictionary->AtNumberPut(index, value);
...@@ -6109,16 +6298,10 @@ Object* JSObject::GetElementPostInterceptor(JSObject* receiver, ...@@ -6109,16 +6298,10 @@ Object* JSObject::GetElementPostInterceptor(JSObject* receiver,
Object* element = dictionary->ValueAt(entry); Object* element = dictionary->ValueAt(entry);
PropertyDetails details = dictionary->DetailsAt(entry); PropertyDetails details = dictionary->DetailsAt(entry);
if (details.type() == CALLBACKS) { if (details.type() == CALLBACKS) {
// Only accessors allowed as elements. return GetElementWithCallback(receiver,
FixedArray* structure = FixedArray::cast(element); element,
Object* getter = structure->get(kGetterIndex); index,
if (getter->IsJSFunction()) { this);
return GetPropertyWithDefinedGetter(receiver,
JSFunction::cast(getter));
} else {
// Getter is not a function.
return Heap::undefined_value();
}
} }
return element; return element;
} }
...@@ -6266,16 +6449,10 @@ Object* JSObject::GetElementWithReceiver(JSObject* receiver, uint32_t index) { ...@@ -6266,16 +6449,10 @@ Object* JSObject::GetElementWithReceiver(JSObject* receiver, uint32_t index) {
Object* element = dictionary->ValueAt(entry); Object* element = dictionary->ValueAt(entry);
PropertyDetails details = dictionary->DetailsAt(entry); PropertyDetails details = dictionary->DetailsAt(entry);
if (details.type() == CALLBACKS) { if (details.type() == CALLBACKS) {
// Only accessors allowed as elements. return GetElementWithCallback(receiver,
FixedArray* structure = FixedArray::cast(element); element,
Object* getter = structure->get(kGetterIndex); index,
if (getter->IsJSFunction()) { this);
return GetPropertyWithDefinedGetter(receiver,
JSFunction::cast(getter));
} else {
// Getter is not a function.
return Heap::undefined_value();
}
} }
return element; return element;
} }
......
...@@ -1248,6 +1248,8 @@ class JSObject: public HeapObject { ...@@ -1248,6 +1248,8 @@ class JSObject: public HeapObject {
PropertyAttributes attributes); PropertyAttributes attributes);
Object* LookupAccessor(String* name, bool is_getter); Object* LookupAccessor(String* name, bool is_getter);
Object* DefineAccessor(AccessorInfo* info);
// Used from Object::GetProperty(). // Used from Object::GetProperty().
Object* GetPropertyWithFailedAccessCheck(Object* receiver, Object* GetPropertyWithFailedAccessCheck(Object* receiver,
LookupResult* result, LookupResult* result,
...@@ -1370,7 +1372,7 @@ class JSObject: public HeapObject { ...@@ -1370,7 +1372,7 @@ class JSObject: public HeapObject {
void LookupRealNamedProperty(String* name, LookupResult* result); void LookupRealNamedProperty(String* name, LookupResult* result);
void LookupRealNamedPropertyInPrototypes(String* name, LookupResult* result); void LookupRealNamedPropertyInPrototypes(String* name, LookupResult* result);
void LookupCallbackSetterInPrototypes(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); void LookupCallback(String* name, LookupResult* result);
// Returns the number of properties on this object filtering out properties // Returns the number of properties on this object filtering out properties
...@@ -1539,6 +1541,14 @@ class JSObject: public HeapObject { ...@@ -1539,6 +1541,14 @@ class JSObject: public HeapObject {
Object* GetElementWithInterceptor(JSObject* receiver, uint32_t index); Object* GetElementWithInterceptor(JSObject* receiver, uint32_t index);
private: 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* SetElementWithInterceptor(uint32_t index, Object* value);
Object* SetElementWithoutInterceptor(uint32_t index, Object* value); Object* SetElementWithoutInterceptor(uint32_t index, Object* value);
...@@ -1569,6 +1579,13 @@ class JSObject: public HeapObject { ...@@ -1569,6 +1579,13 @@ class JSObject: public HeapObject {
// Returns true if most of the elements backing storage is used. // Returns true if most of the elements backing storage is used.
bool HasDenseElements(); 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); Object* DefineGetterSetter(String* name, PropertyAttributes attributes);
void LookupInDescriptor(String* name, LookupResult* result); void LookupInDescriptor(String* name, LookupResult* result);
......
...@@ -76,6 +76,11 @@ static void ExpectBoolean(const char* code, bool expected) { ...@@ -76,6 +76,11 @@ static void ExpectBoolean(const char* code, bool expected) {
} }
static void ExpectTrue(const char* code) {
ExpectBoolean(code, true);
}
static void ExpectObject(const char* code, Local<Value> expected) { static void ExpectObject(const char* code, Local<Value> expected) {
Local<Value> result = CompileRun(code); Local<Value> result = CompileRun(code);
CHECK(result->Equals(expected)); CHECK(result->Equals(expected));
...@@ -2506,7 +2511,7 @@ THREADED_TEST(DefinePropertyOnAPIAccessor) { ...@@ -2506,7 +2511,7 @@ THREADED_TEST(DefinePropertyOnAPIAccessor) {
// Uses getOwnPropertyDescriptor to check the configurable status // Uses getOwnPropertyDescriptor to check the configurable status
Local<Script> script_desc Local<Script> script_desc
= Script::Compile(v8_str("var prop =Object.getOwnPropertyDescriptor( " = Script::Compile(v8_str("var prop = Object.getOwnPropertyDescriptor( "
"obj, 'x');" "obj, 'x');"
"prop.configurable;")); "prop.configurable;"));
Local<Value> result = script_desc->Run(); Local<Value> result = script_desc->Run();
...@@ -2592,7 +2597,166 @@ THREADED_TEST(DefinePropertyOnDefineGetterSetter) { ...@@ -2592,7 +2597,166 @@ THREADED_TEST(DefinePropertyOnDefineGetterSetter) {
} }
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::Persistent<Value> xValue;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment