Commit fb5a5e22 authored by rossberg@chromium.org's avatar rossberg@chromium.org

Object.observe: Make array length and other magic data properties work correctly.

Also, disable TestFastElementsLength test for now, since it flakes on buildbots for yet unknown reasons.

R=mstarzinger@chromium.org
BUG=v8:2409

Review URL: https://codereview.chromium.org/11554019

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13213 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 23d681c5
......@@ -84,8 +84,8 @@ function ObjectObserve(object, callback) {
var objectInfo = objectInfoMap.get(object);
if (IS_UNDEFINED(objectInfo)) {
objectInfo = CreateObjectInfo(object);
%SetIsObserved(object, true);
}
%SetIsObserved(object, true);
var changeObservers = objectInfo.changeObservers;
if (changeObservers.indexOf(callback) < 0)
......@@ -106,8 +106,11 @@ function ObjectUnobserve(object, callback) {
var changeObservers = objectInfo.changeObservers;
var index = changeObservers.indexOf(callback);
if (index >= 0)
if (index >= 0) {
changeObservers.splice(index, 1);
if (changeObservers.length === 0)
%SetIsObserved(object, false);
}
return object;
}
......
......@@ -2943,8 +2943,9 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup,
}
Handle<Object> old_value(heap->the_hole_value(), isolate);
if (FLAG_harmony_observation && map()->is_observed()) {
old_value = handle(lookup->GetLazyValue(), isolate);
if (FLAG_harmony_observation &&
map()->is_observed() && lookup->IsDataProperty()) {
old_value = Object::GetProperty(self, name);
}
// This is a real property that is not read-only, or it is a
......@@ -3030,8 +3031,8 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup,
} else {
LookupResult new_lookup(isolate);
self->LocalLookup(*name, &new_lookup, true);
ASSERT(!new_lookup.GetLazyValue()->IsTheHole());
if (!new_lookup.GetLazyValue()->SameValue(*old_value)) {
if (new_lookup.IsDataProperty() &&
!Object::GetProperty(self, name)->SameValue(*old_value)) {
EnqueueChangeRecord(self, "updated", name, old_value);
}
}
......@@ -3110,15 +3111,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
PropertyAttributes old_attributes = ABSENT;
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);
}
if (lookup.IsDataProperty()) old_value = Object::GetProperty(self, name);
old_attributes = lookup.GetAttributes();
}
......@@ -3188,11 +3181,11 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
} else {
LookupResult new_lookup(isolate);
self->LocalLookup(*name, &new_lookup, true);
ASSERT(!new_lookup.GetLazyValue()->IsTheHole());
if (old_value->IsTheHole() ||
new_lookup.GetAttributes() != old_attributes) {
EnqueueChangeRecord(self, "reconfigured", name, old_value);
} else if (!new_lookup.GetLazyValue()->SameValue(*old_value)) {
} else if (new_lookup.IsDataProperty() &&
!Object::GetProperty(self, name)->SameValue(*old_value)) {
EnqueueChangeRecord(self, "updated", name, old_value);
}
}
......@@ -4255,8 +4248,8 @@ MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) {
Handle<Object> old_value(isolate->heap()->the_hole_value());
bool is_observed = FLAG_harmony_observation && self->map()->is_observed();
if (is_observed) {
old_value = handle(lookup.GetLazyValue(), isolate);
if (is_observed && lookup.IsDataProperty()) {
old_value = Object::GetProperty(self, hname);
}
MaybeObject* result;
......@@ -4947,7 +4940,9 @@ MaybeObject* JSObject::DefineAccessor(String* name_raw,
LookupResult lookup(isolate);
LocalLookup(*name, &lookup, true);
preexists = lookup.IsProperty();
if (preexists) old_value = handle(lookup.GetLazyValue(), isolate);
if (preexists && lookup.IsDataProperty()) {
old_value = Object::GetProperty(self, name);
}
}
}
......
......@@ -310,6 +310,26 @@ class LookupResult BASE_EMBEDDED {
return IsFound() && !IsTransition();
}
bool IsDataProperty() {
switch (type()) {
case FIELD:
case NORMAL:
case CONSTANT_FUNCTION:
return true;
case CALLBACKS: {
Object* callback = GetCallbackObject();
return callback->IsAccessorInfo() || callback->IsForeign();
}
case HANDLER:
case INTERCEPTOR:
case TRANSITION:
case NONEXISTENT:
return false;
}
UNREACHABLE();
return false;
}
bool IsCacheable() { return cacheable_; }
void DisallowCaching() { cacheable_ = false; }
......@@ -327,9 +347,15 @@ class LookupResult BASE_EMBEDDED {
}
case CONSTANT_FUNCTION:
return GetConstantFunction();
default:
case CALLBACKS:
case HANDLER:
case INTERCEPTOR:
case TRANSITION:
case NONEXISTENT:
return Isolate::Current()->heap()->the_hole_value();
}
UNREACHABLE();
return NULL;
}
Map* GetTransitionTarget() {
......
......@@ -893,16 +893,35 @@ function DefineArrayProperty(obj, p, desc, should_throw) {
}
// Make sure the below call to DefineObjectProperty() doesn't overwrite
// any magic "length" property by removing the value.
// TODO(mstarzinger): This hack should be removed once we have addressed the
// respective TODO in Runtime_DefineOrRedefineDataProperty.
// For the time being, we need a hack to prevent Object.observe from
// generating two change records.
var isObserved = %IsObserved(obj);
if (isObserved) %SetIsObserved(obj, false);
obj.length = new_length;
desc.value_ = void 0;
desc.hasValue_ = false;
if (!DefineObjectProperty(obj, "length", desc, should_throw) || threw) {
threw = !DefineObjectProperty(obj, "length", desc, should_throw) || threw;
if (isObserved) %SetIsObserved(obj, true);
if (threw) {
if (should_throw) {
throw MakeTypeError("redefine_disallowed", [p]);
} else {
return false;
}
}
if (isObserved) {
var new_desc = GetOwnProperty(obj, "length");
var updated = length_desc.value_ !== new_desc.value_;
var reconfigured = length_desc.writable_ !== new_desc.writable_ ||
length_desc.configurable_ !== new_desc.configurable_ ||
length_desc.enumerable_ !== new_desc.configurable_;
if (updated || reconfigured) {
NotifyChange(reconfigured ? "reconfigured" : "updated",
obj, "length", length_desc.value_);
}
}
return true;
}
......
......@@ -536,16 +536,13 @@ var objects = [
createProxy(Proxy.create, null),
createProxy(Proxy.createFunction, function(){}),
];
var properties = ["a", "1", 1, "length", "prototype"];
var properties = ["a", "1", 1, "length", "prototype", "name", "caller"];
// Cases that yield non-standard results.
// TODO(observe): ...or don't work yet.
function blacklisted(obj, prop) {
return (obj instanceof Int32Array && prop == 1) ||
(obj instanceof Int32Array && prop === "length") ||
(obj instanceof ArrayBuffer && prop == 1) ||
// TODO(observe): oldValue when reconfiguring array length
(obj instanceof Array && prop === "length")
(obj instanceof ArrayBuffer && prop == 1)
}
for (var i in objects) for (var j in properties) {
......@@ -581,8 +578,10 @@ Object.observe(arr3, observer.callback);
arr.length = 2;
arr.length = 0;
arr.length = 10;
Object.defineProperty(arr, 'length', {writable: false});
arr2.length = 0;
arr2.length = 1; // no change expected
Object.defineProperty(arr2, 'length', {value: 1, writable: false});
arr3.length = 0;
Object.defineProperty(arr3, 'length', {value: 5});
Object.defineProperty(arr3, 'length', {value: 10, writable: false});
......@@ -594,15 +593,15 @@ observer.assertCallbackRecords([
{ 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: arr, name: 'length', type: 'reconfigured', oldValue: 10 },
{ object: arr2, name: '1', type: 'deleted', oldValue: 'beta' },
{ object: arr2, name: 'length', type: 'updated', oldValue: 2 },
{ object: arr2, name: 'length', type: 'reconfigured', oldValue: 1 },
{ object: arr3, name: '2', type: 'deleted', oldValue: 'goodbye' },
{ object: arr3, name: '0', type: 'deleted', oldValue: 'hello' },
{ object: arr3, name: 'length', type: 'updated', oldValue: 6 },
{ object: arr3, name: 'length', type: 'updated', oldValue: 0 },
{ object: arr3, name: 'length', type: 'updated', oldValue: 5 },
// TODO(adamk): This record should be merged with the above
{ object: arr3, name: 'length', type: 'reconfigured' },
{ object: arr3, name: 'length', type: 'reconfigured', oldValue: 5 },
]);
// Assignments in loops (checking different IC states).
......@@ -944,8 +943,11 @@ function TestFastElementsLength(polymorphic, optimize, oldSize, newSize) {
}
}
// TODO(rossberg): Still flaky on buildbots, disable for now...
/*
for (var b1 = 0; b1 < 2; ++b1)
for (var b2 = 0; b2 < 2; ++b2)
for (var n1 = 0; n1 < 3; ++n1)
for (var n2 = 0; n2 < 3; ++n2)
TestFastElementsLength(b1 != 0, b2 != 0, 20*n1, 20*n2);
*/
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