Bug 1169740 - Implement a TDZ-like behavior for |this| in derived class constructors. (r=jandem, r=jorendorff, inputs on nit resoulution from Waldo)
authorEric Faust <efaustbmo@mozilla.com>
Thu, 08 Oct 2015 17:01:48 -0700
changeset 300397 a59b5b0139b4ceb73edba874d64a07af3430c251
parent 300396 a71d7c5dc7bb1549fd0fb720ec8e6cdae1941b27
child 300398 cd25dbf77e5720c5895c330e015270617066ebff
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, jorendorff, inputs
bugs1169740
milestone44.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 1169740 - Implement a TDZ-like behavior for |this| in derived class constructors. (r=jandem, r=jorendorff, inputs on nit resoulution from Waldo)
js/src/frontend/BytecodeEmitter.cpp
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineJIT.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/Ion.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonTypes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/js.msg
js/src/jsfun.h
js/src/tests/ecma_6/Class/classHeritage.js
js/src/tests/ecma_6/Class/derivedConstructorDisabled.js
js/src/tests/ecma_6/Class/derivedConstructorInlining.js
js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js
js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js
js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js
js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js
js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js
js/src/tests/ecma_6/Class/superPropBasicGetter.js
js/src/tests/ecma_6/Class/superPropBasicNew.js
js/src/tests/ecma_6/Class/superPropChains.js
js/src/tests/ecma_6/Class/superPropDelete.js
js/src/tests/ecma_6/Class/superPropDerivedCalls.js
js/src/tests/ecma_6/Class/superPropDestructuring.js
js/src/tests/ecma_6/Class/superPropHomeObject.js
js/src/tests/ecma_6/Class/superPropOrdering.js
js/src/tests/ecma_6/Class/superPropProtoChanges.js
js/src/tests/ecma_6/Class/superPropProxies.js
js/src/tests/ecma_6/Class/superPropSkips.js
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1929,25 +1929,30 @@ BytecodeEmitter::checkSideEffects(ParseN
       // Trivial cases with no side effects.
       case PNK_NOP:
       case PNK_STRING:
       case PNK_TEMPLATE_STRING:
       case PNK_REGEXP:
       case PNK_TRUE:
       case PNK_FALSE:
       case PNK_NULL:
-      case PNK_THIS:
       case PNK_ELISION:
       case PNK_GENERATOR:
       case PNK_NUMBER:
       case PNK_OBJECT_PROPERTY_NAME:
         MOZ_ASSERT(pn->isArity(PN_NULLARY));
         *answer = false;
         return true;
 
+      // |this| can throw in derived class constructors.
+      case PNK_THIS:
+        MOZ_ASSERT(pn->isArity(PN_NULLARY));
+        *answer = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor();
+        return true;
+
       // Trivial binary nodes with more token pos holders.
       case PNK_NEWTARGET:
         MOZ_ASSERT(pn->isArity(PN_BINARY));
         MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER));
         MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER));
         *answer = false;
         return true;
 
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -975,17 +975,17 @@ InitFromBailout(JSContext* cx, HandleScr
     JitSpew(JitSpew_BaselineBailouts, "      Resuming %s pc offset %d (op %s) (line %d) of %s:%" PRIuSIZE,
                 resumeAfter ? "after" : "at", (int) pcOff, js_CodeName[op],
                 PCToLineNumber(script, pc), script->filename(), script->lineno());
     JitSpew(JitSpew_BaselineBailouts, "      Bailout kind: %s",
             BailoutKindString(bailoutKind));
 #endif
 
     bool pushedNewTarget = op == JSOP_NEW;
-    
+
     // If this was the last inline frame, or we are bailing out to a catch or
     // finally block in this frame, then unpacking is almost done.
     if (!iter.moreFrames() || catchingException) {
         // Last frame, so PC for call to next frame is set to nullptr.
         *callPC = nullptr;
 
         // If the bailout was a resumeAfter, and the opcode is monitored,
         // then the bailed out state should be in a position to enter
@@ -1872,16 +1872,18 @@ jit::FinishBailoutToBaseline(BaselineBai
       case Bailout_NonBooleanInput:
       case Bailout_NonObjectInput:
       case Bailout_NonStringInput:
       case Bailout_NonSymbolInput:
       case Bailout_NonSimdInt32x4Input:
       case Bailout_NonSimdFloat32x4Input:
       case Bailout_InitialState:
       case Bailout_Debugger:
+      case Bailout_UninitializedThis:
+      case Bailout_BadDerivedConstructorReturn:
         // Do nothing.
         break;
 
       // Invalid assumption based on baseline code.
       case Bailout_OverflowInvalidate:
       case Bailout_NonStringInputInvalidate:
       case Bailout_DoubleOutput:
       case Bailout_ObjectIdentityOrTypeGuard:
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1280,30 +1280,60 @@ BaselineCompiler::emit_JSOP_HOLE()
 
 bool
 BaselineCompiler::emit_JSOP_NULL()
 {
     frame.push(NullValue());
     return true;
 }
 
+typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame);
+static const VMFunction ThrowUninitializedThisInfo =
+    FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis);
+
+bool
+BaselineCompiler::emitCheckThis()
+{
+    frame.assertSyncedStack();
+
+    Label thisOK;
+    masm.branchTestMagic(Assembler::NotEqual, frame.addressOfThis(), &thisOK);
+
+    prepareVMCall();
+
+    masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+    pushArg(R0.scratchReg());
+
+    if (!callVM(ThrowUninitializedThisInfo))
+        return false;
+
+    masm.bind(&thisOK);
+    return true;
+}
+
 bool
 BaselineCompiler::emit_JSOP_THIS()
 {
     if (function() && function()->isArrow()) {
         // Arrow functions store their (lexical) |this| value in an
         // extended slot.
         frame.syncStack(0);
         Register scratch = R0.scratchReg();
         masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), scratch);
         masm.loadValue(Address(scratch, FunctionExtended::offsetOfArrowThisSlot()), R0);
         frame.push(R0);
         return true;
     }
 
+    if (script->isDerivedClassConstructor()) {
+        frame.syncStack(0);
+        if (!emitCheckThis())
+            return false;
+    }
+
     // Keep this value in R0
     frame.pushThis();
 
     // In strict mode code or self-hosted functions, |this| is left alone.
     if (script->strict() || (function() && function()->isSelfHostedBuiltin()))
         return true;
 
     Label skipIC;
@@ -3285,23 +3315,50 @@ BaselineCompiler::emit_JSOP_DEBUGGER()
     {
         masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
         masm.jump(&return_);
     }
     masm.bind(&done);
     return true;
 }
 
+typedef bool (*ThrowBadDerivedReturnFn)(JSContext*, HandleValue);
+static const VMFunction ThrowBadDerivedReturnInfo =
+    FunctionInfo<ThrowBadDerivedReturnFn>(jit::ThrowBadDerivedReturn);
+
 typedef bool (*DebugEpilogueFn)(JSContext*, BaselineFrame*, jsbytecode*);
 static const VMFunction DebugEpilogueInfo =
     FunctionInfo<DebugEpilogueFn>(jit::DebugEpilogueOnBaselineReturn);
 
 bool
 BaselineCompiler::emitReturn()
 {
+    if (script->isDerivedClassConstructor()) {
+        frame.syncStack(0);
+
+        Label derivedDone, returnOK;
+        masm.branchTestObject(Assembler::Equal, JSReturnOperand, &derivedDone);
+        masm.branchTestUndefined(Assembler::Equal, JSReturnOperand, &returnOK);
+
+        // This is going to smash JSReturnOperand, but we don't care, because it's
+        // also going to throw unconditionally.
+        prepareVMCall();
+        pushArg(JSReturnOperand);
+        if (!callVM(ThrowBadDerivedReturnInfo))
+            return false;
+        masm.assumeUnreachable("Should throw on bad derived constructor return");
+
+        masm.bind(&returnOK);
+
+        if (!emitCheckThis())
+            return false;
+
+        masm.bind(&derivedDone);
+    }
+
     if (compileDebugInstrumentation_) {
         // Move return value into the frame's rval slot.
         masm.storeValue(JSReturnOperand, frame.addressOfReturnValue());
         masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags());
 
         // Load BaselineFrame pointer in R0.
         frame.syncStack(0);
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -300,16 +300,17 @@ class BaselineCompiler : public Baseline
     bool emitSpreadCall();
 
     bool emitInitPropGetterSetter();
     bool emitInitElemGetterSetter();
 
     bool emitFormalArgAccess(uint32_t arg, bool get);
 
     bool emitUninitializedLexicalCheck(const ValueOperand& val);
+    bool emitCheckThis();
 
     bool addPCMappingEntry(bool addIndexEntry);
 
     bool addYieldOffset();
 
     void getScopeCoordinateObject(Register reg);
     Address getScopeCoordinateAddressFromObject(Register objReg, Register reg);
     Address getScopeCoordinateAddress(Register reg);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -390,16 +390,18 @@ ICTypeMonitor_Fallback::addMonitorStubFo
 
     if (numOptimizedMonitorStubs_ >= MAX_OPTIMIZED_STUBS) {
         // TODO: if the TypeSet becomes unknown or has the AnyObject type,
         // replace stubs with a single stub to handle these.
         return true;
     }
 
     if (val.isPrimitive()) {
+        if (val.isMagic(JS_UNINITIALIZED_LEXICAL))
+            return true;
         MOZ_ASSERT(!val.isMagic());
         JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType();
 
         // Check for existing TypeMonitor stub.
         ICTypeMonitor_PrimitiveSet* existingStub = nullptr;
         for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) {
             if (iter->isTypeMonitor_PrimitiveSet()) {
                 existingStub = iter->toTypeMonitor_PrimitiveSet();
@@ -498,30 +500,38 @@ ICTypeMonitor_Fallback::addMonitorStubFo
 }
 
 static bool
 DoTypeMonitorFallback(JSContext* cx, BaselineFrame* frame, ICTypeMonitor_Fallback* stub,
                       HandleValue value, MutableHandleValue res)
 {
     // It's possible that we arrived here from bailing out of Ion, and that
     // Ion proved that the value is dead and optimized out. In such cases, do
-    // nothing.
-    if (value.isMagic(JS_OPTIMIZED_OUT)) {
-        res.set(value);
-        return true;
+    // nothing. However, it's also possible that we have an uninitialized this,
+    // in which case we should not look for other magic values.
+    if (stub->monitorsThis()) {
+        MOZ_ASSERT_IF(value.isMagic(), value.isMagic(JS_UNINITIALIZED_LEXICAL));
+    } else {
+        if (value.isMagic(JS_OPTIMIZED_OUT)) {
+            res.set(value);
+            return true;
+        }
     }
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     TypeFallbackICSpew(cx, stub, "TypeMonitor");
 
     uint32_t argument;
     if (stub->monitorsThis()) {
         MOZ_ASSERT(pc == script->code());
-        TypeScript::SetThis(cx, script, value);
+        if (value.isMagic(JS_UNINITIALIZED_LEXICAL))
+            TypeScript::SetThis(cx, script, TypeSet::UnknownType());
+        else
+            TypeScript::SetThis(cx, script, value);
     } else if (stub->monitorsArgument(&argument)) {
         MOZ_ASSERT(pc == script->code());
         TypeScript::SetArgument(cx, script, argument, value);
     } else {
         TypeScript::Monitor(cx, script, pc, value);
     }
 
     if (!stub->addMonitorStubForValue(cx, script, value))
@@ -9629,22 +9639,23 @@ ICCallScriptedCompiler::generateStubCode
             BaseValueIndex calleeSlot2(masm.getStackPointer(), argcReg,
                                        2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t));
             masm.loadValue(calleeSlot2, R1);
         }
         masm.push(masm.extractObject(R1, ExtractTemp0));
         if (!callVM(CreateThisInfoBaseline, masm))
             return false;
 
-        // Return of CreateThis must be an object.
+        // Return of CreateThis must be an object or uninitialized.
 #ifdef DEBUG
-        Label createdThisIsObject;
-        masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisIsObject);
-        masm.assumeUnreachable("The return of CreateThis must be an object.");
-        masm.bind(&createdThisIsObject);
+        Label createdThisOK;
+        masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisOK);
+        masm.branchTestMagic(Assembler::Equal, JSReturnOperand, &createdThisOK);
+        masm.assumeUnreachable("The return of CreateThis must be an object or uninitialized.");
+        masm.bind(&createdThisOK);
 #endif
 
         // Reset the register set from here on in.
         MOZ_ASSERT(JSReturnOperand == R0);
         regs = availableGeneralRegs(0);
         regs.take(R0);
         regs.take(ArgumentsRectifierReg);
         argcReg = regs.takeAny();
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -106,17 +106,18 @@ EnterBaseline(JSContext* cx, EnterJitDat
     }
 
     MOZ_ASSERT(jit::IsBaselineEnabled(cx));
     MOZ_ASSERT_IF(data.osrFrame, CheckFrame(data.osrFrame));
 
     EnterJitCode enter = cx->runtime()->jitRuntime()->enterBaseline();
 
     // Caller must construct |this| before invoking the Ion function.
-    MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject());
+    MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject() ||
+                                     data.maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL));
 
     data.result.setInt32(data.numActualArgs);
     {
         AssertCompartmentUnchanged pcc(cx);
         JitActivation activation(cx, data.calleeToken);
 
         if (data.osrFrame)
             data.osrFrame->setRunningInJit();
@@ -126,19 +127,22 @@ EnterBaseline(JSContext* cx, EnterJitDat
                             data.scopeChain.get(), data.osrNumStackValues, data.result.address());
 
         if (data.osrFrame)
             data.osrFrame->clearRunningInJit();
     }
 
     MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride());
 
-    // Jit callers wrap primitive constructor return.
-    if (!data.result.isMagic() && data.constructing && data.result.isPrimitive())
+    // Jit callers wrap primitive constructor return, except for derived
+    // class constructors, which are forced to do it themselves.
+    if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) {
+        MOZ_ASSERT(data.maxArgv[0].isObject());
         data.result = data.maxArgv[0];
+    }
 
     // Release temporary buffer used for OSR into Ion.
     cx->runtime()->getJitRuntime(cx)->freeOsrTempData();
 
     MOZ_ASSERT_IF(data.result.isMagic(), data.result.isMagic(JS_ION_ERROR));
     return data.result.isMagic() ? JitExec_Error : JitExec_Ok;
 }
 
@@ -206,17 +210,17 @@ jit::EnterBaselineAtBranch(JSContext* cx
         else
             data.calleeToken = CalleeToToken(fp->script());
 
         if (fp->isEvalFrame()) {
             if (!vals.reserve(2))
                 return JitExec_Aborted;
 
             vals.infallibleAppend(thisv);
-            
+
             if (fp->isFunctionFrame())
                 vals.infallibleAppend(fp->newTarget());
             else
                 vals.infallibleAppend(NullValue());
 
             data.maxArgc = 2;
             data.maxArgv = vals.begin();
         }
@@ -301,25 +305,16 @@ CanEnterBaselineJIT(JSContext* cx, Handl
     // script being a debuggee script, e.g., when performing
     // Debugger.Frame.prototype.eval.
     return BaselineCompile(cx, script, osrFrame && osrFrame->isDebuggee());
 }
 
 MethodStatus
 jit::CanEnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, bool newType)
 {
-   // If constructing, allocate a new |this| object.
-   if (fp->isConstructing() && fp->functionThis().isPrimitive()) {
-       RootedObject callee(cx, &fp->callee());
-       RootedObject obj(cx, CreateThisForFunction(cx, callee, newType ? SingletonObject : GenericObject));
-       if (!obj)
-           return Method_Skipped;
-       fp->functionThis().setObject(*obj);
-   }
-
    if (!CheckFrame(fp))
        return Method_CantCompile;
 
    // This check is needed in the following corner case. Consider a function h,
    //
    //   function h(x) {
    //      h(false);
    //      if (!x)
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10320,16 +10320,29 @@ CodeGenerator::visitNewTarget(LNewTarget
     masm.bind(&actualArgsSufficient);
 
     BaseValueIndex newTarget(masm.getStackPointer(), argvLen, frameSize() + JitFrameLayout::offsetOfActualArgs());
     masm.loadValue(newTarget, output);
 
     masm.bind(&done);
 }
 
+void
+CodeGenerator::visitCheckReturn(LCheckReturn* ins)
+{
+    ValueOperand returnValue = ToValue(ins, LCheckReturn::ReturnValue);
+    ValueOperand thisValue = ToValue(ins, LCheckReturn::ThisValue);
+    Label bail, noChecks;
+    masm.branchTestObject(Assembler::Equal, returnValue, &noChecks);
+    masm.branchTestUndefined(Assembler::NotEqual, returnValue, &bail);
+    masm.branchTestMagicValue(Assembler::Equal, thisValue, JS_UNINITIALIZED_LEXICAL, &bail);
+    bailoutFrom(&bail, ins->snapshot());
+    masm.bind(&noChecks);
+}
+
 // Out-of-line math_random_no_outparam call for LRandom.
 class OutOfLineRandom : public OutOfLineCodeBase<CodeGenerator>
 {
     LRandom* lir_;
 
   public:
     explicit OutOfLineRandom(LRandom* lir)
       : lir_(lir)
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -330,16 +330,17 @@ class CodeGenerator : public CodeGenerat
     void visitAsmJSReturn(LAsmJSReturn* ret);
     void visitAsmJSVoidReturn(LAsmJSVoidReturn* ret);
     void visitLexicalCheck(LLexicalCheck* ins);
     void visitThrowUninitializedLexical(LThrowUninitializedLexical* ins);
     void visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins);
     void visitDebugger(LDebugger* ins);
     void visitNewTarget(LNewTarget* ins);
     void visitArrowNewTarget(LArrowNewTarget* ins);
+    void visitCheckReturn(LCheckReturn* ins);
 
     void visitCheckOverRecursed(LCheckOverRecursed* lir);
     void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool);
 
     void visitInterruptCheckImplicit(LInterruptCheckImplicit* ins);
     void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins);
 
     void visitUnboxFloatingPoint(LUnboxFloatingPoint* lir);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2654,32 +2654,37 @@ EnterIon(JSContext* cx, EnterJitData& da
 {
     JS_CHECK_RECURSION(cx, return JitExec_Aborted);
     MOZ_ASSERT(jit::IsIonEnabled(cx));
     MOZ_ASSERT(!data.osrFrame);
 
     EnterJitCode enter = cx->runtime()->jitRuntime()->enterIon();
 
     // Caller must construct |this| before invoking the Ion function.
-    MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject());
+    MOZ_ASSERT_IF(data.constructing,
+                  data.maxArgv[0].isObject() || data.maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL));
 
     data.result.setInt32(data.numActualArgs);
     {
         AssertCompartmentUnchanged pcc(cx);
         JitActivation activation(cx, data.calleeToken);
 
         CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, /* osrFrame = */nullptr, data.calleeToken,
                             /* scopeChain = */ nullptr, 0, data.result.address());
     }
 
     MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride());
 
-    // Jit callers wrap primitive constructor return.
-    if (!data.result.isMagic() && data.constructing && data.result.isPrimitive())
+    // Jit callers wrap primitive constructor return, except for derived class constructors.
+    if (!data.result.isMagic() && data.constructing &&
+        data.result.isPrimitive())
+    {
+        MOZ_ASSERT(data.maxArgv[0].isObject());
         data.result = data.maxArgv[0];
+    }
 
     // Release temporary buffer used for OSR into Ion.
     cx->runtime()->getJitRuntime(cx)->freeOsrTempData();
 
     MOZ_ASSERT_IF(data.result.isMagic(), data.result.isMagic(JS_ION_ERROR));
     return data.result.isMagic() ? JitExec_Error : JitExec_Ok;
 }
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -4464,16 +4464,25 @@ IonBuilder::processReturn(JSOp op)
         def = current->getSlot(info().returnValueSlot());
         break;
 
       default:
         def = nullptr;
         MOZ_CRASH("unknown return op");
     }
 
+    if (script()->isDerivedClassConstructor() &&
+        def->type() != MIRType_Object)
+    {
+        MOZ_ASSERT(info().funMaybeLazy() && info().funMaybeLazy()->isClassConstructor());
+        MCheckReturn* checkRet = MCheckReturn::New(alloc(), def, current->getSlot(info().thisSlot()));
+        current->add(checkRet);
+        def = checkRet;
+    }
+
     MReturn* ret = MReturn::New(alloc(), def);
     current->end(ret);
 
     if (!graph().addReturn(current))
         return ControlStatus_Error;
 
     // Make sure no one tries to use this block now.
     setCurrent(nullptr);
@@ -6198,16 +6207,21 @@ IonBuilder::createThis(JSFunction* targe
         if (!target->isConstructor())
             return nullptr;
 
         MConstant* magic = MConstant::New(alloc(), MagicValue(JS_IS_CONSTRUCTING));
         current->add(magic);
         return magic;
     }
 
+    if (target->isDerivedClassConstructor()) {
+        MOZ_ASSERT(target->isClassConstructor());
+        return constant(MagicValue(JS_UNINITIALIZED_LEXICAL));
+    }
+
     // Try baking in the prototype.
     if (MDefinition* createThis = createThisScriptedSingleton(target, callee))
         return createThis;
 
     if (MDefinition* createThis = createThisScriptedBaseline(callee))
         return createThis;
 
     return createThisScripted(callee);
@@ -12744,19 +12758,31 @@ IonBuilder::jsop_this()
         MLoadArrowThis* thisObj = MLoadArrowThis::New(alloc(), getCallee());
         current->add(thisObj);
         current->push(thisObj);
         return true;
     }
 
     if (script()->strict() || info().funMaybeLazy()->isSelfHostedBuiltin()) {
         // No need to wrap primitive |this| in strict mode or self-hosted code.
-        current->pushSlot(info().thisSlot());
-        return true;
-    }
+        MDefinition* thisVal = current->getSlot(info().thisSlot());
+        if (script()->isDerivedClassConstructor()) {
+            MOZ_ASSERT(info().funMaybeLazy()->isClassConstructor());
+            MOZ_ASSERT(script()->strict());
+
+            MLexicalCheck* checkThis = MLexicalCheck::New(alloc(), thisVal, Bailout_UninitializedThis);
+            current->add(checkThis);
+            thisVal = checkThis;
+        }
+
+        current->push(thisVal);
+        return true;
+    }
+
+    MOZ_ASSERT(!info().funMaybeLazy()->isClassConstructor());
 
     if (thisTypes && (thisTypes->getKnownMIRType() == MIRType_Object ||
         (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject())))
     {
         // This is safe, because if the entry type of |this| is an object, it
         // will necessarily be an object throughout the entire function. OSR
         // can introduce a phi, but this phi will be specialized.
         current->pushSlot(info().thisSlot());
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -106,19 +106,24 @@ enum BailoutKind
     Bailout_NonSimdFloat32x4Input,
 
     // For the initial snapshot when entering a function.
     Bailout_InitialState,
 
     // We hit a |debugger;| statement.
     Bailout_Debugger,
 
+    // |this| used uninitialized in a derived constructor
+    Bailout_UninitializedThis,
+
+    // Derived constructors must return object or undefined
+    Bailout_BadDerivedConstructorReturn,
+
     // END Normal bailouts
 
-
     // Bailouts caused by invalid assumptions based on Baseline code.
     // Causes immediate invalidation.
 
     // Like Bailout_Overflow, but causes immediate invalidation.
     Bailout_OverflowInvalidate,
 
     // Like NonStringInput, but should cause immediate invalidation.
     // Used for jsop_iternext.
@@ -146,17 +151,17 @@ enum BailoutKind
     // (We saw an object whose shape does not match that / any of those observed
     // by the baseline IC.)
     Bailout_ShapeGuard,
 
     // When we're trying to use an uninitialized lexical.
     Bailout_UninitializedLexical,
 
     // A bailout to baseline from Ion on exception to handle Debugger hooks.
-    Bailout_IonExceptionDebugMode,
+    Bailout_IonExceptionDebugMode
 };
 
 inline const char*
 BailoutKindString(BailoutKind kind)
 {
     switch (kind) {
       // Normal bailouts.
       case Bailout_Inevitable:
@@ -204,16 +209,20 @@ BailoutKindString(BailoutKind kind)
       case Bailout_NonSimdInt32x4Input:
         return "Bailout_NonSimdInt32x4Input";
       case Bailout_NonSimdFloat32x4Input:
         return "Bailout_NonSimdFloat32x4Input";
       case Bailout_InitialState:
         return "Bailout_InitialState";
       case Bailout_Debugger:
         return "Bailout_Debugger";
+      case Bailout_UninitializedThis:
+        return "Bailout_UninitializedThis";
+      case Bailout_BadDerivedConstructorReturn:
+        return "Bailout_BadDerivedConstructorReturn";
 
       // Bailouts caused by invalid assumptions.
       case Bailout_OverflowInvalidate:
         return "Bailout_OverflowInvalidate";
       case Bailout_NonStringInputInvalidate:
         return "Bailout_NonStringInputInvalidate";
       case Bailout_DoubleOutput:
         return "Bailout_DoubleOutput";
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4253,17 +4253,17 @@ LIRGenerator::visitSimdShift(MSimdShift*
 
 void
 LIRGenerator::visitLexicalCheck(MLexicalCheck* ins)
 {
     MDefinition* input = ins->input();
     MOZ_ASSERT(input->type() == MIRType_Value);
     LLexicalCheck* lir = new(alloc()) LLexicalCheck();
     useBox(lir, LLexicalCheck::Input, input);
-    assignSnapshot(lir, Bailout_UninitializedLexical);
+    assignSnapshot(lir, ins->bailoutKind());
     add(lir, ins);
     redefine(ins, input);
 }
 
 void
 LIRGenerator::visitThrowUninitializedLexical(MThrowUninitializedLexical* ins)
 {
     LThrowUninitializedLexical* lir = new(alloc()) LThrowUninitializedLexical();
@@ -4288,16 +4288,32 @@ LIRGenerator::visitDebugger(MDebugger* i
 }
 
 void
 LIRGenerator::visitAtomicIsLockFree(MAtomicIsLockFree* ins)
 {
     define(new(alloc()) LAtomicIsLockFree(useRegister(ins->input())), ins);
 }
 
+void
+LIRGenerator::visitCheckReturn(MCheckReturn* ins)
+{
+    MDefinition* retVal = ins->returnValue();
+    MDefinition* thisVal = ins->thisValue();
+    MOZ_ASSERT(retVal->type() == MIRType_Value);
+    MOZ_ASSERT(thisVal->type() == MIRType_Value);
+
+    LCheckReturn* lir = new(alloc()) LCheckReturn();
+    useBoxAtStart(lir, LCheckReturn::ReturnValue, retVal);
+    useBoxAtStart(lir, LCheckReturn::ThisValue, thisVal);
+    assignSnapshot(lir, Bailout_BadDerivedConstructorReturn);
+    add(lir, ins);
+    redefine(ins, retVal);
+}
+
 static void
 SpewResumePoint(MBasicBlock* block, MInstruction* ins, MResumePoint* resumePoint)
 {
     Fprinter& out = JitSpewPrinter();
     out.printf("Current resume point %p details:\n", (void*)resumePoint);
     out.printf("    frame count: %u\n", resumePoint->frameCount());
 
     if (ins) {
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -301,14 +301,15 @@ class LIRGenerator : public LIRGenerator
     void visitUnknownValue(MUnknownValue* ins);
     void visitLexicalCheck(MLexicalCheck* ins);
     void visitThrowUninitializedLexical(MThrowUninitializedLexical* ins);
     void visitGlobalNameConflictsCheck(MGlobalNameConflictsCheck* ins);
     void visitDebugger(MDebugger* ins);
     void visitNewTarget(MNewTarget* ins);
     void visitArrowNewTarget(MArrowNewTarget* ins);
     void visitAtomicIsLockFree(MAtomicIsLockFree* ins);
+    void visitCheckReturn(MCheckReturn* ins);
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_Lowering_h */
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -7225,40 +7225,47 @@ class MAsmJSInterruptCheck
 };
 
 // Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving
 // it to baseline to throw at the correct pc.
 class MLexicalCheck
   : public MUnaryInstruction,
     public BoxPolicy<0>::Data
 {
-    explicit MLexicalCheck(MDefinition* input)
-      : MUnaryInstruction(input)
+    BailoutKind kind_;
+    explicit MLexicalCheck(MDefinition* input, BailoutKind kind)
+      : MUnaryInstruction(input),
+        kind_(kind)
     {
         setResultType(MIRType_Value);
         setResultTypeSet(input->resultTypeSet());
         setMovable();
         setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(LexicalCheck)
 
-    static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input) {
-        return new(alloc) MLexicalCheck(input);
+    static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input,
+                              BailoutKind kind = Bailout_UninitializedLexical) {
+        return new(alloc) MLexicalCheck(input, kind);
     }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 
     MDefinition* input() const {
         return getOperand(0);
     }
 
+    BailoutKind bailoutKind() const {
+        return kind_;
+    }
+
     bool congruentTo(const MDefinition* ins) const override {
         return congruentIfOperandsEqual(ins);
     }
 };
 
 // Unconditionally throw an uninitialized let error.
 class MThrowUninitializedLexical : public MNullaryInstruction
 {
@@ -12931,16 +12938,43 @@ class MHasClass
         if (!ins->isHasClass())
             return false;
         if (getClass() != ins->toHasClass()->getClass())
             return false;
         return congruentIfOperandsEqual(ins);
     }
 };
 
+class MCheckReturn
+  : public MBinaryInstruction,
+    public BoxInputsPolicy::Data
+{
+    explicit MCheckReturn(MDefinition* retVal, MDefinition* thisVal)
+      : MBinaryInstruction(retVal, thisVal)
+    {
+        setGuard();
+        setResultType(MIRType_Value);
+        setResultTypeSet(retVal->resultTypeSet());
+    }
+
+  public:
+    INSTRUCTION_HEADER(CheckReturn)
+
+    static MCheckReturn* New(TempAllocator& alloc, MDefinition* retVal, MDefinition* thisVal) {
+        return new (alloc) MCheckReturn(retVal, thisVal);
+    }
+
+    MDefinition* returnValue() const {
+        return getOperand(0);
+    }
+    MDefinition* thisValue() const {
+        return getOperand(1);
+    }
+};
+
 // Increase the warm-up counter of the provided script upon execution and test if
 // the warm-up counter surpasses the threshold. Upon hit it will recompile the
 // outermost script (i.e. not the inlined script).
 class MRecompileCheck : public MNullaryInstruction
 {
   public:
     enum RecompileCheckType {
         RecompileCheck_OptimizationLevel,
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -272,17 +272,18 @@ namespace jit {
     _(AsmJSAtomicExchangeHeap)                                              \
     _(AsmJSAtomicBinopHeap)                                                 \
     _(UnknownValue)                                                         \
     _(LexicalCheck)                                                         \
     _(ThrowUninitializedLexical)                                            \
     _(GlobalNameConflictsCheck)                                             \
     _(Debugger)                                                             \
     _(NewTarget)                                                            \
-    _(ArrowNewTarget)
+    _(ArrowNewTarget)                                                       \
+    _(CheckReturn)
 
 // Forward declarations of MIR types.
 #define FORWARD_DECLARE(op) class M##op;
  MIR_OPCODE_LIST(FORWARD_DECLARE)
 #undef FORWARD_DECLARE
 
 class MDefinitionVisitor // interface i.e. pure abstract class
 {
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -570,20 +570,25 @@ CreateThis(JSContext* cx, HandleObject c
     rval.set(MagicValue(JS_IS_CONSTRUCTING));
 
     if (callee->is<JSFunction>()) {
         JSFunction* fun = &callee->as<JSFunction>();
         if (fun->isInterpreted() && fun->isConstructor()) {
             JSScript* script = fun->getOrCreateScript(cx);
             if (!script || !script->ensureHasTypes(cx))
                 return false;
-            JSObject* thisObj = CreateThisForFunction(cx, callee, GenericObject);
-            if (!thisObj)
-                return false;
-            rval.set(ObjectValue(*thisObj));
+            if (script->isDerivedClassConstructor()) {
+                MOZ_ASSERT(fun->isClassConstructor());
+                rval.set(MagicValue(JS_UNINITIALIZED_LEXICAL));
+            } else {
+                JSObject* thisObj = CreateThisForFunction(cx, callee, GenericObject);
+                if (!thisObj)
+                    return false;
+                rval.set(ObjectValue(*thisObj));
+            }
         }
     }
 
     return true;
 }
 
 void
 GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp)
@@ -1286,10 +1291,23 @@ bool
 ThrowUninitializedLexical(JSContext* cx)
 {
     ScriptFrameIter iter(cx);
     RootedScript script(cx, iter.script());
     ReportUninitializedLexical(cx, script, iter.pc());
     return false;
 }
 
+bool
+ThrowBadDerivedReturn(JSContext* cx, HandleValue v)
+{
+    ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, v, nullptr);
+    return false;
+}
+
+bool
+BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame)
+{
+    return ThrowUninitializedThis(cx, frame);
+}
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -729,13 +729,15 @@ IonMarkFunction(MIRType type)
         return JS_FUNC_TO_DATA_PTR(void*, MarkObjectGroupFromIon);
       default: MOZ_CRASH();
     }
 }
 
 bool ObjectIsCallable(JSObject* obj);
 
 bool ThrowUninitializedLexical(JSContext* cx);
+bool BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame);
+bool ThrowBadDerivedReturn(JSContext* cx, HandleValue v);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_VMFunctions_h */
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -7242,12 +7242,21 @@ class LRandom : public LInstructionHelpe
     }
 #endif
 
     MRandom* mir() const {
         return mir_->toRandom();
     }
 };
 
+class LCheckReturn : public LCallInstructionHelper<BOX_PIECES, 2 * BOX_PIECES, 0>
+{
+  public:
+    static const size_t ReturnValue = 0;
+    static const size_t ThisValue = BOX_PIECES;
+
+    LIR_HEADER(CheckReturn)
+};
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_shared_LIR_shared_h */
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -363,11 +363,12 @@
     _(AssertRangeV)                 \
     _(AssertResultV)                \
     _(AssertResultT)                \
     _(LexicalCheck)                 \
     _(ThrowUninitializedLexical)    \
     _(GlobalNameConflictsCheck)     \
     _(Debugger)                     \
     _(NewTarget)                    \
-    _(ArrowNewTarget)
+    _(ArrowNewTarget)               \
+    _(CheckReturn)
 
 #endif /* jit_shared_LOpcodes_shared_h */
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -101,16 +101,18 @@ MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0
 MSG_DEF(JSMSG_CANT_SET_PROTO,          0, JSEXN_TYPEERR, "can't set prototype of this object")
 MSG_DEF(JSMSG_CANT_SET_PROTO_OF,       1, JSEXN_TYPEERR, "can't set prototype of {0}")
 MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE,    0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle")
 MSG_DEF(JSMSG_INVALID_ARG_TYPE,        3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
 MSG_DEF(JSMSG_TERMINATED,              1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
 MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL,     1, JSEXN_TYPEERR, "{0}.prototype is not an object or null")
 MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|")
 MSG_DEF(JSMSG_DISABLED_DERIVED_CLASS,  1, JSEXN_INTERNALERR, "{0} temporarily disallowed in derived class constructors")
+MSG_DEF(JSMSG_UNINITIALIZED_THIS,      1, JSEXN_REFERENCEERR, "|this| used uninitialized in {0} class constructor")
+MSG_DEF(JSMSG_BAD_DERIVED_RETURN,      1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}")
 
 // JSON
 MSG_DEF(JSMSG_JSON_BAD_PARSE,          3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")
 MSG_DEF(JSMSG_JSON_CYCLIC_VALUE,       1, JSEXN_TYPEERR, "cyclic {0} value")
 
 // Runtime errors
 MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS,      1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}")
 MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS,     0, JSEXN_REFERENCEERR, "invalid assignment left-hand side")
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -511,16 +511,26 @@ class JSFunction : public js::NativeObje
         return u.n.jitinfo;
     }
 
     void setJitInfo(const JSJitInfo* data) {
         MOZ_ASSERT(isNative());
         u.n.jitinfo = data;
     }
 
+    bool isDerivedClassConstructor() {
+        bool derived;
+        if (isInterpretedLazy())
+            derived = lazyScript()->isDerivedClassConstructor();
+        else
+            derived = nonLazyScript()->isDerivedClassConstructor();
+        MOZ_ASSERT_IF(derived, isClassConstructor());
+        return derived;
+    }
+
     static unsigned offsetOfNativeOrScript() {
         static_assert(offsetof(U, n.native) == offsetof(U, i.s.script_),
                       "native and script pointers must be in the same spot "
                       "for offsetOfNativeOrScript() have any sense");
         static_assert(offsetof(U, n.native) == offsetof(U, nativeOrScript),
                       "U::nativeOrScript must be at the same offset as "
                       "native");
 
--- a/js/src/tests/ecma_6/Class/classHeritage.js
+++ b/js/src/tests/ecma_6/Class/classHeritage.js
@@ -39,16 +39,18 @@ class derived extends base {
     constructor() { };
     override() { overrideCalled = "derived"; }
 }
 var derivedExpr = class extends base {
     constructor() { };
     override() { overrideCalled = "derived"; }
 };
 
+assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
+/*
 // Make sure we get the right object layouts.
 for (let extension of [derived, derivedExpr]) {
     baseMethodCalled = false;
     staticMethodCalled = false;
     overrideCalled = "";
     // Make sure we get the right object layouts.
     assertEq(Object.getPrototypeOf(extension), base);
     assertEq(Object.getPrototypeOf(extension.prototype), base.prototype);
@@ -60,16 +62,17 @@ for (let extension of [derived, derivedE
     // But we can still override them?
     (new extension()).override();
     assertEq(overrideCalled, "derived");
 
     // What about the statics?
     extension.staticMethod();
     assertEq(staticMethodCalled, true);
 }
+*/
 
 // Gotta extend an object, or null.
 function nope() {
     class Foo extends "Bar" {
         constructor() { }
     }
 }
 function nopeExpr() {
--- a/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js
+++ b/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js
@@ -13,25 +13,28 @@ class derived extends base {
     constructor() {
         eval('');
     }
 }
 
 // Make sure eval and arrows are still valid in non-derived constructors.
 new base();
 
+
 // Eval throws in derived class constructors, in both class expressions and
 // statements.
 assertThrowsInstanceOf((() => new derived()), InternalError);
 assertThrowsInstanceOf((() => new class extends base { constructor() { eval('') } }()), InternalError);
 
 var g = newGlobal();
 var dbg = Debugger(g);
 dbg.onDebuggerStatement = function(frame) { assertThrowsInstanceOf(()=>frame.eval(''), InternalError); };
-g.eval("new class foo extends null { constructor() { debugger; } }()");
 
+// Remove the assertion and add super() when super() is implemented!
+assertThrownErrorContains(() => g.eval("new class foo extends null { constructor() { debugger; } }"), "|this|");
+// g.eval("new class foo extends null { constructor() { debugger; } }()");
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorInlining.js
@@ -0,0 +1,19 @@
+// Since we (for now!) can't emit jitcode for derived class statements. Make
+// sure we can corectly invoke derived class constructors.
+
+class foo extends null {
+    constructor() {
+        // Anything that tests |this| should throw, so just let it run off the
+        // end.
+    }
+}
+
+function intermediate() {
+    new foo();
+}
+
+for (let i = 0; i < 1100; i++)
+    assertThrownErrorContains(intermediate, "|this|");
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js
@@ -0,0 +1,15 @@
+class foo extends null {
+    constructor() {
+        // Returning a primitive is a TypeError in derived constructors. This
+        // ensures that super() can take the return value directly, without
+        // checking it. Use |null| here, as a tricky check to make sure we
+        // didn't lump it in with the object check, somehow.
+        return null;
+    }
+}
+
+for (let i = 0; i < 1100; i++)
+    assertThrownErrorContains(() => new foo(), "return");
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js
@@ -0,0 +1,18 @@
+function pleaseRunMyCode() { }
+
+class foo extends null {
+    constructor() {
+        // Just bareword |this| is DCEd by the BytecodeEmitter. Your guess as
+        // to why we think this is a good idea is as good as mine. In order to
+        // combat this inanity, make it a function arg, so we have to compute
+        // it.
+        pleaseRunMyCode(this);
+        assertEq(false, true);
+    }
+}
+
+for (let i = 0; i < 1100; i++)
+    assertThrownErrorContains(() => new foo(), "|this|");
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js
@@ -0,0 +1,11 @@
+class foo extends null {
+    constructor() {
+        // Let it fall off the edge and throw.
+    }
+}
+
+for (let i = 0; i < 1100; i++)
+    assertThrownErrorContains(() => new foo(), "|this|");
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js
@@ -0,0 +1,13 @@
+class foo extends null {
+    constructor() {
+        // If you return an object, we don't care that |this| went
+        // uninitialized
+        return {};
+    }
+}
+
+for (let i = 0; i < 1100; i++)
+    new foo();
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js
@@ -0,0 +1,13 @@
+class foo extends null {
+    constructor() {
+        // Explicit returns of undefined should act the same as falling off the
+        // end of the function. That is to say, they should throw.
+        return undefined;
+    }
+}
+
+for (let i = 0; i < 1100; i++)
+    assertThrownErrorContains(() => new foo(), "|this|");
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropBasicGetter.js
+++ b/js/src/tests/ecma_6/Class/superPropBasicGetter.js
@@ -26,18 +26,21 @@ class derived extends base {
         assertEq(this.a, 15);
 
         assertEq(this.b, 15);
         this.b = 30;
         assertEq(this.b, 30);
     }
 }
 
+assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
+/*
 var derivedInstance = new derived();
 derivedInstance.test();
+*/
 
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropBasicNew.js
+++ b/js/src/tests/ecma_6/Class/superPropBasicNew.js
@@ -5,19 +5,22 @@ class Base {
 }
 class Mid extends Base {
     constructor() {}
     f() { return new super.constructor(); }
 }
 class Derived extends Mid {
     constructor() {}
 }
+
+assertThrowsInstanceOf(()=>new Derived(), TypeError, "You implemented |super()|?!");
+/*
 let d = new Derived();
 var df = d.f();
 assertEq(df.constructor, Base);
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropChains.js
+++ b/js/src/tests/ecma_6/Class/superPropChains.js
@@ -20,16 +20,18 @@ class derived extends middle {
     constructor() { }
     testChain() {
         super.testChain();
         assertEq(this.middleCalled, true);
         assertEq(this.baseCalled, true);
     }
 }
 
+assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
+/*
 new derived().testChain();
 
 // Super even chains in a wellbehaved fashion with normal functions.
 function bootlegMiddle() { }
 bootlegMiddle.prototype = middle.prototype;
 
 new class extends bootlegMiddle {
         constructor() { }
@@ -50,16 +52,16 @@ for (let i = 0; i < CHAIN_LENGTH; i++)
     chain = class extends chain { constructor() { } }
 
 // Now we poke the chain
 let inst = new chain();
 inst.testChain();
 assertEq(inst.baseCalled, true);
 
 assertEq(inst.x, "yeehaw");
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropDelete.js
+++ b/js/src/tests/ecma_6/Class/superPropDelete.js
@@ -17,16 +17,19 @@ class derived extends base {
     }
     testDeleteElemPropValFirst() {
         // The deletion error is a reference error, but by munging the prototype
         // chain, we can force a typeerror from JSOP_SUPERBASE
         delete super[Object.setPrototypeOf(derived.prototype, null)];
     }
 }
 
+assertThrowsInstanceOf(()=> new derived(), TypeError, "You implemented |super()|?!");
+
+/*
 var d = new derived();
 assertThrowsInstanceOf(() => d.testDeleteProp(), ReferenceError);
 d.testDeleteElem();
 assertThrowsInstanceOf(() => d.testDeleteElemPropValFirst(), TypeError);
 
 // |delete super.x| does not delete anything before throwing.
 var thing1 = {
     go() { delete super.toString; }
@@ -38,16 +41,16 @@ assertEq(Object.prototype.toString, save
 // |delete super.x| does not tell the prototype to delete anything, when it's a proxy.
 var thing2 = {
     go() { delete super.prop; }
 };
 Object.setPrototypeOf(thing2, new Proxy({}, {
     deleteProperty(x) { throw "FAIL"; }
 }));
 assertThrowsInstanceOf(() => thing2.go(), ReferenceError);
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropDerivedCalls.js
+++ b/js/src/tests/ecma_6/Class/superPropDerivedCalls.js
@@ -65,20 +65,22 @@ class derived extends base {
         assertEq(this.methodCalled, true);
         assertEq(this.getterCalled, true);
         assertEq(this.setterCalled, true);
         assertEq(this._prop, 1);
     }
 
 }
 
+assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
+/*
 derivedInstance = new derived();
 derivedInstance.test();
 derivedInstance.testInEval();
 derivedInstance.testInArrow();
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropDestructuring.js
+++ b/js/src/tests/ecma_6/Class/superPropDestructuring.js
@@ -32,19 +32,21 @@ class derived extends base {
     }
     testElems() {
         this.prepForTest();
         [super["minutes"], super["intendent"]] = testArr;
         this.testAsserts();
     }
 }
 
+assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
+/*
 let d = new derived();
 d.testProps();
 d.testElems();
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropHomeObject.js
+++ b/js/src/tests/ecma_6/Class/superPropHomeObject.js
@@ -15,16 +15,18 @@ class base {
 
 class derived extends base {
     constructor() { }
     test(expected) { super.test(expected); }
     testArrow() { return (() => super.test(this)); }
     ["testCPN"](expected) { super.test(expected); }
 }
 
+assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
+/*
 let derivedInstance = new derived();
 derivedInstance.test(derivedInstance);
 derivedInstance.testCPN(derivedInstance);
 
 let obj = { test: derivedInstance.test };
 obj.test(obj);
 
 let test = derivedInstance.test;
@@ -54,16 +56,16 @@ class base2 {
 
 let animals = [];
 for (let exprBase of [base1, base2])
     new class extends exprBase {
         constructor() { }
         test() { animals.push(super["test"]()); }
     }().test();
 assertDeepEq(animals, ["llama", "alpaca"]);
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropOrdering.js
+++ b/js/src/tests/ecma_6/Class/superPropOrdering.js
@@ -4,23 +4,23 @@ class base {
     constructor() { }
     method() { this.methodCalled++; }
 }
 
 class derived extends base {
     constructor() { this.methodCalled = 0; }
 
     // Test orderings of various evaluations relative to the superbase
-    
+
     // Unlike in regular element evaluation, the propVal is evaluated before
     // checking the starting object ([[HomeObject]].[[Prototype]])
     testElem() { super[ruin()]; }
-   
+
     // The starting object for looking up super.method is determined before
-    // ruin() is called. 
+    // ruin() is called.
     testProp() { super.method(ruin()); }
 
     // The entire super.method property lookup has concluded before the args
     // are evaluated
     testPropCallDeleted() { super.method(()=>delete base.prototype.method); }
 
     // The starting object for looking up super["prop"] is determined before
     // ruin() is called.
@@ -65,18 +65,19 @@ function ruin() {
     Object.setPrototypeOf(derived.prototype, null);
     return 5;
 }
 
 function reset() {
     Object.setPrototypeOf(derived.prototype, base.prototype);
 }
 
+assertThrowsInstanceOf(() => new derived(), TypeError, "You implemented |super()|?!");
+/*
 let instance = new derived();
-
 assertThrowsInstanceOf(() => instance.testElem(), TypeError);
 reset();
 
 instance.testProp();
 assertEq(instance.methodCalled, 1);
 reset();
 
 instance.testPropCallDeleted();
@@ -86,15 +87,16 @@ instance.testElemAssign();
 assertEq(instance.prop, 5);
 reset();
 
 instance.testAssignElemPropValChange();
 
 instance.testAssignProp();
 
 instance.testCompoundAssignProp();
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropProtoChanges.js
+++ b/js/src/tests/ecma_6/Class/superPropProtoChanges.js
@@ -13,17 +13,18 @@ class derived extends base {
     constructor() { }
     test() {
         assertEq(super.test(), false);
         Object.setPrototypeOf(derived.prototype, standin);
         assertEq(super["test"](), true);
     }
 }
 
-new derived().test();
+// This shouldn't throw, but we don't have |super()| yet.
+assertThrowsInstanceOf(() => new derived().test(), TypeError);
 
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropProxies.js
+++ b/js/src/tests/ecma_6/Class/superPropProxies.js
@@ -25,17 +25,19 @@ class child extends mid {
     static testStaticLookups() {
         // This funtion is called more than once.
         this.called = false;
         super.prop;
         assertEq(this.called, true);
     }
 }
 
+assertThrowsInstanceOf(()=> new mid(), TypeError, "You implemented |super()|?!");
 
+/*
 let midInstance = new mid();
 
 // Make sure proxies are searched properly on the prototype chain
 let baseHandler = {
     get(target, p, receiver) {
         assertEq(receiver, midInstance);
         getterCalled = true;
         return "found";
@@ -76,16 +78,16 @@ var g = newGlobal();
 var wrappedSuper = g.eval("({ method() { return super.hasOwnProperty('method'); } })");
 assertEq(wrappedSuper.method(), true);
 
 // With a CCW on the proto chain?
 var wrappedBase = g.eval("({ method() { return this.__secretProp__; } })");
 var unwrappedDerived = { __secretProp__: 42, method() { return super.method(); } }
 Object.setPrototypeOf(unwrappedDerived, wrappedBase);
 assertEq(unwrappedDerived.method(), 42);
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropSkips.js
+++ b/js/src/tests/ecma_6/Class/superPropSkips.js
@@ -32,20 +32,23 @@ class derived extends base {
         // skipped in the super lookup.
         assertEq(this.nonWritableProp, "pony");
         super.nonWritableProp = "bear";
         assertEq(this.nonWritableProp, "bear");
     }
 }
 
 Object.defineProperty(derived.prototype, "nonWritableProp", { writable: false, value: "pony" });
+
+assertThrowsInstanceOf(()=> new derived(), TypeError, "You implemented |super()|?!");
+/*
 let instance = new derived();
 instance.testSkipGet();
 instance.testSkipDerivedOverrides();
 instance.testSkipSet();
-
+*/
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -342,21 +342,26 @@ js::ValueToCallable(JSContext* cx, Handl
 
 bool
 RunState::maybeCreateThisForConstructor(JSContext* cx)
 {
     if (isInvoke()) {
         InvokeState& invoke = *asInvoke();
         if (invoke.constructing() && invoke.args().thisv().isPrimitive()) {
             RootedObject callee(cx, &invoke.args().callee());
-            NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject;
-            JSObject* obj = CreateThisForFunction(cx, callee, newKind);
-            if (!obj)
-                return false;
-            invoke.args().setThis(ObjectValue(*obj));
+            if (script()->isDerivedClassConstructor()) {
+                MOZ_ASSERT(callee->as<JSFunction>().isClassConstructor());
+                invoke.args().setThis(MagicValue(JS_UNINITIALIZED_LEXICAL));
+            } else {
+                NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject;
+                JSObject* obj = CreateThisForFunction(cx, callee, newKind);
+                if (!obj)
+                    return false;
+                invoke.args().setThis(ObjectValue(*obj));
+            }
         }
     }
     return true;
 }
 
 static MOZ_NEVER_INLINE bool
 Interpret(JSContext* cx, RunState& state);
 
@@ -2192,18 +2197,22 @@ CASE(JSOP_RETURN)
 CASE(JSOP_RETRVAL)
 {
     /*
      * When the inlined frame exits with an exception or an error, ok will be
      * false after the inline_return label.
      */
     CHECK_BRANCH();
 
+    if (!REGS.fp()->checkReturn(cx))
+        goto error;
+
   successful_return_continuation:
     interpReturnOK = true;
+
   return_continuation:
     if (activation.entryFrame() != REGS.fp()) {
         // Stop the engine. (No details about which engine exactly, could be
         // interpreter, Baseline or IonMonkey.)
         TraceLogStopEvent(logger, TraceLogger_Engine);
         TraceLogStopEvent(logger, TraceLogger_Scripts);
 
         interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), interpReturnOK);
@@ -2770,16 +2779,18 @@ END_CASE(JSOP_TYPEOF)
 
 CASE(JSOP_VOID)
     REGS.sp[-1].setUndefined();
 END_CASE(JSOP_VOID)
 
 CASE(JSOP_THIS)
     if (!ComputeThis(cx, REGS.fp()))
         goto error;
+    if (!REGS.fp()->checkThis(cx))
+        goto error;
     PUSH_COPY(REGS.fp()->thisValue());
 END_CASE(JSOP_THIS)
 
 CASE(JSOP_GETPROP)
 CASE(JSOP_LENGTH)
 CASE(JSOP_CALLPROP)
 {
     MutableHandleValue lval = REGS.stackHandleAt(-1);
@@ -4975,8 +4986,28 @@ js::ReportRuntimeRedeclaration(JSContext
         if (declKind == frontend::Definition::VAR)
             kindStr = "non-configurable global property";
         else
             kindStr = frontend::Definition::kindString(declKind);
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_REDECLARED_VAR,
                              kindStr, printable.ptr());
     }
 }
+
+bool
+js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame)
+{
+    RootedFunction fun(cx, frame.callee());
+
+    MOZ_ASSERT(fun->isClassConstructor());
+    MOZ_ASSERT(fun->nonLazyScript()->isDerivedClassConstructor());
+
+    const char* name = "anonymous";
+    JSAutoByteString str;
+    if (fun->atom()) {
+        if (!AtomToPrintableString(cx, fun->atom(), &str))
+            return false;
+        name = str.ptr();
+    }
+
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_THIS, name);
+    return false;
+}
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -480,12 +480,14 @@ void
 ReportUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc);
 
 // The parser only reports redeclarations that occurs within a single
 // script. Due to the extensibility of the global lexical scope, we also check
 // for redeclarations during runtime in JSOP_DEF{VAR,LET,CONST}.
 void
 ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name,
                            frontend::Definition::Kind declKind);
+bool
+ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame);
 
 }  /* namespace js */
 
 #endif /* vm_Interpreter_h */
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -253,23 +253,28 @@ InterpreterFrame::prologue(JSContext* cx
         pushOnScopeChain(*scope);
         return probes::EnterScript(cx, script, nullptr, this);
     }
 
     MOZ_ASSERT(isNonEvalFunctionFrame());
     if (fun()->needsCallObject() && !initFunctionScopeObjects(cx))
         return false;
 
-    if (isConstructing() && functionThis().isPrimitive()) {
-        RootedObject callee(cx, &this->callee());
-        JSObject* obj = CreateThisForFunction(cx, callee,
-                                              createSingleton() ? SingletonObject : GenericObject);
-        if (!obj)
-            return false;
-        functionThis() = ObjectValue(*obj);
+    if (isConstructing()) {
+        if (script->isDerivedClassConstructor()) {
+            MOZ_ASSERT(callee().isClassConstructor());
+            functionThis() = MagicValue(JS_UNINITIALIZED_LEXICAL);
+        } else if (functionThis().isPrimitive()) {
+            RootedObject callee(cx, &this->callee());
+            JSObject* obj = CreateThisForFunction(cx, callee,
+                                                createSingleton() ? SingletonObject : GenericObject);
+            if (!obj)
+                return false;
+            functionThis() = ObjectValue(*obj);
+        }
     }
 
     return probes::EnterScript(cx, script, script->functionNonDelazifying(), this);
 }
 
 void
 InterpreterFrame::epilogue(JSContext* cx)
 {
@@ -312,16 +317,53 @@ InterpreterFrame::epilogue(JSContext* cx
     if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
         DebugScopes::onPopCall(this, cx);
 
     if (!fun()->isGenerator() && isConstructing() && thisValue().isObject() && returnValue().isPrimitive())
         setReturnValue(ObjectValue(constructorThis()));
 }
 
 bool
+InterpreterFrame::checkThis(JSContext* cx)
+{
+    if (script()->isDerivedClassConstructor()) {
+        MOZ_ASSERT(isNonEvalFunctionFrame());
+        MOZ_ASSERT(fun()->isClassConstructor());
+
+        if (thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)) {
+            RootedFunction func(cx, fun());
+            return ThrowUninitializedThis(cx, this);
+        }
+    }
+    return true;
+}
+
+bool
+InterpreterFrame::checkReturn(JSContext* cx)
+{
+    if (script()->isDerivedClassConstructor()) {
+        MOZ_ASSERT(isNonEvalFunctionFrame());
+        MOZ_ASSERT(callee().isClassConstructor());
+
+        HandleValue retVal = returnValue();
+        if (retVal.isObject())
+            return true;
+
+        if (!retVal.isUndefined()) {
+            ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, retVal, nullptr);
+            return false;
+        }
+
+        if (!checkThis(cx))
+            return false;
+    }
+    return true;
+}
+
+bool
 InterpreterFrame::pushBlock(JSContext* cx, StaticBlockObject& block)
 {
     MOZ_ASSERT(block.needsClone());
 
     Rooted<StaticBlockObject*> blockHandle(cx, &block);
     ClonedBlockObject* clone = ClonedBlockObject::create(cx, blockHandle, this);
     if (!clone)
         return false;
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -447,16 +447,19 @@ class InterpreterFrame
      * over-recursed) after pushing the stack frame but before 'prologue' is
      * called or completes fully. To simplify usage, 'epilogue' does not assume
      * 'prologue' has completed and handles all the intermediate state details.
      */
 
     bool prologue(JSContext* cx);
     void epilogue(JSContext* cx);
 
+    bool checkReturn(JSContext* cx);
+    bool checkThis(JSContext* cx);
+
     bool initFunctionScopeObjects(JSContext* cx);
 
     /*
      * Initialize local variables of newly-pushed frame. 'var' bindings are
      * initialized to undefined and lexical bindings are initialized to
      * JS_UNINITIALIZED_LEXICAL.
      */
     void initLocals();
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,21 +24,21 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 310;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 311;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 415,
+static_assert(JSErr_Limit == 417,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext* cx)