diff --git a/include/v8.h b/include/v8.h index 6125286e80..3adefbcb0d 100644 --- a/include/v8.h +++ b/include/v8.h @@ -1196,6 +1196,13 @@ class V8EXPORT Object : public Value { */ Local 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 prototype); + /** * Finds an instance of the given function template in the prototype * chain. diff --git a/src/accessors.cc b/src/accessors.cc index 5a029285e8..b05719edb5 100644 --- a/src/accessors.cc +++ b/src/accessors.cc @@ -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(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); } diff --git a/src/api.cc b/src/api.cc index 322c90fc5a..bd5fdd81dc 100644 --- a/src/api.cc +++ b/src/api.cc @@ -2032,6 +2032,19 @@ Local v8::Object::GetPrototype() { } +bool v8::Object::SetPrototype(Handle value) { + ON_BAILOUT("v8::Object::SetPrototype()", return false); + ENTER_V8; + i::Handle self = Utils::OpenHandle(this); + i::Handle value_obj = Utils::OpenHandle(*value); + EXCEPTION_PREAMBLE(); + i::Handle result = i::SetPrototype(self, value_obj); + has_pending_exception = result.is_null(); + EXCEPTION_BAILOUT_CHECK(false); + return true; +} + + Local v8::Object::FindInstanceInPrototypeChain( v8::Handle tmpl) { ON_BAILOUT("v8::Object::FindInstanceInPrototypeChain()", diff --git a/src/handles.cc b/src/handles.cc index 909d6e6a22..936177dbba 100644 --- a/src/handles.cc +++ b/src/handles.cc @@ -300,6 +300,12 @@ Handle GetPrototype(Handle obj) { } +Handle SetPrototype(Handle obj, Handle value) { + const bool skip_hidden_prototypes = false; + CALL_HEAP_FUNCTION(obj->SetPrototype(*value, skip_hidden_prototypes), Object); +} + + Handle GetHiddenProperties(Handle obj, bool create_if_needed) { Object* holder = obj->BypassGlobalProxy(); diff --git a/src/handles.h b/src/handles.h index 04f087bd8e..90e51fa56c 100644 --- a/src/handles.h +++ b/src/handles.h @@ -240,6 +240,8 @@ Handle GetPropertyWithInterceptor(Handle receiver, Handle GetPrototype(Handle obj); +Handle SetPrototype(Handle obj, Handle 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. diff --git a/src/objects.cc b/src/objects.cc index 6dd1d49242..2427a08753 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -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(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: { diff --git a/src/objects.h b/src/objects.h index 1ed8e4c8fc..3d60d306eb 100644 --- a/src/objects.h +++ b/src/objects.h @@ -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); diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index bd543ec481..e5e1e0d227 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -4843,6 +4843,84 @@ THREADED_TEST(HiddenPrototype) { } +THREADED_TEST(SetPrototype) { + v8::HandleScope handle_scope; + LocalContext context; + + Local t0 = v8::FunctionTemplate::New(); + t0->InstanceTemplate()->Set(v8_str("x"), v8_num(0)); + Local t1 = v8::FunctionTemplate::New(); + t1->SetHiddenPrototype(true); + t1->InstanceTemplate()->Set(v8_str("y"), v8_num(1)); + Local t2 = v8::FunctionTemplate::New(); + t2->SetHiddenPrototype(true); + t2->InstanceTemplate()->Set(v8_str("z"), v8_num(2)); + Local t3 = v8::FunctionTemplate::New(); + t3->InstanceTemplate()->Set(v8_str("u"), v8_num(3)); + + Local o0 = t0->GetFunction()->NewInstance(); + Local o1 = t1->GetFunction()->NewInstance(); + Local o2 = t2->GetFunction()->NewInstance(); + Local 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 proto = o0->Get(v8_str("__proto__")); + CHECK(proto->IsObject()); + CHECK_EQ(v8::Handle::Cast(proto), o3); + + // However, Object::GetPrototype ignores hidden prototype. + Local proto0 = o0->GetPrototype(); + CHECK(proto0->IsObject()); + CHECK_EQ(v8::Handle::Cast(proto0), o1); + + Local proto1 = o1->GetPrototype(); + CHECK(proto1->IsObject()); + CHECK_EQ(v8::Handle::Cast(proto1), o2); + + Local proto2 = o2->GetPrototype(); + CHECK(proto2->IsObject()); + CHECK_EQ(v8::Handle::Cast(proto2), o3); +} + + +THREADED_TEST(SetPrototypeThrows) { + v8::HandleScope handle_scope; + LocalContext context; + + Local t = v8::FunctionTemplate::New(); + + Local o0 = t->GetFunction()->NewInstance(); + Local 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;