Revision: 12905
Author: [email protected]
Date: Thu Nov 8 08:12:12 2012
Log: Handle Object.observe notifications for setting Array.length
Also handles notification of deleted properties when an array
is truncated by setting length.
Review URL: https://codereview.chromium.org/11338048
Patch from Adam Klein <[email protected]>.
http://code.google.com/p/v8/source/detail?r=12905
Modified:
/branches/bleeding_edge/src/accessors.cc
/branches/bleeding_edge/src/objects.cc
/branches/bleeding_edge/src/objects.h
/branches/bleeding_edge/test/mjsunit/harmony/object-observe.js
=======================================
--- /branches/bleeding_edge/src/accessors.cc Mon Oct 8 05:58:46 2012
+++ /branches/bleeding_edge/src/accessors.cc Thu Nov 8 08:12:12 2012
@@ -93,6 +93,47 @@
if (wrapper->map() == number_map) return wrapper->value();
return value;
}
+
+
+static MaybeObject* ArraySetLengthObserved(Isolate* isolate,
+ Handle<JSArray> array,
+ Handle<Object>
new_length_handle) {
+ List<Handle<String> > indices;
+ List<Handle<Object> > old_values;
+ Handle<Object> old_length_handle(array->length(), isolate);
+ uint32_t old_length = 0;
+ CHECK(old_length_handle->ToArrayIndex(&old_length));
+ uint32_t new_length = 0;
+ CHECK(new_length_handle->ToArrayIndex(&new_length));
+ // TODO(adamk): This loop can be very slow for arrays in dictionary mode.
+ // Find another way to iterate over arrays with dictionary elements.
+ for (uint32_t i = old_length - 1; i + 1 > new_length; --i) {
+ PropertyAttributes attributes = array->GetLocalElementAttribute(i);
+ if (attributes == ABSENT) continue;
+ // A non-configurable property will cause the truncation operation to
+ // stop at this index.
+ if (attributes == DONT_DELETE) break;
+ // TODO(adamk): Don't fetch the old value if it's an accessor.
+ old_values.Add(Object::GetElement(array, i));
+ indices.Add(isolate->factory()->Uint32ToString(i));
+ }
+
+ MaybeObject* result = array->SetElementsLength(*new_length_handle);
+ Handle<Object> hresult;
+ if (!result->ToHandle(&hresult)) return result;
+
+ CHECK(array->length()->ToArrayIndex(&new_length));
+ if (old_length != new_length) {
+ for (int i = 0; i < indices.length(); ++i) {
+ JSObject::EnqueueChangeRecord(
+ array, "deleted", indices[i], old_values[i]);
+ }
+ JSObject::EnqueueChangeRecord(
+ array, "updated", isolate->factory()->length_symbol(),
+ old_length_handle);
+ }
+ return *hresult;
+}
MaybeObject* Accessors::ArraySetLength(JSObject* object, Object* value,
void*) {
@@ -112,7 +153,7 @@
HandleScope scope(isolate);
// Protect raw pointers.
- Handle<JSObject> object_handle(object, isolate);
+ Handle<JSArray> array_handle(JSArray::cast(object), isolate);
Handle<Object> value_handle(value, isolate);
bool has_exception;
@@ -122,7 +163,11 @@
if (has_exception) return Failure::Exception();
if (uint32_v->Number() == number_v->Number()) {
- return
Handle<JSArray>::cast(object_handle)->SetElementsLength(*uint32_v);
+ if (FLAG_harmony_observation && array_handle->map()->is_observed()) {
+ return ArraySetLengthObserved(isolate, array_handle, uint32_v);
+ } else {
+ return array_handle->SetElementsLength(*uint32_v);
+ }
}
return isolate->Throw(
*isolate->factory()->NewRangeError("invalid_array_length",
=======================================
--- /branches/bleeding_edge/src/objects.cc Thu Nov 8 05:44:59 2012
+++ /branches/bleeding_edge/src/objects.cc Thu Nov 8 08:12:12 2012
@@ -1717,20 +1717,21 @@
if (!result->ToHandle(&hresult)) return result;
if (FLAG_harmony_observation && map()->is_observed()) {
- this->EnqueueChangeRecord(
- "new", handle(name), handle(heap->the_hole_value()));
+ EnqueueChangeRecord(handle(this), "new", handle(name),
+ handle(heap->the_hole_value()));
}
return *hresult;
}
-void JSObject::EnqueueChangeRecord(
- const char* type_str, Handle<String> name, Handle<Object> old_value) {
- Isolate* isolate = GetIsolate();
+void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
+ const char* type_str,
+ Handle<String> name,
+ Handle<Object> old_value) {
+ Isolate* isolate = object->GetIsolate();
HandleScope scope;
Handle<String> type = isolate->factory()->LookupAsciiSymbol(type_str);
- Handle<JSObject> object(this);
Handle<Object> args[] = { type, object, name, old_value };
bool threw;
Execution::Call(Handle<JSFunction>(isolate->observers_notify_change()),
@@ -2998,13 +2999,13 @@
if (FLAG_harmony_observation && map()->is_observed()) {
if (lookup->IsTransition()) {
- self->EnqueueChangeRecord("new", name, old_value);
+ EnqueueChangeRecord(self, "new", name, old_value);
} else {
LookupResult new_lookup(self->GetIsolate());
self->LocalLookup(*name, &new_lookup);
ASSERT(!new_lookup.GetLazyValue()->IsTheHole());
if (!new_lookup.GetLazyValue()->SameValue(*old_value)) {
- self->EnqueueChangeRecord("updated", name, old_value);
+ EnqueueChangeRecord(self, "updated", name, old_value);
}
}
}
@@ -3146,16 +3147,16 @@
if (FLAG_harmony_observation && map()->is_observed()) {
if (lookup.IsTransition()) {
- self->EnqueueChangeRecord("new", name, old_value);
+ EnqueueChangeRecord(self, "new", name, old_value);
} else {
LookupResult new_lookup(isolate);
self->LocalLookup(*name, &new_lookup);
ASSERT(!new_lookup.GetLazyValue()->IsTheHole());
if (old_value->IsTheHole() ||
new_lookup.GetAttributes() != old_attributes) {
- self->EnqueueChangeRecord("reconfigured", name, old_value);
+ EnqueueChangeRecord(self, "reconfigured", name, old_value);
} else if (!new_lookup.GetLazyValue()->SameValue(*old_value)) {
- self->EnqueueChangeRecord("updated", name, old_value);
+ EnqueueChangeRecord(self, "updated", name, old_value);
}
}
}
@@ -4152,7 +4153,7 @@
if (FLAG_harmony_observation && map()->is_observed()) {
if (preexists && !self->HasLocalElement(index))
- self->EnqueueChangeRecord("deleted", name, old_value);
+ EnqueueChangeRecord(self, "deleted", name, old_value);
}
return *hresult;
@@ -4239,7 +4240,7 @@
if (FLAG_harmony_observation && map()->is_observed()) {
if (!self->HasLocalProperty(*hname))
- self->EnqueueChangeRecord("deleted", hname, old_value);
+ EnqueueChangeRecord(self, "deleted", hname, old_value);
}
return *hresult;
@@ -4906,7 +4907,7 @@
if (FLAG_harmony_observation && map()->is_observed()) {
const char* type = preexists ? "reconfigured" : "new";
- self->EnqueueChangeRecord(type, name, old_value);
+ EnqueueChangeRecord(self, type, name, old_value);
}
return *hresult;
@@ -10331,13 +10332,13 @@
if (FLAG_harmony_observation && map()->is_observed()) {
PropertyAttributes new_attributes =
self->GetLocalPropertyAttribute(*name);
if (!preexists) {
- self->EnqueueChangeRecord("new", name, old_value);
+ EnqueueChangeRecord(self, "new", name, old_value);
} else if (new_attributes != old_attributes || old_value->IsTheHole())
{
- self->EnqueueChangeRecord("reconfigured", name, old_value);
+ EnqueueChangeRecord(self, "reconfigured", name, old_value);
} else {
Handle<Object> newValue = Object::GetElement(self, index);
if (!newValue->SameValue(*old_value))
- self->EnqueueChangeRecord("updated", name, old_value);
+ EnqueueChangeRecord(self, "updated", name, old_value);
}
}
=======================================
--- /branches/bleeding_edge/src/objects.h Thu Nov 8 05:44:59 2012
+++ /branches/bleeding_edge/src/objects.h Thu Nov 8 08:12:12 2012
@@ -2208,6 +2208,12 @@
static inline int SizeOf(Map* map, HeapObject* object);
};
+ // Enqueue change record for Object.observe. May cause GC.
+ static void EnqueueChangeRecord(Handle<JSObject> object,
+ const char* type,
+ Handle<String> name,
+ Handle<Object> old_value);
+
// Deliver change records to observers. May cause GC.
static void DeliverChangeRecords(Isolate* isolate);
@@ -2316,11 +2322,6 @@
MUST_USE_RESULT MaybeObject* SetHiddenPropertiesHashTable(
Object* value);
- // Enqueue change record for Object.observe. May cause GC.
- void EnqueueChangeRecord(const char* type,
- Handle<String> name,
- Handle<Object> old_value);
-
DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject);
};
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Thu Nov
8 05:15:54 2012
+++ /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Thu Nov
8 08:12:12 2012
@@ -333,6 +333,41 @@
{ object: obj, name: "1", type: "new" },
]);
+// Observing array length (including truncation)
+reset();
+var arr = ['a', 'b', 'c', 'd'];
+var arr2 = ['alpha', 'beta'];
+var arr3 = ['hello'];
+// TODO(adamk): Enable this test case when it can run in a reasonable
+// amount of time.
+//var slow_arr = new Array(1000000000);
+//slow_arr[500000000] = 'hello';
+Object.defineProperty(arr, '0', {configurable: false});
+Object.defineProperty(arr, '2', {get: function(){}});
+Object.defineProperty(arr2, '0', {get: function(){}, configurable: false});
+Object.observe(arr, observer.callback);
+Object.observe(arr2, observer.callback);
+Object.observe(arr3, observer.callback);
+arr.length = 2;
+arr.length = 0;
+arr.length = 10;
+arr2.length = 0;
+arr2.length = 1; // no change expected
+arr3.length = 0;
+Object.deliverChangeRecords(observer.callback);
+observer.assertCallbackRecords([
+ { object: arr, name: '3', type: 'deleted', oldValue: 'd' },
+ // TODO(adamk): oldValue should not be present below
+ { object: arr, name: '2', type: 'deleted', oldValue: undefined },
+ { object: arr, name: 'length', type: 'updated', oldValue: 4 },
+ { object: arr, name: '1', type: 'deleted', oldValue: 'b' },
+ { object: arr, name: 'length', type: 'updated', oldValue: 2 },
+ { object: arr, name: 'length', type: 'updated', oldValue: 1 },
+ { object: arr2, name: '1', type: 'deleted', oldValue: 'beta' },
+ { object: arr2, name: 'length', type: 'updated', oldValue: 2 },
+ { object: arr3, name: '0', type: 'deleted', oldValue: 'hello' },
+ { object: arr3, name: 'length', type: 'updated', oldValue: 1 },
+]);
// Assignments in loops (checking different IC states).
reset();
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev