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.
*/
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);
}
}
+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()",
}
+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();
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.
}
+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: {
// 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);
}
+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;