Commit 2af47448 authored by rossberg@chromium.org's avatar rossberg@chromium.org

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 <adamk@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12905 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent a65e166a
...@@ -95,6 +95,47 @@ Object* Accessors::FlattenNumber(Object* value) { ...@@ -95,6 +95,47 @@ Object* Accessors::FlattenNumber(Object* 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*) { MaybeObject* Accessors::ArraySetLength(JSObject* object, Object* value, void*) {
Isolate* isolate = object->GetIsolate(); Isolate* isolate = object->GetIsolate();
...@@ -112,7 +153,7 @@ MaybeObject* Accessors::ArraySetLength(JSObject* object, Object* value, void*) { ...@@ -112,7 +153,7 @@ MaybeObject* Accessors::ArraySetLength(JSObject* object, Object* value, void*) {
HandleScope scope(isolate); HandleScope scope(isolate);
// Protect raw pointers. // Protect raw pointers.
Handle<JSObject> object_handle(object, isolate); Handle<JSArray> array_handle(JSArray::cast(object), isolate);
Handle<Object> value_handle(value, isolate); Handle<Object> value_handle(value, isolate);
bool has_exception; bool has_exception;
...@@ -122,7 +163,11 @@ MaybeObject* Accessors::ArraySetLength(JSObject* object, Object* value, void*) { ...@@ -122,7 +163,11 @@ MaybeObject* Accessors::ArraySetLength(JSObject* object, Object* value, void*) {
if (has_exception) return Failure::Exception(); if (has_exception) return Failure::Exception();
if (uint32_v->Number() == number_v->Number()) { 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( return isolate->Throw(
*isolate->factory()->NewRangeError("invalid_array_length", *isolate->factory()->NewRangeError("invalid_array_length",
......
...@@ -1717,20 +1717,21 @@ MaybeObject* JSObject::AddProperty(String* name, ...@@ -1717,20 +1717,21 @@ MaybeObject* JSObject::AddProperty(String* name,
if (!result->ToHandle(&hresult)) return result; if (!result->ToHandle(&hresult)) return result;
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
this->EnqueueChangeRecord( EnqueueChangeRecord(handle(this), "new", handle(name),
"new", handle(name), handle(heap->the_hole_value())); handle(heap->the_hole_value()));
} }
return *hresult; return *hresult;
} }
void JSObject::EnqueueChangeRecord( void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
const char* type_str, Handle<String> name, Handle<Object> old_value) { const char* type_str,
Isolate* isolate = GetIsolate(); Handle<String> name,
Handle<Object> old_value) {
Isolate* isolate = object->GetIsolate();
HandleScope scope; HandleScope scope;
Handle<String> type = isolate->factory()->LookupAsciiSymbol(type_str); Handle<String> type = isolate->factory()->LookupAsciiSymbol(type_str);
Handle<JSObject> object(this);
Handle<Object> args[] = { type, object, name, old_value }; Handle<Object> args[] = { type, object, name, old_value };
bool threw; bool threw;
Execution::Call(Handle<JSFunction>(isolate->observers_notify_change()), Execution::Call(Handle<JSFunction>(isolate->observers_notify_change()),
...@@ -2998,13 +2999,13 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup, ...@@ -2998,13 +2999,13 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup,
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
if (lookup->IsTransition()) { if (lookup->IsTransition()) {
self->EnqueueChangeRecord("new", name, old_value); EnqueueChangeRecord(self, "new", name, old_value);
} else { } else {
LookupResult new_lookup(self->GetIsolate()); LookupResult new_lookup(self->GetIsolate());
self->LocalLookup(*name, &new_lookup); self->LocalLookup(*name, &new_lookup);
ASSERT(!new_lookup.GetLazyValue()->IsTheHole()); ASSERT(!new_lookup.GetLazyValue()->IsTheHole());
if (!new_lookup.GetLazyValue()->SameValue(*old_value)) { if (!new_lookup.GetLazyValue()->SameValue(*old_value)) {
self->EnqueueChangeRecord("updated", name, old_value); EnqueueChangeRecord(self, "updated", name, old_value);
} }
} }
} }
...@@ -3146,16 +3147,16 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( ...@@ -3146,16 +3147,16 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
if (lookup.IsTransition()) { if (lookup.IsTransition()) {
self->EnqueueChangeRecord("new", name, old_value); EnqueueChangeRecord(self, "new", name, old_value);
} else { } else {
LookupResult new_lookup(isolate); LookupResult new_lookup(isolate);
self->LocalLookup(*name, &new_lookup); self->LocalLookup(*name, &new_lookup);
ASSERT(!new_lookup.GetLazyValue()->IsTheHole()); ASSERT(!new_lookup.GetLazyValue()->IsTheHole());
if (old_value->IsTheHole() || if (old_value->IsTheHole() ||
new_lookup.GetAttributes() != old_attributes) { 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)) { } else if (!new_lookup.GetLazyValue()->SameValue(*old_value)) {
self->EnqueueChangeRecord("updated", name, old_value); EnqueueChangeRecord(self, "updated", name, old_value);
} }
} }
} }
...@@ -4152,7 +4153,7 @@ MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) { ...@@ -4152,7 +4153,7 @@ MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) {
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
if (preexists && !self->HasLocalElement(index)) if (preexists && !self->HasLocalElement(index))
self->EnqueueChangeRecord("deleted", name, old_value); EnqueueChangeRecord(self, "deleted", name, old_value);
} }
return *hresult; return *hresult;
...@@ -4239,7 +4240,7 @@ MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) { ...@@ -4239,7 +4240,7 @@ MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) {
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
if (!self->HasLocalProperty(*hname)) if (!self->HasLocalProperty(*hname))
self->EnqueueChangeRecord("deleted", hname, old_value); EnqueueChangeRecord(self, "deleted", hname, old_value);
} }
return *hresult; return *hresult;
...@@ -4906,7 +4907,7 @@ MaybeObject* JSObject::DefineAccessor(String* name_raw, ...@@ -4906,7 +4907,7 @@ MaybeObject* JSObject::DefineAccessor(String* name_raw,
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
const char* type = preexists ? "reconfigured" : "new"; const char* type = preexists ? "reconfigured" : "new";
self->EnqueueChangeRecord(type, name, old_value); EnqueueChangeRecord(self, type, name, old_value);
} }
return *hresult; return *hresult;
...@@ -10331,13 +10332,13 @@ MaybeObject* JSObject::SetElement(uint32_t index, ...@@ -10331,13 +10332,13 @@ MaybeObject* JSObject::SetElement(uint32_t index,
if (FLAG_harmony_observation && map()->is_observed()) { if (FLAG_harmony_observation && map()->is_observed()) {
PropertyAttributes new_attributes = self->GetLocalPropertyAttribute(*name); PropertyAttributes new_attributes = self->GetLocalPropertyAttribute(*name);
if (!preexists) { if (!preexists) {
self->EnqueueChangeRecord("new", name, old_value); EnqueueChangeRecord(self, "new", name, old_value);
} else if (new_attributes != old_attributes || old_value->IsTheHole()) { } else if (new_attributes != old_attributes || old_value->IsTheHole()) {
self->EnqueueChangeRecord("reconfigured", name, old_value); EnqueueChangeRecord(self, "reconfigured", name, old_value);
} else { } else {
Handle<Object> newValue = Object::GetElement(self, index); Handle<Object> newValue = Object::GetElement(self, index);
if (!newValue->SameValue(*old_value)) if (!newValue->SameValue(*old_value))
self->EnqueueChangeRecord("updated", name, old_value); EnqueueChangeRecord(self, "updated", name, old_value);
} }
} }
......
...@@ -2208,6 +2208,12 @@ class JSObject: public JSReceiver { ...@@ -2208,6 +2208,12 @@ class JSObject: public JSReceiver {
static inline int SizeOf(Map* map, HeapObject* object); 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. // Deliver change records to observers. May cause GC.
static void DeliverChangeRecords(Isolate* isolate); static void DeliverChangeRecords(Isolate* isolate);
...@@ -2316,11 +2322,6 @@ class JSObject: public JSReceiver { ...@@ -2316,11 +2322,6 @@ class JSObject: public JSReceiver {
MUST_USE_RESULT MaybeObject* SetHiddenPropertiesHashTable( MUST_USE_RESULT MaybeObject* SetHiddenPropertiesHashTable(
Object* value); 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); DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject);
}; };
......
...@@ -333,6 +333,41 @@ observer.assertCallbackRecords([ ...@@ -333,6 +333,41 @@ observer.assertCallbackRecords([
{ object: obj, name: "1", type: "new" }, { 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). // Assignments in loops (checking different IC states).
reset(); reset();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment