Revision: 13177
Author:   [email protected]
Date:     Mon Dec 10 02:53:57 2012
Log:      Object.observe support for Function 'prototype' property

BUG=v8:2409

Review URL: https://codereview.chromium.org/11416353
Patch from Adam Klein <[email protected]>.
http://code.google.com/p/v8/source/detail?r=13177

Modified:
 /branches/bleeding_edge/src/accessors.cc
 /branches/bleeding_edge/src/objects.cc
 /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js

=======================================
--- /branches/bleeding_edge/src/accessors.cc    Wed Dec  5 03:47:45 2012
+++ /branches/bleeding_edge/src/accessors.cc    Mon Dec 10 02:53:57 2012
@@ -465,24 +465,46 @@


 MaybeObject* Accessors::FunctionSetPrototype(JSObject* object,
-                                             Object* value,
+                                             Object* value_raw,
                                              void*) {
-  Heap* heap = object->GetHeap();
-  JSFunction* function = FindInstanceOf<JSFunction>(object);
-  if (function == NULL) return heap->undefined_value();
-  if (!function->should_have_prototype()) {
+  Isolate* isolate = object->GetIsolate();
+  Heap* heap = isolate->heap();
+  JSFunction* function_raw = FindInstanceOf<JSFunction>(object);
+  if (function_raw == NULL) return heap->undefined_value();
+  if (!function_raw->should_have_prototype()) {
     // Since we hit this accessor, object will have no prototype property.
return object->SetLocalPropertyIgnoreAttributes(heap->prototype_symbol(),
-                                                    value,
+                                                    value_raw,
                                                     NONE);
   }

-  Object* prototype;
-  { MaybeObject* maybe_prototype = function->SetPrototype(value);
-    if (!maybe_prototype->ToObject(&prototype)) return maybe_prototype;
+  HandleScope scope(isolate);
+  Handle<JSFunction> function(function_raw, isolate);
+  Handle<Object> value(value_raw, isolate);
+
+  Handle<Object> old_value;
+  bool is_observed =
+      FLAG_harmony_observation &&
+      *function == object &&
+      function->map()->is_observed();
+  if (is_observed) {
+    if (function->has_prototype())
+      old_value = handle(function->prototype(), isolate);
+    else
+      old_value = isolate->factory()->NewFunctionPrototype(function);
   }
-  ASSERT(function->prototype() == value);
-  return function;
+
+  Handle<Object> result;
+  MaybeObject* maybe_result = function->SetPrototype(*value);
+  if (!maybe_result->ToHandle(&result, isolate)) return maybe_result;
+  ASSERT(function->prototype() == *value);
+
+  if (is_observed && !old_value->SameValue(*value)) {
+    JSObject::EnqueueChangeRecord(
+ function, "updated", isolate->factory()->prototype_symbol(), old_value);
+  }
+
+  return *function;
 }


=======================================
--- /branches/bleeding_edge/src/objects.cc      Fri Dec  7 04:58:09 2012
+++ /branches/bleeding_edge/src/objects.cc      Mon Dec 10 02:53:57 2012
@@ -27,6 +27,7 @@

 #include "v8.h"

+#include "accessors.h"
 #include "api.h"
 #include "arguments.h"
 #include "bootstrapper.h"
@@ -3107,8 +3108,17 @@

   Handle<Object> old_value(isolate->heap()->the_hole_value(), isolate);
   PropertyAttributes old_attributes = ABSENT;
-  if (FLAG_harmony_observation && map()->is_observed()) {
-    old_value = handle(lookup.GetLazyValue(), isolate);
+ bool is_observed = FLAG_harmony_observation && self->map()->is_observed();
+  if (is_observed) {
+    // Function prototypes are stored specially
+    if (self->IsJSFunction() &&
+        JSFunction::cast(*self)->should_have_prototype() &&
+        name->Equals(isolate->heap()->prototype_symbol())) {
+      MaybeObject* maybe = Accessors::FunctionGetPrototype(*self, NULL);
+      if (!maybe->ToHandle(&old_value, isolate)) return maybe;
+    } else {
+      old_value = handle(lookup.GetLazyValue(), isolate);
+    }
     old_attributes = lookup.GetAttributes();
   }

@@ -3172,7 +3182,7 @@
   Handle<Object> hresult;
   if (!result->ToHandle(&hresult, isolate)) return result;

-  if (FLAG_harmony_observation && map()->is_observed()) {
+  if (is_observed) {
     if (lookup.IsTransition()) {
       EnqueueChangeRecord(self, "new", name, old_value);
     } else {
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Wed Dec 5 04:03:57 2012 +++ /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Mon Dec 10 02:53:57 2012
@@ -455,7 +455,7 @@
   delete obj[prop];
 }

-function TestObserveNonConfigurable(obj, prop) {
+function TestObserveNonConfigurable(obj, prop, desc) {
   reset();
   obj[prop] = 1;
   Object.observe(obj, observer.callback);
@@ -465,10 +465,10 @@
   Object.defineProperty(obj, prop, {value: 6});
   Object.defineProperty(obj, prop, {value: 6});  // ignored
   Object.defineProperty(obj, prop, {value: 7});
-  Object.defineProperty(obj, prop, {enumerable: true});  // ignored
+  Object.defineProperty(obj, prop,
+                        {enumerable: desc.enumerable});  // ignored
   Object.defineProperty(obj, prop, {writable: false});
   obj[prop] = 7;  // ignored
-  Object.defineProperty(obj, prop, {get: function() {}});  // ignored
   Object.deliverChangeRecords(observer.callback);
   observer.assertCallbackRecords([
     { object: obj, name: prop, type: "updated", oldValue: 1 },
@@ -544,9 +544,7 @@
     (obj instanceof Int32Array && prop === "length") ||
     (obj instanceof ArrayBuffer && prop == 1) ||
     // TODO(observe): oldValue when reconfiguring array length
-    (obj instanceof Array && prop === "length") ||
-    // TODO(observe): prototype property on functions
-    (obj instanceof Function && prop === "prototype")
+    (obj instanceof Array && prop === "length")
 }

 for (var i in objects) for (var j in properties) {
@@ -558,7 +556,7 @@
   if (!desc || desc.configurable)
     TestObserveConfigurable(obj, prop);
   else if (desc.writable)
-    TestObserveNonConfigurable(obj, prop);
+    TestObserveNonConfigurable(obj, prop, desc);
 }


@@ -817,6 +815,7 @@
   { object: array, name: 'length', type: 'updated', oldValue: 1},
 ]);

+
 // __proto__
 reset();
 var obj = {};
@@ -836,3 +835,39 @@
   { object: obj, name: '__proto__', type: 'prototype', oldValue: p },
   { object: obj, name: '__proto__', type: 'prototype', oldValue: null },
 ]);
+
+// Function.prototype
+reset();
+var fun = function(){};
+Object.observe(fun, observer.callback);
+var myproto = {foo: 'bar'};
+fun.prototype = myproto;
+fun.prototype = 7;
+fun.prototype = 7;  // ignored
+Object.defineProperty(fun, 'prototype', {value: 8});
+Object.deliverChangeRecords(observer.callback);
+observer.assertRecordCount(3);
+// Manually examine the first record in order to test
+// lazy creation of oldValue
+assertSame(fun, observer.records[0].object);
+assertEquals('prototype', observer.records[0].name);
+assertEquals('updated', observer.records[0].type);
+// The only existing reference to the oldValue object is in this
+// record, so to test that lazy creation happened correctly
+// we compare its constructor to our function (one of the invariants
+// ensured when creating an object via AllocateFunctionPrototype).
+assertSame(fun, observer.records[0].oldValue.constructor);
+observer.records.splice(0, 1);
+observer.assertCallbackRecords([
+  { object: fun, name: 'prototype', type: 'updated', oldValue: myproto },
+  { object: fun, name: 'prototype', type: 'updated', oldValue: 7 },
+]);
+
+// Function.prototype should not be observable except on the object itself
+reset();
+var fun = function(){};
+var obj = { __proto__: fun };
+Object.observe(obj, observer.callback);
+obj.prototype = 7;
+Object.deliverChangeRecords(observer.callback);
+observer.assertNotCalled();

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to