Bug 1377051 - Support JSOP_SETPROP_SUPER in Baseline draft
authorTed Campbell <tcampbell@mozilla.com>
Thu, 29 Jun 2017 15:02:53 -0400
changeset 602914 c39cd0e73adc3d1dd56e8a7745eb64c54df398a3
parent 602911 587daa4bdc4b40b7053f4ca3b74723ca747f3b52
child 602915 6ed59d9db340ef128d85f2406f37dd3074b6767c
push id66609
push userbmo:tcampbell@mozilla.com
push dateFri, 30 Jun 2017 22:07:36 +0000
bugs1377051
milestone56.0a1
Bug 1377051 - Support JSOP_SETPROP_SUPER in Baseline MozReview-Commit-ID: LNOVCntYDsM
js/src/jit-test/tests/class/superSetPropThrow.js
js/src/jit-test/tests/class/superSetProperty.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/class/superSetPropThrow.js
@@ -0,0 +1,64 @@
+var g_foo = "foo";
+var g_bar = "bar";
+
+// Define base class with a read-only and a writable data property
+class Base
+{
+}
+Object.defineProperty(Base.prototype, "foo", { value: "Base", writable: true });
+Object.defineProperty(Base.prototype, "bar", { value: "Base", writable: false });
+
+// Test various cases that should throw during SETPROP_SUPER
+class Derived extends Base
+{
+    // ECMA-2018 9.1.9.1, step 4.a
+    testReadonly() {
+        super.bar = "Derived";
+    }
+    testReadonlyElem() {
+        super[g_bar] = "Derived";
+    }
+
+    // ECMA-2018 9.1.9.1, step 4.b
+    testPrimitiveReceiver() {
+        super.foo = "Derived";
+    }
+    testPrimitiveReceiverElem() {
+        super[g_foo] = "Derived";
+    }
+
+    // ECMA-2018 9.1.9.1, step 4.d.i
+    testAccessorShadow() {
+        Object.defineProperty(this, "foo", { get: function() { } });
+        super.foo = "Derived";
+    }
+    testAccessorShadowElem() {
+        Object.defineProperty(this, "foo", { get: function() { } });
+        super[g_foo] = "Derived";
+    }
+
+    // ECMA-2018 9.1.9.1, step 4.d.ii
+    testReadonlyShadow() {
+        Object.defineProperty(this, "foo", { writable: false });
+        super.foo = "Derived";
+    }
+    testReadonlyShadowElem() {
+        Object.defineProperty(this, "foo", { writable: false });
+        super[g_foo] = "Derived";
+    }
+}
+
+for (let i = 0; i < 10; ++i) {
+    var cnt = 0;
+
+    try { new Derived().testReadonly(); } catch(e) { cnt++; }
+    try { new Derived().testReadonlyElem(); } catch(e) { cnt++; }
+    try { Derived.prototype.testPrimitiveReceiver.call(null); } catch(e) { cnt++; }
+    try { Derived.prototype.testPrimitiveReceiverElem.call(null); } catch(e) { cnt++; }
+    try { new Derived().testAccessorShadow(); } catch(e) { cnt++; }
+    try { new Derived().testAccessorShadowElem(); } catch(e) { cnt++; }
+    try { new Derived().testReadonlyShadow(); } catch(e) { cnt++; }
+    try { new Derived().testReadonlyShadowElem(); } catch(e) { cnt++; }
+
+    assertEq(cnt, 8);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/class/superSetProperty.js
@@ -0,0 +1,67 @@
+
+class Base
+{
+    set setter(val) {
+        this.set_val = val;
+        this.set_this = this;
+    }
+}
+Base.prototype.prop = "Base";
+
+class Derived extends Base
+{
+    set setter(val) { super.setter = val; }
+    setelem(pname, val) { super[pname] = val; }
+}
+
+// Test SETPROP_SUPER invoke setters correctly
+function testSetterChain() {
+    let d = new Derived();
+
+    for (let i = 0; i < 10; ++i)
+    {
+        d.setter = i;
+        assertEq(d.set_val, i);
+        assertEq(d.set_this, d);
+    }
+}
+function testSetterChainElem() {
+    let d = new Derived();
+
+    for (let i = 0; i < 10; ++i)
+    {
+        d.setelem("setter", i);
+        assertEq(d.set_val, i);
+        assertEq(d.set_this, d);
+    }
+}
+
+// Test that SETPROP_SUPER modifies |this| and not home object
+function testSuperSetProp() {
+    let d = new Derived();
+
+    for (let i = 0; i < 10; ++i)
+    {
+        d.prop = i;
+        assertEq(d.prop, i);
+        assertEq(d.hasOwnProperty("prop"), true);
+        assertEq(Derived.prototype.prop, "Base");
+    }
+}
+function testSuperSetPropElem() {
+    let d = new Derived();
+
+    for (let i = 0; i < 10; ++i)
+    {
+        d.setelem("prop", i);
+        assertEq(d.prop, i);
+        assertEq(d.hasOwnProperty("prop"), true);
+        assertEq(Derived.prototype.prop, "Base");
+    }
+}
+
+testSetterChain();
+testSetterChainElem();
+
+testSuperSetProp();
+testSuperSetPropElem();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2541,16 +2541,56 @@ BaselineCompiler::emit_JSOP_SETGNAME()
 }
 
 bool
 BaselineCompiler::emit_JSOP_STRICTSETGNAME()
 {
     return emit_JSOP_SETPROP();
 }
 
+typedef bool (*SetPropertySuperFn)(JSContext*, HandleObject, HandleValue,
+                                   HandlePropertyName, HandleValue, bool);
+static const VMFunction SetPropertySuperInfo =
+    FunctionInfo<SetPropertySuperFn>(js::SetPropertySuper, "SetPropertySuper");
+
+bool
+BaselineCompiler::emit_JSOP_SETPROP_SUPER()
+{
+    bool strict = IsCheckStrictOp(JSOp(*pc));
+
+    // Incoming stack is |receiver, obj, rval|. We need to shuffle stack to
+    // leave rval when operation is complete.
+
+    // Pop rval into R0, then load receiver into R1 and replace with rval.
+    frame.popRegsAndSync(1);
+    masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1);
+    masm.storeValue(R0, frame.addressOfStackValue(frame.peek(-2)));
+
+    prepareVMCall();
+
+    pushArg(Imm32(strict));
+    pushArg(R0); // rval
+    pushArg(ImmGCPtr(script->getName(pc)));
+    pushArg(R1); // receiver
+    masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg());
+    pushArg(R0.scratchReg()); // obj
+
+    if (!callVM(SetPropertySuperInfo))
+        return false;
+
+    frame.pop();
+    return true;
+}
+
+bool
+BaselineCompiler::emit_JSOP_STRICTSETPROP_SUPER()
+{
+    return emit_JSOP_SETPROP_SUPER();
+}
+
 bool
 BaselineCompiler::emit_JSOP_GETPROP()
 {
     // Keep object in R0.
     frame.popRegsAndSync(1);
 
     // Call IC.
     ICGetProp_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline);
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -133,16 +133,18 @@ namespace jit {
     _(JSOP_STRICTSETNAME)      \
     _(JSOP_GETPROP)            \
     _(JSOP_SETPROP)            \
     _(JSOP_STRICTSETPROP)      \
     _(JSOP_CALLPROP)           \
     _(JSOP_DELPROP)            \
     _(JSOP_STRICTDELPROP)      \
     _(JSOP_GETPROP_SUPER)      \
+    _(JSOP_SETPROP_SUPER)      \
+    _(JSOP_STRICTSETPROP_SUPER) \
     _(JSOP_LENGTH)             \
     _(JSOP_GETBOUNDNAME)       \
     _(JSOP_GETALIASEDVAR)      \
     _(JSOP_SETALIASEDVAR)      \
     _(JSOP_GETNAME)            \
     _(JSOP_BINDNAME)           \
     _(JSOP_DELNAME)            \
     _(JSOP_GETIMPORT)          \
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2863,28 +2863,24 @@ CASE(JSOP_STRICTSETPROP)
 END_CASE(JSOP_SETPROP)
 
 CASE(JSOP_SETPROP_SUPER)
 CASE(JSOP_STRICTSETPROP_SUPER)
 {
     static_assert(JSOP_SETPROP_SUPER_LENGTH == JSOP_STRICTSETPROP_SUPER_LENGTH,
                   "setprop-super and strictsetprop-super must be the same size");
 
-
     ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-3]);
     ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
     ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
-    ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
-
-    ObjectOpResult result;
-    if (!SetProperty(cx, obj, id, rval, receiver, result))
-        goto error;
+    ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
 
     bool strict = JSOp(*REGS.pc) == JSOP_STRICTSETPROP_SUPER;
-    if (!result.checkStrictErrorOrWarning(cx, obj, id, strict))
+
+    if (!SetPropertySuper(cx, obj, receiver, name, rval, strict))
         goto error;
 
     REGS.sp[-3] = REGS.sp[-1];
     REGS.sp -= 2;
 }
 END_CASE(JSOP_SETPROP_SUPER)
 
 CASE(JSOP_GETELEM)
@@ -5252,8 +5248,20 @@ js::SuperFunOperation(JSContext* cx, Han
 }
 
 bool
 js::ThrowInitializedThis(JSContext* cx, AbstractFramePtr frame)
 {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_REINIT_THIS);
     return false;
 }
+
+bool
+js::SetPropertySuper(JSContext* cx, HandleObject obj, HandleValue receiver,
+                     HandlePropertyName name, HandleValue rval, bool strict)
+{
+    RootedId id(cx, NameToId(name));
+    ObjectOpResult result;
+    if (!SetProperty(cx, obj, id, rval, receiver, result))
+        return false;
+
+    return result.checkStrictErrorOrWarning(cx, obj, id, strict);
+}
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -593,11 +593,15 @@ JSFunction*
 MakeDefaultConstructor(JSContext* cx, HandleScript script, jsbytecode* pc, HandleObject proto);
 
 JSObject*
 HomeObjectSuperBase(JSContext* cx, HandleObject homeObj);
 
 JSObject*
 SuperFunOperation(JSContext* cx, HandleObject callee);
 
+bool
+SetPropertySuper(JSContext* cx, HandleObject obj, HandleValue receiver,
+                 HandlePropertyName id, HandleValue rval, bool strict);
+
 }  /* namespace js */
 
 #endif /* vm_Interpreter_h */