Refactor prototype setting code and expose SetPrototype to public V8 API.
Review URL: http://codereview.chromium.org/598020 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@3829 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
b04a26dc31
commit
5ecfd4bfe9
@ -1196,6 +1196,13 @@ class V8EXPORT Object : public Value {
|
||||
*/
|
||||
Local<Value> GetPrototype();
|
||||
|
||||
/**
|
||||
* Set the prototype object. This does not skip objects marked to
|
||||
* be skipped by __proto__ and it does not consult the security
|
||||
* handler.
|
||||
*/
|
||||
bool SetPrototype(Handle<Value> prototype);
|
||||
|
||||
/**
|
||||
* Finds an instance of the given function template in the prototype
|
||||
* chain.
|
||||
|
@ -647,42 +647,9 @@ Object* Accessors::ObjectGetPrototype(Object* receiver, void*) {
|
||||
Object* Accessors::ObjectSetPrototype(JSObject* receiver,
|
||||
Object* value,
|
||||
void*) {
|
||||
// Before we can set the prototype we need to be sure
|
||||
// prototype cycles are prevented.
|
||||
// It is sufficient to validate that the receiver is not in the new prototype
|
||||
// chain.
|
||||
|
||||
// Silently ignore the change if value is not a JSObject or null.
|
||||
// SpiderMonkey behaves this way.
|
||||
if (!value->IsJSObject() && !value->IsNull()) return value;
|
||||
|
||||
for (Object* pt = value; pt != Heap::null_value(); pt = pt->GetPrototype()) {
|
||||
if (JSObject::cast(pt) == receiver) {
|
||||
// Cycle detected.
|
||||
HandleScope scope;
|
||||
return Top::Throw(*Factory::NewError("cyclic_proto",
|
||||
HandleVector<Object>(NULL, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first object in the chain whose prototype object is not
|
||||
// hidden and set the new prototype on that object.
|
||||
JSObject* current = receiver;
|
||||
Object* current_proto = receiver->GetPrototype();
|
||||
while (current_proto->IsJSObject() &&
|
||||
JSObject::cast(current_proto)->map()->is_hidden_prototype()) {
|
||||
current = JSObject::cast(current_proto);
|
||||
current_proto = current_proto->GetPrototype();
|
||||
}
|
||||
|
||||
// Set the new prototype of the object.
|
||||
Object* new_map = current->map()->CopyDropTransitions();
|
||||
if (new_map->IsFailure()) return new_map;
|
||||
Map::cast(new_map)->set_prototype(value);
|
||||
current->set_map(Map::cast(new_map));
|
||||
|
||||
const bool skip_hidden_prototypes = true;
|
||||
// To be consistent with other Set functions, return the value.
|
||||
return value;
|
||||
return receiver->SetPrototype(value, skip_hidden_prototypes);
|
||||
}
|
||||
|
||||
|
||||
|
13
src/api.cc
13
src/api.cc
@ -2032,6 +2032,19 @@ Local<Value> v8::Object::GetPrototype() {
|
||||
}
|
||||
|
||||
|
||||
bool v8::Object::SetPrototype(Handle<Value> value) {
|
||||
ON_BAILOUT("v8::Object::SetPrototype()", return false);
|
||||
ENTER_V8;
|
||||
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
|
||||
i::Handle<i::Object> value_obj = Utils::OpenHandle(*value);
|
||||
EXCEPTION_PREAMBLE();
|
||||
i::Handle<i::Object> result = i::SetPrototype(self, value_obj);
|
||||
has_pending_exception = result.is_null();
|
||||
EXCEPTION_BAILOUT_CHECK(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Local<Object> v8::Object::FindInstanceInPrototypeChain(
|
||||
v8::Handle<FunctionTemplate> tmpl) {
|
||||
ON_BAILOUT("v8::Object::FindInstanceInPrototypeChain()",
|
||||
|
@ -300,6 +300,12 @@ Handle<Object> GetPrototype(Handle<Object> obj) {
|
||||
}
|
||||
|
||||
|
||||
Handle<Object> SetPrototype(Handle<JSObject> obj, Handle<Object> value) {
|
||||
const bool skip_hidden_prototypes = false;
|
||||
CALL_HEAP_FUNCTION(obj->SetPrototype(*value, skip_hidden_prototypes), Object);
|
||||
}
|
||||
|
||||
|
||||
Handle<Object> GetHiddenProperties(Handle<JSObject> obj,
|
||||
bool create_if_needed) {
|
||||
Object* holder = obj->BypassGlobalProxy();
|
||||
|
@ -240,6 +240,8 @@ Handle<Object> GetPropertyWithInterceptor(Handle<JSObject> receiver,
|
||||
|
||||
Handle<Object> GetPrototype(Handle<Object> obj);
|
||||
|
||||
Handle<Object> SetPrototype(Handle<JSObject> obj, Handle<Object> value);
|
||||
|
||||
// Return the object's hidden properties object. If the object has no hidden
|
||||
// properties and create_if_needed is true, then a new hidden property object
|
||||
// will be allocated. Otherwise the Heap::undefined_value is returned.
|
||||
|
@ -5353,6 +5353,48 @@ Object* JSObject::SetElementsLength(Object* len) {
|
||||
}
|
||||
|
||||
|
||||
Object* JSObject::SetPrototype(Object* value,
|
||||
bool skip_hidden_prototypes) {
|
||||
// Silently ignore the change if value is not a JSObject or null.
|
||||
// SpiderMonkey behaves this way.
|
||||
if (!value->IsJSObject() && !value->IsNull()) return value;
|
||||
|
||||
// Before we can set the prototype we need to be sure
|
||||
// prototype cycles are prevented.
|
||||
// It is sufficient to validate that the receiver is not in the new prototype
|
||||
// chain.
|
||||
for (Object* pt = value; pt != Heap::null_value(); pt = pt->GetPrototype()) {
|
||||
if (JSObject::cast(pt) == this) {
|
||||
// Cycle detected.
|
||||
HandleScope scope;
|
||||
return Top::Throw(*Factory::NewError("cyclic_proto",
|
||||
HandleVector<Object>(NULL, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
JSObject* real_receiver = this;
|
||||
|
||||
if (skip_hidden_prototypes) {
|
||||
// Find the first object in the chain whose prototype object is not
|
||||
// hidden and set the new prototype on that object.
|
||||
Object* current_proto = real_receiver->GetPrototype();
|
||||
while (current_proto->IsJSObject() &&
|
||||
JSObject::cast(current_proto)->map()->is_hidden_prototype()) {
|
||||
real_receiver = JSObject::cast(current_proto);
|
||||
current_proto = current_proto->GetPrototype();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the new prototype of the object.
|
||||
Object* new_map = real_receiver->map()->CopyDropTransitions();
|
||||
if (new_map->IsFailure()) return new_map;
|
||||
Map::cast(new_map)->set_prototype(value);
|
||||
real_receiver->set_map(Map::cast(new_map));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
bool JSObject::HasElementPostInterceptor(JSObject* receiver, uint32_t index) {
|
||||
switch (GetElementsKind()) {
|
||||
case FAST_ELEMENTS: {
|
||||
|
@ -1317,6 +1317,9 @@ class JSObject: public HeapObject {
|
||||
// Return the object's prototype (might be Heap::null_value()).
|
||||
inline Object* GetPrototype();
|
||||
|
||||
// Set the object's prototype (only JSObject and null are allowed).
|
||||
Object* SetPrototype(Object* value, bool skip_hidden_prototypes);
|
||||
|
||||
// Tells whether the index'th element is present.
|
||||
inline bool HasElement(uint32_t index);
|
||||
bool HasElementWithReceiver(JSObject* receiver, uint32_t index);
|
||||
|
@ -4843,6 +4843,84 @@ THREADED_TEST(HiddenPrototype) {
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(SetPrototype) {
|
||||
v8::HandleScope handle_scope;
|
||||
LocalContext context;
|
||||
|
||||
Local<v8::FunctionTemplate> t0 = v8::FunctionTemplate::New();
|
||||
t0->InstanceTemplate()->Set(v8_str("x"), v8_num(0));
|
||||
Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New();
|
||||
t1->SetHiddenPrototype(true);
|
||||
t1->InstanceTemplate()->Set(v8_str("y"), v8_num(1));
|
||||
Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New();
|
||||
t2->SetHiddenPrototype(true);
|
||||
t2->InstanceTemplate()->Set(v8_str("z"), v8_num(2));
|
||||
Local<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New();
|
||||
t3->InstanceTemplate()->Set(v8_str("u"), v8_num(3));
|
||||
|
||||
Local<v8::Object> o0 = t0->GetFunction()->NewInstance();
|
||||
Local<v8::Object> o1 = t1->GetFunction()->NewInstance();
|
||||
Local<v8::Object> o2 = t2->GetFunction()->NewInstance();
|
||||
Local<v8::Object> o3 = t3->GetFunction()->NewInstance();
|
||||
|
||||
// Setting the prototype on an object does not skip hidden prototypes.
|
||||
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
|
||||
CHECK(o0->SetPrototype(o1));
|
||||
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
|
||||
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
|
||||
CHECK(o1->SetPrototype(o2));
|
||||
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
|
||||
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
|
||||
CHECK_EQ(2, o0->Get(v8_str("z"))->Int32Value());
|
||||
CHECK(o2->SetPrototype(o3));
|
||||
CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value());
|
||||
CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value());
|
||||
CHECK_EQ(2, o0->Get(v8_str("z"))->Int32Value());
|
||||
CHECK_EQ(3, o0->Get(v8_str("u"))->Int32Value());
|
||||
|
||||
// Getting the prototype of o0 should get the first visible one
|
||||
// which is o3. Therefore, z should not be defined on the prototype
|
||||
// object.
|
||||
Local<Value> proto = o0->Get(v8_str("__proto__"));
|
||||
CHECK(proto->IsObject());
|
||||
CHECK_EQ(v8::Handle<v8::Object>::Cast(proto), o3);
|
||||
|
||||
// However, Object::GetPrototype ignores hidden prototype.
|
||||
Local<Value> proto0 = o0->GetPrototype();
|
||||
CHECK(proto0->IsObject());
|
||||
CHECK_EQ(v8::Handle<v8::Object>::Cast(proto0), o1);
|
||||
|
||||
Local<Value> proto1 = o1->GetPrototype();
|
||||
CHECK(proto1->IsObject());
|
||||
CHECK_EQ(v8::Handle<v8::Object>::Cast(proto1), o2);
|
||||
|
||||
Local<Value> proto2 = o2->GetPrototype();
|
||||
CHECK(proto2->IsObject());
|
||||
CHECK_EQ(v8::Handle<v8::Object>::Cast(proto2), o3);
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(SetPrototypeThrows) {
|
||||
v8::HandleScope handle_scope;
|
||||
LocalContext context;
|
||||
|
||||
Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
|
||||
|
||||
Local<v8::Object> o0 = t->GetFunction()->NewInstance();
|
||||
Local<v8::Object> o1 = t->GetFunction()->NewInstance();
|
||||
|
||||
CHECK(o0->SetPrototype(o1));
|
||||
// If setting the prototype leads to the cycle, SetPrototype should
|
||||
// return false and keep VM in sane state.
|
||||
v8::TryCatch try_catch;
|
||||
CHECK(!o1->SetPrototype(o0));
|
||||
CHECK(!try_catch.HasCaught());
|
||||
ASSERT(!i::Top::has_pending_exception());
|
||||
|
||||
CHECK_EQ(42, CompileRun("function f() { return 42; }; f()")->Int32Value());
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(GetterSetterExceptions) {
|
||||
v8::HandleScope handle_scope;
|
||||
LocalContext context;
|
||||
|
Loading…
Reference in New Issue
Block a user