Bug 1071646 - Support labelled function declarations in sloppy mode per Annex B.3.2. (r=jorendorff)
authorShu-yu Guo <shu@rfrn.org>
Fri, 18 Dec 2015 13:18:19 -0800
changeset 277073 204aeab8d5f8d21f1ebf0d62776b27bed9476290
parent 277072 001519eae1c86e57b859310a1deaab0a668ff435
child 277074 2335624d41d7975dd55ade1ee3807bce2e29b3b2
push id16724
push usercbook@mozilla.com
push dateMon, 21 Dec 2015 11:00:52 +0000
treeherderfx-team@3f3f0361567c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1071646
milestone46.0a1
Bug 1071646 - Support labelled function declarations in sloppy mode per Annex B.3.2. (r=jorendorff)
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FullParseHandler.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/frontend/SyntaxParseHandler.h
js/src/js.msg
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1367,30 +1367,30 @@ BytecodeEmitter::emitVarIncDec(ParseNode
         return false;
     if (post && !emit1(JSOP_POP))                            // RESULT
         return false;
 
     return true;
 }
 
 bool
-BytecodeEmitter::atBodyLevel() const
+BytecodeEmitter::atBodyLevel(StmtInfoBCE* stmt) const
 {
     // 'eval' and non-syntactic scripts are always under an invisible lexical
     // scope, but since it is not syntactic, it should still be considered at
     // body level.
     if (sc->staticScope()->is<StaticEvalObject>()) {
-        bool bl = !innermostStmt()->enclosing;
-        MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK);
-        MOZ_ASSERT_IF(bl, innermostStmt()->staticScope
-                                         ->as<StaticBlockObject>()
-                                         .enclosingStaticScope() == sc->staticScope());
+        bool bl = !stmt->enclosing;
+        MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
+        MOZ_ASSERT_IF(bl, stmt->staticScope
+                              ->as<StaticBlockObject>()
+                              .enclosingStaticScope() == sc->staticScope());
         return bl;
     }
-    return !innermostStmt() || sc->isModuleBox();
+    return !stmt || sc->isModuleBox();
 }
 
 uint32_t
 BytecodeEmitter::computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut)
 {
     Definition* dn = pn->resolve();
     MOZ_ASSERT(dn->isDefn());
     MOZ_ASSERT(!dn->isPlaceholder());
@@ -3093,21 +3093,33 @@ BytecodeEmitter::emitSwitch(ParseNode* p
         return false;
 
     StmtInfoBCE stmtInfo(cx);
     ptrdiff_t top;
     if (cases->isKind(PNK_LEXICALSCOPE)) {
         if (!enterBlockScope(&stmtInfo, cases->pn_objbox, JSOP_UNINITIALIZED, 0))
             return false;
 
+        // Advance |cases| to refer to the switch case list.
+        cases = cases->expr();
+
+        // A switch statement may contain hoisted functions inside its
+        // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
+        // bodies of the cases to the case list.
+        if (cases->pn_xflags & PNX_FUNCDEFS) {
+            for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
+                if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
+                    if (!emitHoistedFunctionsInList(caseNode->pn_right))
+                        return false;
+                }
+            }
+        }
+
         stmtInfo.type = StmtType::SWITCH;
         stmtInfo.update = top = offset();
-
-        // Advance |cases| to refer to the switch case list.
-        cases = cases->expr();
     } else {
         MOZ_ASSERT(cases->isKind(PNK_STATEMENTLIST));
         top = offset();
         pushStatement(&stmtInfo, StmtType::SWITCH, top);
     }
 
     // Switch bytecodes run from here till end of final case.
     uint32_t caseCount = cases->pn_count;
@@ -5331,16 +5343,21 @@ BytecodeEmitter::emitLetBlock(ParseNode*
 }
 
 bool
 BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list)
 {
     MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS);
 
     for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) {
+        if (!sc->strict()) {
+            while (pn->isKind(PNK_LABEL))
+                pn = pn->as<LabeledStatement>().statement();
+        }
+
         if (pn->isKind(PNK_ANNEXB_FUNCTION) ||
             (pn->isKind(PNK_FUNCTION) && pn->functionIsHoisted()))
         {
             if (!emitTree(pn))
                 return false;
         }
     }
 
@@ -6365,28 +6382,38 @@ BytecodeEmitter::emitFunction(ParseNode*
      *
      * Functions are fully parsed prior to invocation of the emitter and calls
      * to emitTree for function definitions are scheduled before generating
      * the rest of code.
      *
      * For modules, we record the function and instantiate the binding during
      * ModuleDeclarationInstantiation(), before the script is run.
      */
-    if (!atBodyLevel()) {
+
+    // Check for functions that were parsed under labeled statements per ES6
+    // Annex B.3.2.
+    bool blockScopedFunction = !atBodyLevel();
+    if (!sc->strict() && blockScopedFunction) {
+        StmtInfoBCE* stmt = innermostStmt();
+        while (stmt && stmt->type == StmtType::LABEL)
+            stmt = stmt->enclosing;
+        blockScopedFunction = !atBodyLevel(stmt);
+    }
+
+    if (blockScopedFunction) {
         if (!emitIndexOp(JSOP_LAMBDA, index))
             return false;
         MOZ_ASSERT(pn->getOp() == JSOP_INITLEXICAL);
         if (!emitVarOp(pn, pn->getOp()))
             return false;
         if (!emit1(JSOP_POP))
             return false;
     } else if (sc->isGlobalContext()) {
         MOZ_ASSERT(pn->pn_scopecoord.isFree());
         MOZ_ASSERT(pn->getOp() == JSOP_NOP);
-        MOZ_ASSERT(atBodyLevel());
         switchToPrologue();
         if (!emitIndex32(JSOP_DEFFUN, index))
             return false;
         if (!updateSourceCoordNotes(pn->pn_pos.begin))
             return false;
         switchToMain();
     } else if (sc->isFunctionBox()) {
 #ifdef DEBUG
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -244,17 +244,20 @@ struct BytecodeEmitter
 
     StmtInfoBCE* innermostStmt() const { return stmtStack.innermost(); }
     StmtInfoBCE* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
     JSObject* innermostStaticScope() const;
     JSObject* blockScopeOfDef(Definition* dn) const {
         return parser->blockScopes[dn->pn_blockid];
     }
 
-    bool atBodyLevel() const;
+    bool atBodyLevel(StmtInfoBCE* stmt) const;
+    bool atBodyLevel() const {
+        return atBodyLevel(innermostStmt());
+    }
     uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut);
     bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn);
     bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op);
 
     MOZ_ALWAYS_INLINE
     bool makeAtomIndex(JSAtom* atom, jsatomid* indexp) {
         AtomIndexAddPtr p = atomIndices->lookupForAdd(atom);
         if (p) {
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -440,27 +440,49 @@ class FullParseHandler
     ParseNode* newStatementList(unsigned blockid, const TokenPos& pos) {
         ParseNode* pn = new_<ListNode>(PNK_STATEMENTLIST, pos);
         if (pn)
             pn->pn_blockid = blockid;
         return pn;
     }
 
     template <typename PC>
+    bool isFunctionStmt(ParseNode* stmt, PC* pc) {
+        if (!pc->sc->strict()) {
+            while (stmt->isKind(PNK_LABEL))
+                stmt = stmt->as<LabeledStatement>().statement();
+        }
+
+        return stmt->isKind(PNK_FUNCTION) || stmt->isKind(PNK_ANNEXB_FUNCTION);
+    }
+
+    template <typename PC>
     void addStatementToList(ParseNode* list, ParseNode* stmt, PC* pc) {
         MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
 
-        if (stmt->isKind(PNK_FUNCTION) || stmt->isKind(PNK_ANNEXB_FUNCTION)) {
+        list->append(stmt);
+
+        if (isFunctionStmt(stmt, pc)) {
             // PNX_FUNCDEFS notifies the emitter that the block contains
             // body-level function definitions that should be processed
             // before the rest of nodes.
             list->pn_xflags |= PNX_FUNCDEFS;
         }
+    }
 
-        list->append(stmt);
+    template <typename PC>
+    void addCaseStatementToList(ParseNode* list, ParseNode* casepn, PC* pc) {
+        MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
+        MOZ_ASSERT(casepn->isKind(PNK_CASE));
+        MOZ_ASSERT(casepn->pn_right->isKind(PNK_STATEMENTLIST));
+
+        list->append(casepn);
+
+        if (casepn->pn_right->pn_xflags & PNX_FUNCDEFS)
+            list->pn_xflags |= PNX_FUNCDEFS;
     }
 
     bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {
         MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
 
         TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1);
         ParseNode* makeGen = new_<NullaryNode>(PNK_GENERATOR, yieldPos);
         if (!makeGen)
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2325,29 +2325,52 @@ Parser<FullParseHandler>::checkFunctionD
 {
     ParseNode*& pn = *pn_;
     *pbodyProcessed = false;
 
     if (kind == Statement) {
         MOZ_ASSERT(assignmentForAnnexBOut);
         *assignmentForAnnexBOut = nullptr;
 
-        if (pc->atBodyLevel()) {
+        // In sloppy mode, ES6 Annex B.3.2 allows labelled function
+        // declarations. Otherwise it is a parse error.
+        bool bodyLevelFunction = pc->atBodyLevel();
+        if (!bodyLevelFunction) {
+            StmtInfoPC* stmt = pc->innermostStmt();
+            if (stmt->type == StmtType::LABEL) {
+                if (pc->sc->strict()) {
+                    report(ParseError, false, null(), JSMSG_FUNCTION_LABEL);
+                    return false;
+                }
+
+                stmt = pc->innermostNonLabelStmt();
+                // A switch statement is always braced, so it's okay to label
+                // functions in sloppy mode under switch.
+                if (stmt && stmt->type != StmtType::BLOCK && stmt->type != StmtType::SWITCH) {
+                    report(ParseError, false, null(), JSMSG_SLOPPY_FUNCTION_LABEL);
+                    return false;
+                }
+
+                bodyLevelFunction = pc->atBodyLevel(stmt);
+            }
+        }
+
+        if (bodyLevelFunction) {
             if (!bindBodyLevelFunctionName(funName, pn_))
                 return false;
         } else {
             Definition* annexDef = nullptr;
             Node synthesizedDeclarationList = null();
 
             if (!pc->sc->strict()) {
-                // Under non-strict mode, try Annex B.3.3 semantics. If making
-                // an additional 'var' binding of the same name does not throw
-                // an early error, do so. This 'var' binding would be assigned
-                // the function object in situ, e.g., when its declaration is
-                // reached, not at the start of the block.
+                // Under non-strict mode, try ES6 Annex B.3.3 semantics. If
+                // making an additional 'var' binding of the same name does
+                // not throw an early error, do so. This 'var' binding would
+                // be assigned the function object in situ, e.g., when its
+                // declaration is reached, not at the start of the block.
 
                 annexDef = pc->decls().lookupFirst(funName);
                 if (annexDef) {
                     if (annexDef->kind() == Definition::CONSTANT ||
                         annexDef->kind() == Definition::LET)
                     {
                         // Do not emit Annex B assignment if we would've
                         // thrown a redeclaration error.
@@ -3108,26 +3131,26 @@ Parser<ParseHandler>::functionStmt(Yield
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
     // ES6 Annex B.3.4 says we can parse function declarations unbraced under if or
     // else as if it were braced. That is, |if (x) function f() {}| is parsed as
     // |if (x) { function f() {} }|.
     Maybe<AutoPushStmtInfoPC> synthesizedStmtInfoForAnnexB;
     Node synthesizedBlockForAnnexB = null();
     StmtInfoPC *stmt = pc->innermostStmt();
-    if (!pc->sc->strict() && stmt &&
-        (stmt->type == StmtType::IF || stmt->type == StmtType::ELSE))
-    {
-        if (!abortIfSyntaxParser())
-            return null();
-
-        synthesizedStmtInfoForAnnexB.emplace(*this, StmtType::BLOCK);
-        synthesizedBlockForAnnexB = pushLexicalScope(*synthesizedStmtInfoForAnnexB);
-        if (!synthesizedBlockForAnnexB)
-            return null();
+    if (!pc->sc->strict() && stmt) {
+        if (stmt->type == StmtType::IF || stmt->type == StmtType::ELSE) {
+            if (!abortIfSyntaxParser())
+                return null();
+
+            synthesizedStmtInfoForAnnexB.emplace(*this, StmtType::BLOCK);
+            synthesizedBlockForAnnexB = pushLexicalScope(*synthesizedStmtInfoForAnnexB);
+            if (!synthesizedBlockForAnnexB)
+                return null();
+        }
     }
 
     RootedPropertyName name(context);
     GeneratorKind generatorKind = NotGenerator;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
@@ -4549,49 +4572,66 @@ Parser<ParseHandler>::variables(YieldHan
             break;
     }
 
     return pn;
 }
 
 template <>
 bool
-Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos& errorPos)
+Parser<FullParseHandler>::checkAndPrepareLexical(PrepareLexicalKind prepareWhat,
+                                                 const TokenPos& errorPos)
 {
     /*
-     * This is a lexical declaration. We must be directly under a block, but
-     * not an implicit block created due to 'for (let ...)'. If we pass this
-     * error test, make the enclosing StmtInfoPC be our scope. Further let
-     * declarations in this block will find this scope statement and use the
-     * same block object.
+     * This is a lexical declaration. We must be directly under a block for
+     * 'let' and 'const' declarations. If we pass this error test, make the
+     * enclosing StmtInfoPC be our scope. Further let declarations in this
+     * block will find this scope statement and use the same block object.
+     *
+     * Function declarations behave like 'let', except that they are allowed
+     * per ES6 Annex B.3.2 to be labeled, unlike plain 'let' and 'const'
+     * declarations.
      *
      * If we are the first let declaration in this block (i.e., when the
      * enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then
      * we also need to set pc->blockNode to be our PNK_LEXICALSCOPE.
      */
-    StmtInfoPC* stmt = pc->innermostStmt();
+
+    // ES6 Annex B.3.2 does not apply in strict mode, and labeled functions in
+    // strict mode should have been rejected by checkFunctionDefinition.
+    MOZ_ASSERT_IF(pc->innermostStmt() &&
+                  pc->innermostStmt()->type == StmtType::LABEL &&
+                  prepareWhat == PrepareFunction,
+                  !pc->sc->strict());
+
+    StmtInfoPC* stmt = prepareWhat == PrepareFunction
+                       ? pc->innermostNonLabelStmt()
+                       : pc->innermostStmt();
     if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
-        reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
-                         isConst ? "const" : "lexical");
+        reportWithOffset(ParseError, false, errorPos.begin,
+                         stmt->type == StmtType::LABEL
+                         ? JSMSG_LEXICAL_DECL_LABEL
+                         : JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
+                         prepareWhat == PrepareConst ? "const" : "lexical");
         return false;
     }
 
     if (!stmt) {
-        MOZ_ASSERT(pc->atBodyLevel());
+        MOZ_ASSERT_IF(prepareWhat != PrepareFunction, pc->atBodyLevel());
 
         /*
          * Self-hosted code must be usable against *any* global object,
          * including ones with other let variables -- variables possibly
          * placed in conflicting slots.  Forbid top-level let declarations to
          * prevent such conflicts from ever occurring.
          */
         bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->innermostScopeStmt();
         if (options().selfHostingMode && isGlobal) {
             report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL,
-                   isConst ? "'const'" : "'let'");
+                   prepareWhat == PrepareConst ? "'const'" : "'let'");
             return false;
         }
         return true;
     }
 
     if (stmt->isBlockScope) {
         // Nothing to do, the top statement already has a block scope.
         MOZ_ASSERT(pc->innermostScopeStmt() == stmt);
@@ -4607,18 +4647,22 @@ Parser<FullParseHandler>::checkAndPrepar
             return false;
 
         /*
          * Some obvious assertions here, but they may help clarify the
          * situation. This stmt is not yet a scope, so it must not be a
          * catch block (catch is a lexical scope by definition).
          */
         MOZ_ASSERT(stmt->canBeBlockScope() && stmt->type != StmtType::CATCH);
-
-        pc->stmtStack.makeInnermostLexicalScope(*blockObj);
+        if (prepareWhat == PrepareFunction) {
+            stmt->isBlockScope = true;
+            pc->stmtStack.linkAsInnermostScopeStmt(stmt, *blockObj);
+        } else {
+            pc->stmtStack.makeInnermostLexicalScope(*blockObj);
+        }
         MOZ_ASSERT(!blockScopes[stmt->blockid]);
         blockScopes[stmt->blockid].set(blockObj);
 
 #ifdef DEBUG
         ParseNode* tmp = pc->blockNode;
         MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
 #endif
 
@@ -4640,61 +4684,62 @@ CurrentLexicalStaticBlock(ParseContext<F
                (!pc->sc->isGlobalContext() ||
                 HasNonSyntacticStaticScopeChain(pc->innermostStaticScope())));
     return nullptr;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::prepareAndBindInitializedLexicalWithNode(HandlePropertyName name,
-                                                                   bool isConst,
+                                                                   PrepareLexicalKind prepareWhat,
                                                                    ParseNode* pn,
                                                                    const TokenPos& pos)
 {
     BindData<FullParseHandler> data(context);
-    if (!checkAndPrepareLexical(isConst, pos))
+    if (!checkAndPrepareLexical(prepareWhat, pos))
         return false;
-    data.initLexical(HoistVars, isConst ? JSOP_DEFCONST : JSOP_DEFLET,
+    data.initLexical(HoistVars, prepareWhat == PrepareConst ? JSOP_DEFCONST : JSOP_DEFLET,
                      CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS);
     return bindInitialized(&data, name, pn);
 }
 
 template <>
 ParseNode*
-Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst,
+Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name,
+                                                        PrepareLexicalKind prepareWhat,
                                                         const TokenPos& pos)
 {
     ParseNode* dn = newBindingNode(name, false);
     if (!dn)
         return null();
     handler.setPosition(dn, pos);
 
-    if (!prepareAndBindInitializedLexicalWithNode(name, isConst, dn, pos))
+    if (!prepareAndBindInitializedLexicalWithNode(name, prepareWhat, dn, pos))
         return null();
 
     return dn;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::bindLexicalFunctionName(HandlePropertyName funName,
                                                   ParseNode* pn)
 {
     MOZ_ASSERT(!pc->atBodyLevel());
     pn->pn_blockid = pc->blockid();
-    return prepareAndBindInitializedLexicalWithNode(funName, /* isConst = */ false, pn, pos());
+    return prepareAndBindInitializedLexicalWithNode(funName, PrepareFunction, pn, pos());
 }
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool isConst)
 {
     handler.disableSyntaxParser();
 
-    if (!checkAndPrepareLexical(isConst, pos()))
+    if (!checkAndPrepareLexical(isConst ? PrepareConst : PrepareLet, pos()))
         return null();
 
     /*
      * Parse body-level lets without a new block object. ES6 specs
      * that an execution environment's initial lexical environment
      * is the VariableEnvironment, i.e., body-level lets are in
      * the same environment record as vars.
      *
@@ -5213,17 +5258,17 @@ Parser<FullParseHandler>::exportDeclarat
           case TOK_CLASS:
             kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName);
             if (!kid)
                 return null();
             break;
           default:
             tokenStream.ungetToken();
             RootedPropertyName name(context, context->names().starDefaultStar);
-            binding = makeInitializedLexicalBinding(name, true, pos());
+            binding = makeInitializedLexicalBinding(name, PrepareConst, pos());
             if (!binding)
                 return null();
             kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
             if (!kid)
                 return null();
             if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
                 return null();
             break;
@@ -5962,17 +6007,17 @@ Parser<ParseHandler>::switchStatement(Yi
                             return null();
                         }
                         warnedAboutStatementsAfterReturn = true;
                     }
                 } else if (handler.isReturnStatement(stmt)) {
                     afterReturn = true;
                 }
             }
-            handler.addList(body, stmt);
+            handler.addStatementToList(body, stmt, pc);
         }
 
         // In ES6, lexical bindings cannot be accessed until initialized. If
         // there was a 'let' declaration in the case we just parsed, remember
         // the slot starting at which new lexical bindings will be
         // assigned. Since lexical bindings from previous cases will not
         // dominate uses in the current case, any such uses will require a
         // dead zone check.
@@ -5981,17 +6026,17 @@ Parser<ParseHandler>::switchStatement(Yi
         // declaring lexical bindings within switch cases without introducing
         // a new block is poor form and should be avoided.
         if (stmtInfo->isBlockScope)
             stmtInfo->firstDominatingLexicalInCase = stmtInfo->staticBlock().numVariables();
 
         Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body);
         if (!casepn)
             return null();
-        handler.addList(caseList, casepn);
+        handler.addCaseStatementToList(caseList, casepn, pc);
     }
 
     /*
      * Handle the case where there was a let declaration in any case in
      * the switch body, but not within an inner block.  If it replaced
      * pc->blockNode with a new block node then we must refresh caseList and
      * then restore pc->blockNode.
      */
@@ -6808,28 +6853,28 @@ Parser<FullParseHandler>::classDefinitio
         JSOp op = JSOpFromPropertyType(propType);
         if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic))
             return null();
     }
 
     ParseNode* nameNode = null();
     ParseNode* methodsOrBlock = classMethods;
     if (name) {
-        ParseNode* innerBinding = makeInitializedLexicalBinding(name, true, namePos);
+        ParseNode* innerBinding = makeInitializedLexicalBinding(name, PrepareConst, namePos);
         if (!innerBinding)
             return null();
 
         MOZ_ASSERT(classBlock);
         handler.setLexicalScopeBody(classBlock, classMethods);
         methodsOrBlock = classBlock;
         classStmt.reset();
 
         ParseNode* outerBinding = null();
         if (classContext == ClassStatement) {
-            outerBinding = makeInitializedLexicalBinding(name, false, namePos);
+            outerBinding = makeInitializedLexicalBinding(name, PrepareLet, namePos);
             if (!outerBinding)
                 return null();
         }
 
         nameNode = handler.newClassNames(outerBinding, innerBinding, namePos);
         if (!nameNode)
             return null();
     }
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -284,42 +284,47 @@ struct MOZ_STACK_CLASS ParseContext : pu
     ~ParseContext();
 
     bool init(Parser<ParseHandler>& parser);
 
     unsigned blockid() { return stmtStack.innermost() ? stmtStack.innermost()->blockid : bodyid; }
 
     StmtInfoPC* innermostStmt() const { return stmtStack.innermost(); }
     StmtInfoPC* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
+    StmtInfoPC* innermostNonLabelStmt() const { return stmtStack.innermostNonLabel(); }
     JSObject* innermostStaticScope() const {
         if (StmtInfoPC* stmt = innermostScopeStmt())
             return stmt->staticScope;
         return sc->staticScope();
     }
 
     // True if we are at the topmost level of a entire script or function body.
     // For example, while parsing this code we would encounter f1 and f2 at
     // body level, but we would not encounter f3 or f4 at body level:
     //
     //   function f1() { function f2() { } }
     //   if (cond) { function f3() { if (cond) { function f4() { } } } }
     //
-    bool atBodyLevel() {
+    bool atBodyLevel(StmtInfoPC* stmt) {
         // 'eval' and non-syntactic scripts are always under an invisible
         // lexical scope, but since it is not syntactic, it should still be
         // considered at body level.
         if (sc->staticScope()->is<StaticEvalObject>()) {
-            bool bl = !innermostStmt()->enclosing;
-            MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK);
-            MOZ_ASSERT_IF(bl, innermostStmt()->staticScope
-                                             ->template as<StaticBlockObject>()
-                                             .enclosingStaticScope() == sc->staticScope());
+            bool bl = !stmt->enclosing;
+            MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
+            MOZ_ASSERT_IF(bl, stmt->staticScope
+                                  ->template as<StaticBlockObject>()
+                                  .enclosingStaticScope() == sc->staticScope());
             return bl;
         }
-        return !innermostStmt();
+        return !stmt;
+    }
+
+    bool atBodyLevel() {
+        return atBodyLevel(innermostStmt());
     }
 
     bool atGlobalLevel() {
         return atBodyLevel() && sc->isGlobalContext() && !innermostScopeStmt();
     }
 
     // True if we are at the topmost level of a module only.
     bool atModuleLevel() {
@@ -837,20 +842,27 @@ class Parser : private JS::AutoGCRooter,
     Node propertyName(YieldHandling yieldHandling, Node propList,
                       PropertyType* propType, MutableHandleAtom propAtom);
     Node computedPropertyName(YieldHandling yieldHandling, Node literal);
     Node arrayInitializer(YieldHandling yieldHandling);
     Node newRegExp();
 
     Node objectLiteral(YieldHandling yieldHandling);
 
-    bool checkAndPrepareLexical(bool isConst, const TokenPos& errorPos);
-    bool prepareAndBindInitializedLexicalWithNode(HandlePropertyName name, bool isConst,
+    enum PrepareLexicalKind {
+        PrepareLet,
+        PrepareConst,
+        PrepareFunction
+    };
+    bool checkAndPrepareLexical(PrepareLexicalKind prepareWhat, const TokenPos& errorPos);
+    bool prepareAndBindInitializedLexicalWithNode(HandlePropertyName name,
+                                                  PrepareLexicalKind prepareWhat,
                                                   ParseNode* pn, const TokenPos& pos);
-    Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos& pos);
+    Node makeInitializedLexicalBinding(HandlePropertyName name, PrepareLexicalKind prepareWhat,
+                                       const TokenPos& pos);
 
     Node newBindingNode(PropertyName* name, bool functionScope, VarContext varContext = HoistVars);
 
     // Top-level entrypoint into destructuring pattern checking/name-analyzing.
     bool checkDestructuringPattern(BindData<ParseHandler>* data, Node pattern);
 
     // Recursive methods for checking/name-analyzing subcomponents of a
     // destructuring pattern.  The array/object methods *must* be passed arrays
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -582,16 +582,22 @@ class MOZ_STACK_CLASS StmtInfoStack
   public:
     explicit StmtInfoStack(ExclusiveContext* cx)
       : innermostStmt_(nullptr),
         innermostScopeStmt_(nullptr)
     { }
 
     StmtInfo* innermost() const { return innermostStmt_; }
     StmtInfo* innermostScopeStmt() const { return innermostScopeStmt_; }
+    StmtInfo* innermostNonLabel() const {
+        StmtInfo* stmt = innermost();
+        while (stmt && stmt->type == StmtType::LABEL)
+            stmt = stmt->enclosing;
+        return stmt;
+    }
 
     void push(StmtInfo* stmt, StmtType type) {
         stmt->type = type;
         stmt->isBlockScope = false;
         stmt->isForLetBlock = false;
         stmt->label = nullptr;
         stmt->staticScope = nullptr;
         stmt->enclosing = innermostStmt_;
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -285,16 +285,17 @@ class SyntaxParseHandler
     bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; }
     Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeUnparenthesizedYieldExpr; }
     Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
 
     // Statements
 
     Node newStatementList(unsigned blockid, const TokenPos& pos) { return NodeGeneric; }
     void addStatementToList(Node list, Node stmt, ParseContext<SyntaxParseHandler>* pc) {}
+    void addCaseStatementToList(Node list, Node stmt, ParseContext<SyntaxParseHandler>* pc) {}
     bool prependInitialYield(Node stmtList, Node gen) { return true; }
     Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; }
 
     Node newSetThis(Node thisName, Node value) { return value; }
 
     Node newExprStatement(Node expr, uint32_t end) {
         return expr == NodeUnparenthesizedString ? NodeStringExprStatement : NodeGeneric;
     }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -265,16 +265,19 @@ MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER,    0
 MSG_DEF(JSMSG_ILLEGAL_CHARACTER,       0, JSEXN_SYNTAXERR, "illegal character")
 MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module")
 MSG_DEF(JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT,1,JSEXN_SYNTAXERR,"for-{0} loop head declarations may not have initializers")
 MSG_DEF(JSMSG_IN_AFTER_FOR_NAME,       0, JSEXN_SYNTAXERR, "missing 'in' or 'of' after for")
 MSG_DEF(JSMSG_LABEL_NOT_FOUND,         0, JSEXN_SYNTAXERR, "label not found")
 MSG_DEF(JSMSG_LET_CLASS_BINDING,       0, JSEXN_SYNTAXERR, "'let' is not a valid name for a class")
 MSG_DEF(JSMSG_LET_COMP_BINDING,        0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
 MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,   1, JSEXN_SYNTAXERR, "{0} declaration not directly within block")
+MSG_DEF(JSMSG_LEXICAL_DECL_LABEL,      1, JSEXN_SYNTAXERR, "{0} declarations cannot be labelled")
+MSG_DEF(JSMSG_FUNCTION_LABEL,          0, JSEXN_SYNTAXERR, "functions cannot be labelled")
+MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL,   0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks")
 MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW,  0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression")
 MSG_DEF(JSMSG_MALFORMED_ESCAPE,        1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
 MSG_DEF(JSMSG_MISSING_BINARY_DIGITS,   0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")
 MSG_DEF(JSMSG_MISSING_EXPONENT,        0, JSEXN_SYNTAXERR, "missing exponent")
 MSG_DEF(JSMSG_MISSING_EXPR_AFTER_THROW,0, JSEXN_SYNTAXERR, "throw statement is missing an expression")
 MSG_DEF(JSMSG_MISSING_FORMAL,          0, JSEXN_SYNTAXERR, "missing formal parameter")
 MSG_DEF(JSMSG_MISSING_HEXDIGITS,       0, JSEXN_SYNTAXERR, "missing hexadecimal digits after '0x'")
 MSG_DEF(JSMSG_MISSING_OCTAL_DIGITS,    0, JSEXN_SYNTAXERR, "missing octal digits after '0o'")
--- 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 = 337;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 338;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 429,
+static_assert(JSErr_Limit == 432,
               "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)