}
UNREACHABLE();
- return 0;
+ return NULL;
}
}
UNREACHABLE();
- return 0;
+ return NULL;
}
}
-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()) {
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;
}
// 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;
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;
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);
}
}
}
// 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();
}
}
+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.
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);
+ }
}
}
}
}
+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.
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;
}
}
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);
} 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);
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;
}
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;
}
}
+static void ExpectTrue(const char* code) {
+ ExpectBoolean(code, true);
+}
+
+
static void ExpectObject(const char* code, Local<Value> expected) {
Local<Value> result = CompileRun(code);
CHECK(result->Equals(expected));
// 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();
}
+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;