Bug 1071646 - Support labelled function declarations in sloppy mode per Annex B.3.2. (r=jorendorff)
authorShu-yu Guo <shu@rfrn.org>
Wed, 09 Dec 2015 07:52:58 -0800
changeset 275978 5b4fe5acd50c8e16c1df2d9cb312c6875f3028fc
parent 275977 73c94ff300b2a4250abdc84935c30457077c4f9c
child 275979 a44b841c33fb129b236e5dceab9129370511f418
push id69002
push usershu@rfrn.org
push dateWed, 09 Dec 2015 15:53:07 +0000
treeherdermozilla-inbound@ffd21df83fee [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1071646
milestone45.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 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/tests/ecma_6/LexicalEnvironment/block-scoped-functions-annex-b-if.js
js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-annex-b-label.js
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());
@@ -3083,21 +3083,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;
@@ -5297,16 +5309,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;
         }
     }
 
@@ -6331,28 +6348,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
@@ -2321,29 +2321,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.
@@ -3104,26 +3127,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();
 
@@ -4545,49 +4568,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);
@@ -4603,18 +4643,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
 
@@ -4636,61 +4680,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.
      *
@@ -5192,17 +5237,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;
@@ -5939,17 +5984,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.
@@ -5958,17 +6003,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.
      */
@@ -6785,28 +6830,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() {
@@ -836,20 +841,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
@@ -575,16 +575,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/tests/ecma_6/LexicalEnvironment/block-scoped-functions-annex-b-if.js
+++ b/js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-annex-b-if.js
@@ -25,9 +25,18 @@ function f(x) {
     function g() { return "g4"; }
 
   log += g();
 }
 
 f(true);
 f(false);
 
-reportCompare(log, "g0g2g2g4g1g1g3g3");
+try {
+  eval(`
+    if (1)
+      l: function foo() {}
+  `);
+} catch (e) {
+  log += "e";
+}
+
+reportCompare(log, "g0g2g2g4g1g1g3g3e");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-annex-b-label.js
@@ -0,0 +1,43 @@
+function expectSyntaxError(str) {
+  var threwSyntaxError;
+  try {
+    eval(str);
+  } catch (e) {
+    threwSyntaxError = e instanceof SyntaxError;
+  }
+  assertEq(threwSyntaxError, true);
+
+  try {
+    eval('"use strict";' + str);
+  } catch (e) {
+    threwSyntaxError = e instanceof SyntaxError;
+  }
+  assertEq(threwSyntaxError, true);
+}
+
+function expectSloppyPass(str) {
+  eval(str);
+
+  try {
+    eval('"use strict";' + str);
+  } catch (e) {
+    threwSyntaxError = e instanceof SyntaxError;
+  }
+  assertEq(threwSyntaxError, true);
+}
+
+expectSloppyPass(`l: function f1() {}`);
+expectSloppyPass(`l0: l: function f1() {}`);
+expectSloppyPass(`{ f1(); l: function f1() {} }`);
+expectSloppyPass(`{ f1(); l0: l: function f1() {} }`);
+expectSloppyPass(`{ f1(); l: function f1() { return 42; } } assertEq(f1(), 42);`);
+expectSloppyPass(`eval("fe(); l: function fe() {}")`);
+expectSyntaxError(`if (1) l: function f2() {}`);
+expectSyntaxError(`if (1) {} else l: function f3() {}`);
+expectSyntaxError(`do l: function f4() {} while (0)`);
+expectSyntaxError(`while (0) l: function f5() {}`);
+expectSyntaxError(`for (;;) l: function f6() {}`);
+expectSloppyPass(`switch (1) { case 1: l: function f7() {} }`);
+expectSloppyPass(`switch (1) { case 1: assertEq(f8(), 'f8'); case 2: l: function f8() { return 'f8'; } } assertEq(f8(), 'f8');`);
+
+reportCompare(0, 0);
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -28,17 +28,17 @@ namespace js {
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
 static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 327;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 422,
+static_assert(JSErr_Limit == 425,
               "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)