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);
......
This diff is collapsed.
...@@ -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