Bug 1184414 - X.[[SetPrototypeOf]](Y) should succeed if X.[[Prototype]] is already Y. r=jorendorff.
authorFlorian Merz <flomerz@gmail.com>
Tue, 28 Jul 2015 07:35:22 +0200
changeset 287967 685014edb54e3421439ef30630932ba9c3e2ab2d
parent 287966 6f97ff142c890acb5f5c722d824315a7f475fa37
child 287968 50dcfa114147b73256dcc5f4bce68fad10d23206
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1184414
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1184414 - X.[[SetPrototypeOf]](Y) should succeed if X.[[Prototype]] is already Y. r=jorendorff.
js/src/jit-test/tests/basic/setPrototypeOf.js
js/src/jsobj.cpp
js/src/jsobj.h
js/src/tests/ecma_6/Reflect/setPrototypeOf.js
--- a/js/src/jit-test/tests/basic/setPrototypeOf.js
+++ b/js/src/jit-test/tests/basic/setPrototypeOf.js
@@ -67,16 +67,23 @@ for (var object of objects) {
 var objects = getObjects();
 var proto = {};
 for (var object of objects) {
     Object.preventExtensions(object);
     assertThrowsInstanceOf(() => Object.setPrototypeOf(object, proto),
         TypeError, "Object.setPrototypeOf should fail when the object is not extensible");
 }
 
+// check if Object.setPrototypeof(A, B) succeeds on not extensible object A if
+// prototype of A == B already
+var objectProto = {};
+var nonExtensibleObject = Object.create(objectProto);
+Object.preventExtensions(nonExtensibleObject);
+assertEq(Object.setPrototypeOf(nonExtensibleObject, objectProto), nonExtensibleObject);
+
 // check if Object.setPrototypeOf works with prototype lookup
 var object = {};
 assertEq('x' in object, false);
 assertEq('y' in object, false);
 
 var oldProto = {
     x: 'old x',
     y: 'old y'
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2437,16 +2437,23 @@ js::SetPrototype(JSContext* cx, HandleOb
      * for flash-related security reasons.
      */
     if (!strcmp(obj->getClass()->name, "Location")) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_SET_PROTO_OF,
                              "incompatible Location object");
         return false;
     }
 
+    /*
+     * ES6 9.1.2 step 3-4 if |obj.[[Prototype]]| has SameValue as |proto| return true.
+     * Since the values in question are objects, we can just compare pointers.
+     */
+    if (proto == obj->getProto())
+        return result.succeed();
+
     /* ES6 9.1.2 step 5 forbids changing [[Prototype]] if not [[Extensible]]. */
     bool extensible;
     if (!IsExtensible(cx, obj, &extensible))
         return false;
     if (!extensible)
         return result.fail(JSMSG_CANT_SET_PROTO);
 
     /*
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -344,34 +344,35 @@ class JSObject : public js::gc::Cell
         return group_;
     }
 
     /*
      * We allow the prototype of an object to be lazily computed if the object
      * is a proxy. In the lazy case, we store (JSObject*)0x1 in the proto field
      * of the object's group. We offer three ways of getting the prototype:
      *
-     * 1. obj->getProto() returns the prototype, but asserts if obj is a proxy.
+     * 1. obj->getProto() returns the prototype, but asserts if obj is a proxy
+     *    with a relevant getPrototype() handler.
      * 2. obj->getTaggedProto() returns a TaggedProto, which can be tested to
      *    check if the proto is an object, nullptr, or lazily computed.
      * 3. js::GetPrototype(cx, obj, &proto) computes the proto of an object.
      *    If obj is a proxy and the proto is lazy, this code may allocate or
      *    GC in order to compute the proto. Currently, it will not run JS code.
      */
 
     js::TaggedProto getTaggedProto() const {
         return group_->proto();
     }
 
     bool hasTenuredProto() const;
 
     bool uninlinedIsProxy() const;
 
     JSObject* getProto() const {
-        MOZ_ASSERT(!uninlinedIsProxy());
+        MOZ_ASSERT(!hasLazyPrototype());
         return getTaggedProto().toObjectOrNull();
     }
 
     // Normal objects and a subset of proxies have uninteresting [[Prototype]].
     // For such objects the [[Prototype]] is just a value returned when needed
     // for accesses, or modified in response to requests.  These objects store
     // the [[Prototype]] directly within |obj->type_|.
     //
--- a/js/src/tests/ecma_6/Reflect/setPrototypeOf.js
+++ b/js/src/tests/ecma_6/Reflect/setPrototypeOf.js
@@ -22,17 +22,17 @@ for (proto of [undefined, false, 0, 1.6,
     assertThrowsInstanceOf(() => Reflect.setPrototypeOf(obj, proto), TypeError);
 }
 
 // Return false if the target is inextensible.
 proto = {};
 obj = Object.preventExtensions(Object.create(proto));
 assertEq(Reflect.setPrototypeOf(obj, {}), false);
 assertEq(Reflect.setPrototypeOf(obj, null), false);
-assertEq(Reflect.setPrototypeOf(obj, proto), false);  // even if not changing anything
+assertEq(Reflect.setPrototypeOf(obj, proto), true);  // except if not changing anything
 
 // Return false rather than create a [[Prototype]] cycle.
 obj = {};
 assertEq(Reflect.setPrototypeOf(obj, obj), false);
 
 // Don't create a [[Prototype]] cycle involving 2 objects.
 obj = Object.create(proto);
 assertEq(Reflect.setPrototypeOf(proto, obj), false);