Revision: 3829
Author: [email protected]
Date: Wed Feb 10 06:44:15 2010
Log: Refactor prototype setting code and expose SetPrototype to public V8 API.

Review URL: http://codereview.chromium.org/598020
http://code.google.com/p/v8/source/detail?r=3829

Modified:
 /branches/bleeding_edge/include/v8.h
 /branches/bleeding_edge/src/accessors.cc
 /branches/bleeding_edge/src/api.cc
 /branches/bleeding_edge/src/handles.cc
 /branches/bleeding_edge/src/handles.h
 /branches/bleeding_edge/src/objects.cc
 /branches/bleeding_edge/src/objects.h
 /branches/bleeding_edge/test/cctest/test-api.cc

=======================================
--- /branches/bleeding_edge/include/v8.h        Mon Jan 11 04:13:24 2010
+++ /branches/bleeding_edge/include/v8.h        Wed Feb 10 06:44:15 2010
@@ -1196,6 +1196,13 @@
    */
   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.
=======================================
--- /branches/bleeding_edge/src/accessors.cc    Fri Jan 29 01:52:51 2010
+++ /branches/bleeding_edge/src/accessors.cc    Wed Feb 10 06:44:15 2010
@@ -647,42 +647,9 @@
 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);
 }


=======================================
--- /branches/bleeding_edge/src/api.cc  Mon Jan 18 07:48:41 2010
+++ /branches/bleeding_edge/src/api.cc  Wed Feb 10 06:44:15 2010
@@ -2030,6 +2030,19 @@
   i::Handle<i::Object> result = i::GetPrototype(self);
   return Utils::ToLocal(result);
 }
+
+
+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(
=======================================
--- /branches/bleeding_edge/src/handles.cc      Wed Feb  3 08:12:55 2010
+++ /branches/bleeding_edge/src/handles.cc      Wed Feb 10 06:44:15 2010
@@ -298,6 +298,12 @@
   Handle<Object> result(obj->GetPrototype());
   return result;
 }
+
+
+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,
=======================================
--- /branches/bleeding_edge/src/handles.h       Fri Jan 29 01:52:51 2010
+++ /branches/bleeding_edge/src/handles.h       Wed Feb 10 06:44:15 2010
@@ -240,6 +240,8 @@

 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.
=======================================
--- /branches/bleeding_edge/src/objects.cc      Fri Jan 29 03:46:55 2010
+++ /branches/bleeding_edge/src/objects.cc      Wed Feb 10 06:44:15 2010
@@ -5351,6 +5351,48 @@
   set_elements(FixedArray::cast(obj));
   return this;
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/objects.h       Thu Feb  4 12:36:58 2010
+++ /branches/bleeding_edge/src/objects.h       Wed Feb 10 06:44:15 2010
@@ -1317,6 +1317,9 @@
   // 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);
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc     Tue Feb  9 08:14:14 2010
+++ /branches/bleeding_edge/test/cctest/test-api.cc     Wed Feb 10 06:44:15 2010
@@ -4841,6 +4841,84 @@
   CHECK(proto->IsObject());
   CHECK(Local<v8::Object>::Cast(proto)->Get(v8_str("z"))->IsUndefined());
 }
+
+
+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-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to