Commit 19341006 authored by neis's avatar neis Committed by Commit bot

Fix corner-case behavior of JSObject::SetPrototype.

Setting the prototype to whatever it currently is must succeed even if
the object is not extensible.

R=verwaest@chromium.org
BUG=

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

Cr-Commit-Position: refs/heads/master@{#31527}
parent 5cf6ee34
...@@ -14002,6 +14002,26 @@ Maybe<bool> JSObject::SetPrototypeUnobserved(Handle<JSObject> object, ...@@ -14002,6 +14002,26 @@ Maybe<bool> JSObject::SetPrototypeUnobserved(Handle<JSObject> object,
// SpiderMonkey behaves this way. // SpiderMonkey behaves this way.
if (!value->IsJSReceiver() && !value->IsNull()) return Just(true); if (!value->IsJSReceiver() && !value->IsNull()) return Just(true);
bool dictionary_elements_in_chain =
object->map()->DictionaryElementsInPrototypeChainOnly();
bool all_extensible = object->map()->is_extensible();
Handle<JSObject> real_receiver = object;
if (from_javascript) {
// Find the first object in the chain whose prototype object is not
// hidden.
PrototypeIterator iter(isolate, real_receiver);
while (!iter.IsAtEnd(PrototypeIterator::END_AT_NON_HIDDEN)) {
real_receiver = PrototypeIterator::GetCurrent<JSObject>(iter);
iter.Advance();
all_extensible = all_extensible && real_receiver->map()->is_extensible();
}
}
Handle<Map> map(real_receiver->map());
// Nothing to do if prototype is already set.
if (map->prototype() == *value) return Just(true);
// From 8.6.2 Object Internal Methods // From 8.6.2 Object Internal Methods
// ... // ...
// In addition, if [[Extensible]] is false the value of the [[Class]] and // In addition, if [[Extensible]] is false the value of the [[Class]] and
...@@ -14010,16 +14030,14 @@ Maybe<bool> JSObject::SetPrototypeUnobserved(Handle<JSObject> object, ...@@ -14010,16 +14030,14 @@ Maybe<bool> JSObject::SetPrototypeUnobserved(Handle<JSObject> object,
// Implementation specific extensions that modify [[Class]], [[Prototype]] // Implementation specific extensions that modify [[Class]], [[Prototype]]
// or [[Extensible]] must not violate the invariants defined in the preceding // or [[Extensible]] must not violate the invariants defined in the preceding
// paragraph. // paragraph.
if (!object->map()->is_extensible()) { if (!all_extensible) {
RETURN_FAILURE(isolate, should_throw, RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kNonExtensibleProto, object)); NewTypeError(MessageTemplate::kNonExtensibleProto, object));
// TODO(neis): Don't fail if new and old prototype happen to be the same.
} }
// Before we can set the prototype we need to be sure // Before we can set the prototype we need to be sure prototype cycles are
// prototype cycles are prevented. // prevented. It is sufficient to validate that the receiver is not in the
// It is sufficient to validate that the receiver is not in the new prototype // new prototype chain.
// chain.
for (PrototypeIterator iter(isolate, *value, for (PrototypeIterator iter(isolate, *value,
PrototypeIterator::START_AT_RECEIVER); PrototypeIterator::START_AT_RECEIVER);
!iter.IsAtEnd(); iter.Advance()) { !iter.IsAtEnd(); iter.Advance()) {
...@@ -14030,30 +14048,7 @@ Maybe<bool> JSObject::SetPrototypeUnobserved(Handle<JSObject> object, ...@@ -14030,30 +14048,7 @@ Maybe<bool> JSObject::SetPrototypeUnobserved(Handle<JSObject> object,
} }
} }
bool dictionary_elements_in_chain =
object->map()->DictionaryElementsInPrototypeChainOnly();
Handle<JSObject> real_receiver = object;
if (from_javascript) {
// Find the first object in the chain whose prototype object is not
// hidden and set the new prototype on that object.
PrototypeIterator iter(isolate, real_receiver);
while (!iter.IsAtEnd(PrototypeIterator::END_AT_NON_HIDDEN)) {
real_receiver = PrototypeIterator::GetCurrent<JSObject>(iter);
iter.Advance();
if (!real_receiver->map()->is_extensible()) {
RETURN_FAILURE(
isolate, should_throw,
NewTypeError(MessageTemplate::kNonExtensibleProto, object));
}
}
}
// Set the new prototype of the object. // Set the new prototype of the object.
Handle<Map> map(real_receiver->map());
// Nothing to do if prototype is already set.
if (map->prototype() == *value) return Just(true);
isolate->UpdateArrayProtectorOnSetPrototype(real_receiver); isolate->UpdateArrayProtectorOnSetPrototype(real_receiver);
......
...@@ -136,6 +136,9 @@ function TestSetPrototypeOfNonExtensibleObject() { ...@@ -136,6 +136,9 @@ function TestSetPrototypeOfNonExtensibleObject() {
for (var i = 0; i < objects.length; i++) { for (var i = 0; i < objects.length; i++) {
var object = objects[i]; var object = objects[i];
Object.preventExtensions(object); Object.preventExtensions(object);
// Setting the current prototype must succeed.
assertTrue(Reflect.setPrototypeOf(object, Object.getPrototypeOf(object)));
// Setting any other must fail.
assertFalse(Reflect.setPrototypeOf(object, proto)); assertFalse(Reflect.setPrototypeOf(object, proto));
} }
} }
......
...@@ -131,6 +131,9 @@ function TestSetPrototypeOfNonExtensibleObject() { ...@@ -131,6 +131,9 @@ function TestSetPrototypeOfNonExtensibleObject() {
for (var i = 0; i < objects.length; i++) { for (var i = 0; i < objects.length; i++) {
var object = objects[i]; var object = objects[i];
Object.preventExtensions(object); Object.preventExtensions(object);
// Setting the current prototype must succeed.
Object.setPrototypeOf(object, Object.getPrototypeOf(object));
// Setting any other must fail.
assertThrows(function() { assertThrows(function() {
Object.setPrototypeOf(object, proto); Object.setPrototypeOf(object, proto);
}, TypeError); }, TypeError);
......
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