Bug 1071646 - Make functions block-scoped in JS and implement Annex B semantics for compatibility. (r=jorendorff)
authorShu-yu Guo <shu@rfrn.org>
Fri, 18 Dec 2015 13:18:19 -0800
changeset 277072 001519eae1c86e57b859310a1deaab0a668ff435
parent 277071 cd6226b5ea820de6651baa1c3a66aac8359d275c
child 277073 204aeab8d5f8d21f1ebf0d62776b27bed9476290
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 - Make functions block-scoped in JS and implement Annex B semantics for compatibility. (r=jorendorff)
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FoldConstants.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/NameFunctions.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SyntaxParseHandler.h
js/src/jit-test/tests/auto-regress/bug771027.js
js/src/jit-test/tests/baseline/bug1081850.js
js/src/jit-test/tests/basic/bug667504-syntax.js
js/src/jit-test/tests/ion/bug1148973-1.js
js/src/tests/ecma_5/extensions/function-definition-with.js
js/src/tests/ecma_5/extensions/strict-function-statements.js
js/src/tests/ecma_6/extensions/for-loop-with-lexical-declaration-and-nested-function-statement.js
js/src/tests/js1_5/Regress/regress-326453.js
js/src/tests/js1_5/extensions/regress-245795.js
js/src/tests/js1_5/extensions/regress-406572.js
js/src/tests/js1_8_5/reflect-parse/declarations.js
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -2035,16 +2035,19 @@ ASTSerializer::declaration(ParseNode* pn
                pn->isKind(PNK_VAR) ||
                pn->isKind(PNK_LET) ||
                pn->isKind(PNK_CONST));
 
     switch (pn->getKind()) {
       case PNK_FUNCTION:
         return function(pn, AST_FUNC_DECL, dst);
 
+      case PNK_ANNEXB_FUNCTION:
+        return function(pn->pn_left, AST_FUNC_DECL, dst);
+
       case PNK_VAR:
         return variableDeclaration(pn, false, dst);
 
       default:
         MOZ_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_CONST));
         return variableDeclaration(pn, true, dst);
     }
 }
@@ -2406,16 +2409,19 @@ bool
 ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
 {
     JS_CHECK_RECURSION(cx, return false);
     switch (pn->getKind()) {
       case PNK_FUNCTION:
       case PNK_VAR:
         return declaration(pn, dst);
 
+      case PNK_ANNEXB_FUNCTION:
+        return declaration(pn->pn_left, dst);
+
       case PNK_LETBLOCK:
         return letBlock(pn, dst);
 
       case PNK_LET:
       case PNK_CONST:
         return declaration(pn, dst);
 
       case PNK_IMPORT:
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2339,16 +2339,20 @@ BytecodeEmitter::checkSideEffects(ParseN
         *answer = pn->pn_count > 1;
         return true;
 
       case PNK_ARRAYCOMP:
         MOZ_ASSERT(pn->isArity(PN_LIST));
         MOZ_ASSERT(pn->pn_count == 1);
         return checkSideEffects(pn->pn_head, answer);
 
+      case PNK_ANNEXB_FUNCTION:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        return checkSideEffects(pn->pn_left, answer);
+
       case PNK_ARGSBODY:
         *answer = true;
         return true;
 
       case PNK_FORIN:           // by PNK_FOR/PNK_COMPREHENSIONFOR
       case PNK_FOROF:           // by PNK_FOR/PNK_COMPREHENSIONFOR
       case PNK_FORHEAD:         // by PNK_FOR/PNK_COMPREHENSIONFOR
       case PNK_CLASSMETHOD:     // by PNK_CLASS
@@ -5321,28 +5325,55 @@ BytecodeEmitter::emitLetBlock(ParseNode*
         return false;
 
     if (!leaveNestedScope(&stmtInfo))
         return false;
 
     return true;
 }
 
+bool
+BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list)
+{
+    MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS);
+
+    for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) {
+        if (pn->isKind(PNK_ANNEXB_FUNCTION) ||
+            (pn->isKind(PNK_FUNCTION) && pn->functionIsHoisted()))
+        {
+            if (!emitTree(pn))
+                return false;
+        }
+    }
+
+    return true;
+}
+
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitLexicalScope(ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
 
     StmtInfoBCE stmtInfo(cx);
     if (!enterBlockScope(&stmtInfo, pn->pn_objbox, JSOP_UNINITIALIZED, 0))
         return false;
 
-    if (!emitTree(pn->pn_expr))
+    ParseNode* body = pn->pn_expr;
+
+    if (body->isKind(PNK_STATEMENTLIST) && body->pn_xflags & PNX_FUNCDEFS) {
+        // This block contains function statements whose definitions are
+        // hoisted to the top of the block. Emit these as a separate pass
+        // before the rest of the block.
+        if (!emitHoistedFunctionsInList(body))
+            return false;
+    }
+
+    if (!emitTree(body))
         return false;
 
     if (!leaveNestedScope(&stmtInfo))
         return false;
 
     return true;
 }
 
@@ -6193,29 +6224,50 @@ BytecodeEmitter::emitComprehensionFor(Pa
     return compFor->pn_left->isKind(PNK_FORIN)
            ? emitComprehensionForIn(compFor)
            : emitComprehensionForOf(compFor);
 }
 
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
 {
+    ParseNode* assignmentForAnnexB = nullptr;
+    if (pn->isKind(PNK_ANNEXB_FUNCTION)) {
+        assignmentForAnnexB = pn->pn_right;
+        pn = pn->pn_left;
+    }
+
     FunctionBox* funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
     MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
 
     /*
      * Set the |wasEmitted| flag in the funbox once the function has been
      * emitted. Function definitions that need hoisting to the top of the
      * function will be seen by emitFunction in two places.
      */
     if (funbox->wasEmitted) {
+        // Annex B block-scoped functions are hoisted like any other
+        // block-scoped function to the top of their scope. When their
+        // definitions are seen for the second time, we need to emit the
+        // assignment that assigns the function to the outer 'var' binding.
+        if (assignmentForAnnexB) {
+            if (!emitTree(assignmentForAnnexB))
+                return false;
+
+            // If we did not synthesize a new binding and only a simple
+            // assignment, manually pop the result.
+            if (assignmentForAnnexB->isKind(PNK_ASSIGN)) {
+                if (!emit1(JSOP_POP))
+                    return false;
+            }
+        }
+
         MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript());
         MOZ_ASSERT(pn->functionIsHoisted());
-        MOZ_ASSERT(sc->isFunctionBox());
         return true;
     }
 
     funbox->wasEmitted = true;
 
     /*
      * Mark as singletons any function which will only be executed once, or
      * which is inner to a lambda we only expect to run once. In the latter
@@ -6313,17 +6365,25 @@ 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 (sc->isGlobalContext()) {
+    if (!atBodyLevel()) {
+        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;
@@ -7994,17 +8054,16 @@ bool
 BytecodeEmitter::emitArgsBody(ParseNode *pn)
 {
     RootedFunction fun(cx, sc->asFunctionBox()->function());
     ParseNode* pnlast = pn->last();
 
     // Carefully emit everything in the right order:
     // 1. Defaults and Destructuring for each argument
     // 2. Functions
-    ParseNode* pnchild = pnlast->pn_head;
     bool hasDefaults = sc->asFunctionBox()->hasDefaults();
     ParseNode* rest = nullptr;
     bool restIsDefn = false;
     if (fun->hasRest() && hasDefaults) {
         // Defaults with a rest parameter need special handling. The
         // rest parameter needs to be undefined while defaults are being
         // processed. To do this, we create the rest argument and let it
         // sit on the stack while processing defaults. The rest
@@ -8055,31 +8114,21 @@ BytecodeEmitter::emitArgsBody(ParseNode 
             if (!emitVarOp(pn2, JSOP_SETARG))
                 return false;
             if (!emit1(JSOP_POP))
                 return false;
             switchToMain();
         }
     }
     if (pnlast->pn_xflags & PNX_FUNCDEFS) {
-        // This block contains top-level function definitions. To ensure
-        // that we emit the bytecode defining them before the rest of code
-        // in the block we use a separate pass over functions. During the
-        // main pass later the emitter will add JSOP_NOP with source notes
-        // for the function to preserve the original functions position
-        // when decompiling.
-        //
-        // Currently this is used only for functions, as compile-as-we go
-        // mode for scripts does not allow separate emitter passes.
-        for (ParseNode* pn2 = pnchild; pn2; pn2 = pn2->pn_next) {
-            if (pn2->isKind(PNK_FUNCTION) && pn2->functionIsHoisted()) {
-                if (!emitTree(pn2))
-                    return false;
-            }
-        }
+        // This function contains top-level inner function definitions. To
+        // ensure that we emit the bytecode defining them before the rest
+        // of code in the block we use a separate pass over functions.
+        if (!emitHoistedFunctionsInList(pnlast))
+            return false;
     }
     return emitTree(pnlast);
 }
 
 bool
 BytecodeEmitter::emitDefaultsAndDestructuring(ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_ARGSBODY));
@@ -8288,16 +8337,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
        However, a couple trees require special treatment; see the
        relevant emitter functions for details. */
     if (emitLineNote == EMIT_LINENOTE && pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR &&
         !updateLineNumberNotes(pn->pn_pos.begin))
         return false;
 
     switch (pn->getKind()) {
       case PNK_FUNCTION:
+      case PNK_ANNEXB_FUNCTION:
         if (!emitFunction(pn))
             return false;
         break;
 
       case PNK_ARGSBODY:
         if (!emitArgsBody(pn))
             return false;
         break;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -459,16 +459,18 @@ struct BytecodeEmitter
     bool emitInternedObjectOp(uint32_t index, JSOp op);
     bool emitObjectOp(ObjectBox* objbox, JSOp op);
     bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
     bool emitRegExp(uint32_t index);
 
     MOZ_NEVER_INLINE bool emitFunction(ParseNode* pn, bool needsProto = false);
     MOZ_NEVER_INLINE bool emitObject(ParseNode* pn);
 
+    bool emitHoistedFunctionsInList(ParseNode* pn);
+
     bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type);
 
     // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
     // not used to unconditionally emit JSOP_GETLOCAL. Variable access should
     // instead be emitted using EmitVarOp. In special cases, when the caller
     // definitely knows that a given local slot is unaliased, this function may be
     // used as a non-asserting version of emitUint16Operand.
     bool emitLocalOp(JSOp op, uint32_t slot);
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -95,16 +95,21 @@ ContainsHoistedDeclaration(ExclusiveCont
       // the function statement is evaluated.  Thus any declaration introduced
       // by a function statement, as observed by this function, isn't a hoisted
       // declaration.
       case PNK_FUNCTION:
         MOZ_ASSERT(node->isArity(PN_CODE));
         *result = false;
         return true;
 
+      case PNK_ANNEXB_FUNCTION:
+        MOZ_ASSERT(node->isArity(PN_BINARY));
+        *result = false;
+        return true;
+
       case PNK_MODULE:
         *result = false;
         return true;
 
       // Statements with no sub-components at all.
       case PNK_NOP: // induced by function f() {} function f() {}
       case PNK_DEBUGGER:
         MOZ_ASSERT(node->isArity(PN_NULLARY));
@@ -1778,16 +1783,19 @@ Fold(ExclusiveContext* cx, ParseNode** p
 
       case PNK_AND:
       case PNK_OR:
         return FoldAndOr(cx, pnp, parser, inGenexpLambda);
 
       case PNK_FUNCTION:
         return FoldFunction(cx, pn, parser, inGenexpLambda);
 
+      case PNK_ANNEXB_FUNCTION:
+        return FoldFunction(cx, pn->pn_left, parser, inGenexpLambda);
+
       case PNK_MODULE:
         return FoldModule(cx, pn, parser);
 
       case PNK_SUB:
       case PNK_STAR:
       case PNK_LSH:
       case PNK_RSH:
       case PNK_URSH:
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -443,27 +443,21 @@ class FullParseHandler
             pn->pn_blockid = blockid;
         return pn;
     }
 
     template <typename PC>
     void addStatementToList(ParseNode* list, ParseNode* stmt, PC* pc) {
         MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
 
-        if (stmt->isKind(PNK_FUNCTION)) {
-            if (pc->atBodyLevel()) {
-                // 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;
-            } else {
-                // General deoptimization was done in Parser::functionDef.
-                MOZ_ASSERT_IF(pc->sc->isFunctionBox(),
-                              pc->sc->asFunctionBox()->hasExtensibleScope());
-            }
+        if (stmt->isKind(PNK_FUNCTION) || stmt->isKind(PNK_ANNEXB_FUNCTION)) {
+            // 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);
     }
 
     bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {
         MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
 
@@ -651,16 +645,21 @@ class FullParseHandler
     }
     void setFunctionBody(ParseNode* pn, ParseNode* kid) {
         pn->pn_body = kid;
     }
     void setFunctionBox(ParseNode* pn, FunctionBox* funbox) {
         MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
         pn->pn_funbox = funbox;
     }
+    ParseNode* newFunctionDefinitionForAnnexB(ParseNode* pn, ParseNode* assignment) {
+        MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
+        MOZ_ASSERT(assignment->isKind(PNK_ASSIGN) || assignment->isKind(PNK_VAR));
+        return new_<BinaryNode>(PNK_ANNEXB_FUNCTION, JSOP_NOP, pos(), pn, assignment);
+    }
     void addFunctionArgument(ParseNode* pn, ParseNode* argpn) {
         pn->pn_body->append(argpn);
     }
     void setDerivedClassConstructor(ParseNode* pn) {
         MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
         pn->pn_funbox->setDerivedClassConstructor();
     }
 
@@ -722,17 +721,17 @@ class FullParseHandler
         return kind == PNK_FUNCTION || kind == PNK_VAR || kind == PNK_BREAK || kind == PNK_THROW ||
                (kind == PNK_SEMI && !node->pn_kid);
     }
 
     bool isSuperBase(ParseNode* node) {
         return node->isKind(PNK_SUPERBASE);
     }
 
-    inline bool finishInitializerAssignment(ParseNode* pn, ParseNode* init, JSOp op);
+    inline bool finishInitializerAssignment(ParseNode* pn, ParseNode* init);
     inline void setLexicalDeclarationOp(ParseNode* pn, JSOp op);
 
     void setBeginPosition(ParseNode* pn, ParseNode* oth) {
         setBeginPosition(pn, oth->pn_pos.begin);
     }
     void setBeginPosition(ParseNode* pn, uint32_t begin) {
         pn->pn_pos.begin = begin;
         MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
@@ -985,17 +984,17 @@ FullParseHandler::setLastFunctionArgumen
     ParseNode* arg = funcpn->pn_body->last();
     MOZ_ASSERT(arg->isKind(PNK_NAME));
     MOZ_ASSERT(!arg->isUsed());
     MOZ_ASSERT(arg->isDefn());
     arg->pn_expr = destruct;
 }
 
 inline bool
-FullParseHandler::finishInitializerAssignment(ParseNode* pn, ParseNode* init, JSOp op)
+FullParseHandler::finishInitializerAssignment(ParseNode* pn, ParseNode* init)
 {
     if (pn->isUsed()) {
         pn = makeAssignment(pn, init);
         if (!pn)
             return false;
     } else {
         pn->pn_expr = init;
     }
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -786,16 +786,22 @@ class NameResolver
 
           case PNK_FUNCTION:
           case PNK_MODULE:
             MOZ_ASSERT(cur->isArity(PN_CODE));
             if (!resolve(cur->pn_body, prefix))
                 return false;
             break;
 
+          case PNK_ANNEXB_FUNCTION:
+            MOZ_ASSERT(cur->isArity(PN_BINARY));
+            if (!resolve(cur->pn_left, prefix))
+                return false;
+            break;
+
           // Kinds that should be handled by parent node resolution.
 
           case PNK_IMPORT_SPEC: // by PNK_IMPORT_SPEC_LIST
           case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST
           case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE
           case PNK_CLASSNAMES:  // by PNK_CLASS
             MOZ_CRASH("should have been handled by a parent node");
 
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -278,17 +278,18 @@ PushNodeChildren(ParseNode* pn, NodeStac
       case PNK_DOWHILE:
       case PNK_WHILE:
       case PNK_SWITCH:
       case PNK_LETBLOCK:
       case PNK_CLASSMETHOD:
       case PNK_NEWTARGET:
       case PNK_SETTHIS:
       case PNK_FOR:
-      case PNK_COMPREHENSIONFOR: {
+      case PNK_COMPREHENSIONFOR:
+      case PNK_ANNEXB_FUNCTION: {
         MOZ_ASSERT(pn->isArity(PN_BINARY));
         stack->push(pn->pn_left);
         stack->push(pn->pn_right);
         return PushResult::Recyclable;
       }
 
       // Default clauses are PNK_CASE but do not have case expressions.
       // Named class expressions do not have outer binding nodes.
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -160,16 +160,17 @@ class PackedScopeCoordinate
     F(EXPORT_FROM) \
     F(EXPORT_DEFAULT) \
     F(EXPORT_SPEC_LIST) \
     F(EXPORT_SPEC) \
     F(EXPORT_BATCH_SPEC) \
     F(FORIN) \
     F(FOROF) \
     F(FORHEAD) \
+    F(ANNEXB_FUNCTION) \
     F(ARGSBODY) \
     F(SPREAD) \
     F(MUTATEPROTO) \
     F(CLASS) \
     F(CLASSMETHOD) \
     F(CLASSMETHODLIST) \
     F(CLASSNAMES) \
     F(NEWTARGET) \
@@ -266,16 +267,19 @@ IsDeleteKind(ParseNodeKind kind)
  *                            object containing arg and var properties.  We
  *                            create the function object at parse (not emit)
  *                            time to specialize arg and var bytecodes early.
  *                          pn_body: PNK_ARGSBODY, ordinarily;
  *                            PNK_LEXICALSCOPE for implicit function in genexpr
  *                          pn_scopecoord: hops and var index for function
  *                          pn_dflags: PND_* definition/use flags (see below)
  *                          pn_blockid: block id number
+ * PNK_ANNEXB_FUNCTION binary pn_left: PNK_FUNCTION
+ *                            pn_right: assignment for annex B semantics for
+ *                              block-scoped function
  * PNK_ARGSBODY list        list of formal parameters with
  *                              PNK_NAME node with non-empty name for
  *                                SingleNameBinding without Initializer
  *                              PNK_ASSIGN node for SingleNameBinding with
  *                                Initializer
  *                              PNK_NAME node with empty name for destructuring
  *                                pn_expr: PNK_ARRAY, PNK_OBJECT, or PNK_ASSIGN
  *                                  PNK_ARRAY or PNK_OBJECT for BindingPattern
@@ -775,17 +779,18 @@ class ParseNode
 
     bool functionIsHoisted() const {
         MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION);
         MOZ_ASSERT(isOp(JSOP_LAMBDA) ||        // lambda, genexpr
                    isOp(JSOP_LAMBDA_ARROW) ||  // arrow function
                    isOp(JSOP_DEFFUN) ||        // non-body-level function statement
                    isOp(JSOP_NOP) ||           // body-level function stmt in global code
                    isOp(JSOP_GETLOCAL) ||      // body-level function stmt in function code
-                   isOp(JSOP_GETARG));         // body-level function redeclaring formal
+                   isOp(JSOP_GETARG) ||        // body-level function redeclaring formal
+                   isOp(JSOP_INITLEXICAL));    // block-level function stmt
         return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN);
     }
 
     /*
      * True if this statement node could be a member of a Directive Prologue: an
      * expression statement consisting of a single string literal.
      *
      * This considers only the node and its children, not its context. After
@@ -1610,16 +1615,18 @@ struct Definition : public ParseNode
     bool canHaveInitializer() { return int(kind()) <= int(ARG); }
 
     static const char* kindString(Kind kind);
 
     Kind kind() {
         if (getKind() == PNK_FUNCTION) {
             if (isOp(JSOP_GETARG))
                 return ARG;
+            if (isOp(JSOP_INITLEXICAL))
+                return LET;
             return VAR;
         }
         MOZ_ASSERT(getKind() == PNK_NAME);
         if (isOp(JSOP_CALLEE))
             return NAMED_LAMBDA;
         if (isPlaceholder())
             return PLACEHOLDER;
         if (isOp(JSOP_GETARG))
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1194,28 +1194,16 @@ Parser<FullParseHandler>::standaloneFunc
 
     return fn;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::checkFunctionArguments()
 {
-    /*
-     * Non-top-level functions use JSOP_DEFFUN which is a dynamic scope
-     * operation which means it aliases any bindings with the same name.
-     */
-    if (FuncStmtSet* set = pc->funcStmts) {
-        for (FuncStmtSet::Range r = set->all(); !r.empty(); r.popFront()) {
-            PropertyName* name = r.front()->asPropertyName();
-            if (Definition* dn = pc->decls().lookupFirst(name))
-                dn->pn_dflags |= PND_CLOSED;
-        }
-    }
-
     /* Time to implement the odd semantics of 'arguments'. */
     HandlePropertyName arguments = context->names().arguments;
 
     /*
      * As explained by the ContextFlags::funArgumentsHasLocalBinding comment,
      * create a declaration for 'arguments' if there are any unbound uses in
      * the function body.
      */
@@ -1486,28 +1474,32 @@ struct BindData
 
     explicit BindData(ExclusiveContext* cx)
       : kind_(Uninitialized), nameNode_(ParseHandler::null()), letData_(cx)
     {}
 
     void initLexical(VarContext varContext, JSOp op, StaticBlockObject* blockObj,
                      unsigned overflow)
     {
-        init(LexicalBinding, op, op == JSOP_DEFCONST);
+        init(LexicalBinding, op, op == JSOP_DEFCONST, false);
         letData_.varContext = varContext;
         letData_.blockObj = blockObj;
         letData_.overflow = overflow;
     }
 
     void initVar(JSOp op) {
-        init(VarBinding, op, false);
+        init(VarBinding, op, false, false);
+    }
+
+    void initAnnexBVar() {
+        init(VarBinding, JSOP_DEFVAR, false, true);
     }
 
     void initDestructuring(JSOp op) {
-        init(DestructuringBinding, op, false);
+        init(DestructuringBinding, op, false, false);
     }
 
     void setNameNode(typename ParseHandler::Node pn) {
         MOZ_ASSERT(isInitialized());
         nameNode_ = pn;
     }
 
     typename ParseHandler::Node nameNode() {
@@ -1520,16 +1512,21 @@ struct BindData
         return op_;
     }
 
     bool isConst() {
         MOZ_ASSERT(isInitialized());
         return isConst_;
     }
 
+    bool isAnnexB() {
+        MOZ_ASSERT(isInitialized());
+        return isAnnexB_;
+    }
+
     const LetData& letData() {
         MOZ_ASSERT(kind_ == LexicalBinding);
         return letData_;
     }
 
     bool bind(HandlePropertyName name, Parser<ParseHandler>* parser) {
         MOZ_ASSERT(isInitialized());
         MOZ_ASSERT(nameNode_ != ParseHandler::null());
@@ -1556,27 +1553,29 @@ struct BindData
 
     BindingKind kind_;
 
     // Name node for definition processing and error source coordinates.
     typename ParseHandler::Node nameNode_;
 
     JSOp op_;         // Prologue bytecode or nop.
     bool isConst_;    // Whether this is a const binding.
+    bool isAnnexB_;   // Whether this is a synthesized 'var' binding for Annex B.3.
     LetData letData_;
 
     bool isInitialized() {
         return kind_ != Uninitialized;
     }
 
-    void init(BindingKind kind, JSOp op, bool isConst) {
+    void init(BindingKind kind, JSOp op, bool isConst, bool isAnnexB) {
         MOZ_ASSERT(!isInitialized());
         kind_ = kind;
         op_ = op;
         isConst_ = isConst;
+        isAnnexB_ = isAnnexB;
     }
 };
 
 template <typename ParseHandler>
 JSFunction*
 Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
                                   GeneratorKind generatorKind, HandleObject proto)
 {
@@ -2232,146 +2231,201 @@ Parser<ParseHandler>::functionArguments(
         return false;
     }
 
     return true;
 }
 
 template <>
 bool
+Parser<FullParseHandler>::bindBodyLevelFunctionName(HandlePropertyName funName,
+                                                    ParseNode** pn_)
+{
+    MOZ_ASSERT(pc->atBodyLevel() || !pc->sc->strict());
+
+    ParseNode*& pn = *pn_;
+
+    /*
+     * Handle redeclaration and optimize cases where we can statically bind the
+     * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
+     */
+    if (Definition* dn = pc->decls().lookupFirst(funName)) {
+        MOZ_ASSERT(!dn->isUsed());
+        MOZ_ASSERT(dn->isDefn());
+
+        if (dn->kind() == Definition::CONSTANT || dn->kind() == Definition::LET)
+            return reportRedeclaration(nullptr, Definition::VAR, funName);
+
+        /*
+         * Body-level function statements are effectively variable
+         * declarations where the initialization is hoisted to the
+         * beginning of the block. This means that any other variable
+         * declaration with the same name is really just an assignment to
+         * the function's binding (which is mutable), so turn any existing
+         * declaration into a use.
+         */
+        if (dn->kind() == Definition::ARG) {
+            // The exception to the above comment is when the function
+            // has the same name as an argument. Then the argument node
+            // remains a definition. But change the function node pn so
+            // that it knows where the argument is located.
+            pn->setOp(JSOP_GETARG);
+            pn->setDefn(true);
+            pn->pn_scopecoord = dn->pn_scopecoord;
+            pn->pn_blockid = dn->pn_blockid;
+            pn->pn_dflags |= PND_BOUND;
+            dn->markAsAssigned();
+        } else {
+            if (!makeDefIntoUse(dn, pn, funName))
+                return false;
+        }
+    } else {
+        /*
+         * If this function was used before it was defined, claim the
+         * pre-created definition node for this function that primaryExpr
+         * put in pc->lexdeps on first forward reference, and recycle pn.
+         */
+        if (Definition* fn = pc->lexdeps.lookupDefn<FullParseHandler>(funName)) {
+            MOZ_ASSERT(fn->isDefn());
+            fn->setKind(PNK_FUNCTION);
+            fn->setArity(PN_CODE);
+            fn->pn_pos.begin = pn->pn_pos.begin;
+            fn->pn_pos.end = pn->pn_pos.end;
+
+            fn->pn_body = nullptr;
+            fn->pn_scopecoord.makeFree();
+
+            pc->lexdeps->remove(funName);
+            handler.freeTree(pn);
+            pn = fn;
+        }
+
+        if (!pc->define(tokenStream, funName, pn, Definition::VAR))
+            return false;
+    }
+
+    /* No further binding (in BindNameToSlot) is needed for functions. */
+    pn->pn_dflags |= PND_BOUND;
+
+    MOZ_ASSERT(pn->functionIsHoisted());
+    MOZ_ASSERT(pc->sc->isGlobalContext() == pn->pn_scopecoord.isFree());
+
+    return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::bindLexicalFunctionName(HandlePropertyName funName,
+                                                  ParseNode* pn);
+
+template <>
+bool
 Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
                                                   ParseNode** pn_, FunctionSyntaxKind kind,
-                                                  bool* pbodyProcessed)
+                                                  bool* pbodyProcessed,
+                                                  ParseNode** assignmentForAnnexBOut)
 {
     ParseNode*& pn = *pn_;
     *pbodyProcessed = false;
 
-    /* Function statements add a binding to the enclosing scope. */
-    bool bodyLevel = pc->atBodyLevel();
-
     if (kind == Statement) {
-        /*
-         * Handle redeclaration and optimize cases where we can statically bind the
-         * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
-         */
-        if (Definition* dn = pc->decls().lookupFirst(funName)) {
-            MOZ_ASSERT(!dn->isUsed());
-            MOZ_ASSERT(dn->isDefn());
-
-            bool throwRedeclarationError = dn->kind() == Definition::CONSTANT ||
-                                           dn->kind() == Definition::LET;
-            if (options().extraWarningsOption || throwRedeclarationError) {
-                JSAutoByteString name;
-                ParseReportKind reporter = throwRedeclarationError
-                                           ? ParseError
-                                           : ParseExtraWarning;
-                if (!AtomToPrintableString(context, funName, &name) ||
-                    !report(reporter, false, nullptr, JSMSG_REDECLARED_VAR,
-                            Definition::kindString(dn->kind()), name.ptr()))
-                {
-                    return false;
-                }
-            }
-
-            /*
-             * Body-level function statements are effectively variable
-             * declarations where the initialization is hoisted to the
-             * beginning of the block. This means that any other variable
-             * declaration with the same name is really just an assignment to
-             * the function's binding (which is mutable), so turn any existing
-             * declaration into a use.
-             */
-            if (bodyLevel) {
-                if (dn->kind() == Definition::ARG) {
-                    // The exception to the above comment is when the function
-                    // has the same name as an argument. Then the argument node
-                    // remains a definition. But change the function node pn so
-                    // that it knows where the argument is located.
-                    pn->setOp(JSOP_GETARG);
-                    pn->setDefn(true);
-                    pn->pn_scopecoord = dn->pn_scopecoord;
-                    pn->pn_blockid = dn->pn_blockid;
-                    pn->pn_dflags |= PND_BOUND;
-                    dn->markAsAssigned();
+        MOZ_ASSERT(assignmentForAnnexBOut);
+        *assignmentForAnnexBOut = nullptr;
+
+        if (pc->atBodyLevel()) {
+            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.
+
+                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.
+                        annexDef = nullptr;
+                    }
                 } else {
-                    if (!makeDefIntoUse(dn, pn, funName))
+                    // Synthesize a new 'var' binding if one does not exist.
+                    ParseNode* varNode = newBindingNode(funName, /* functionScope = */ true);
+                    if (!varNode)
                         return false;
+
+                    // Treat the 'var' binding as body level. Otherwise the
+                    // lexical binding of the function name below would result
+                    // in a redeclaration. That is,
+                    // { var x; let x; } is an early error.
+                    // var x; { let x; } is not.
+                    varNode->pn_blockid = pc->bodyid;
+
+                    BindData<FullParseHandler> data(context);
+                    data.initAnnexBVar();
+                    data.setNameNode(varNode);
+                    if (!data.bind(funName, this))
+                        return false;
+
+                    MOZ_ASSERT(varNode->isDefn());
+                    annexDef = static_cast<Definition*>(varNode);
+
+                    synthesizedDeclarationList = handler.newDeclarationList(PNK_VAR, JSOP_DEFVAR);
+                    if (!synthesizedDeclarationList)
+                        return false;
+                    handler.addList(synthesizedDeclarationList, annexDef);
                 }
             }
-        } else if (bodyLevel) {
-            /*
-             * If this function was used before it was defined, claim the
-             * pre-created definition node for this function that primaryExpr
-             * put in pc->lexdeps on first forward reference, and recycle pn.
-             */
-            if (Definition* fn = pc->lexdeps.lookupDefn<FullParseHandler>(funName)) {
-                MOZ_ASSERT(fn->isDefn());
-                fn->setKind(PNK_FUNCTION);
-                fn->setArity(PN_CODE);
-                fn->pn_pos.begin = pn->pn_pos.begin;
-                fn->pn_pos.end = pn->pn_pos.end;
-
-                fn->pn_body = nullptr;
-                fn->pn_scopecoord.makeFree();
-
-                pc->lexdeps->remove(funName);
-                handler.freeTree(pn);
-                pn = fn;
-            }
-
-            if (!pc->define(tokenStream, funName, pn, Definition::VAR))
+
+            if (!bindLexicalFunctionName(funName, pn))
                 return false;
-        }
-
-        if (bodyLevel) {
-            MOZ_ASSERT(pn->functionIsHoisted());
-            MOZ_ASSERT(pc->sc->isGlobalContext() == pn->pn_scopecoord.isFree());
-        } else {
-            /*
-             * As a SpiderMonkey-specific extension, non-body-level function
-             * statements (e.g., functions in an "if" or "while" block) are
-             * dynamically bound when control flow reaches the statement.
-             */
-            MOZ_ASSERT(!pc->sc->strict());
-            MOZ_ASSERT(pn->pn_scopecoord.isFree());
-            if (pc->sc->isFunctionBox()) {
-                FunctionBox* funbox = pc->sc->asFunctionBox();
-                funbox->setMightAliasLocals();
-                funbox->setHasExtensibleScope();
-            }
-            pn->setOp(JSOP_DEFFUN);
-
-            /*
-             * Instead of setting bindingsAccessedDynamically, which would be
-             * overly conservative, remember the names of all function
-             * statements and mark any bindings with the same as aliased at the
-             * end of functionBody.
-             */
-            if (!pc->funcStmts) {
-                pc->funcStmts = alloc.new_<FuncStmtSet>(alloc);
-                if (!pc->funcStmts || !pc->funcStmts->init()) {
-                    ReportOutOfMemory(context);
+
+            if (annexDef) {
+                MOZ_ASSERT(!pc->sc->strict());
+
+                // Synthesize an assignment assigning the lexical name to the
+                // 'var' name for Annex B.
+
+                ParseNode* rhs = newName(funName);
+                if (!rhs)
+                    return false;
+                if (!noteNameUse(funName, rhs))
                     return false;
+
+                // If we synthesized a new definition, emit the declaration to
+                // ensure DEFVAR is correctly emitted in global scripts.
+                // Otherwise, synthesize a simple assignment and emit that.
+                if (synthesizedDeclarationList) {
+                    if (!handler.finishInitializerAssignment(annexDef, rhs))
+                        return false;
+                    *assignmentForAnnexBOut = synthesizedDeclarationList;
+                } else {
+                    ParseNode* lhs = newName(funName);
+                    if (!lhs)
+                        return false;
+                    lhs->setOp(JSOP_SETNAME);
+
+                    // Manually link up the LHS with the non-lexical definition.
+                    handler.linkUseToDef(lhs, annexDef);
+
+                    ParseNode* assign = handler.newAssignment(PNK_ASSIGN, lhs, rhs, pc, JSOP_NOP);
+                    if (!assign)
+                        return false;
+
+                    *assignmentForAnnexBOut = assign;
                 }
             }
-            if (!pc->funcStmts->put(funName))
-                return false;
-
-            /*
-             * Due to the implicit declaration mechanism, 'arguments' will not
-             * have decls and, even if it did, they will not be noted as closed
-             * in the emitter. Thus, in the corner case of function statements
-             * overridding arguments, flag the whole scope as dynamic.
-             */
-            if (funName == context->names().arguments)
-                pc->sc->setBindingsAccessedDynamically();
-        }
-
-        /* No further binding (in BindNameToSlot) is needed for functions. */
-        pn->pn_dflags |= PND_BOUND;
+        }
     } else {
         /* A function expression does not introduce any binding. */
         pn->setOp(kind == Arrow ? JSOP_LAMBDA_ARROW : JSOP_LAMBDA);
     }
 
     // When a lazily-parsed function is called, we only fully parse (and emit)
     // that function, not any of its nested children. The initial syntax-only
     // parse recorded the free variables of nested functions and their extents,
@@ -2470,48 +2524,54 @@ Parser<ParseHandler>::addFreeVariablesFr
     PropagateTransitiveParseFlags(lazy, pc->sc);
     return true;
 }
 
 template <>
 bool
 Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
                                                     Node* pn, FunctionSyntaxKind kind,
-                                                    bool* pbodyProcessed)
+                                                    bool* pbodyProcessed,
+                                                    Node* assignmentForAnnexBOut)
 {
     *pbodyProcessed = false;
 
     /* Function statements add a binding to the enclosing scope. */
     bool bodyLevel = pc->atBodyLevel();
 
     if (kind == Statement) {
+        *assignmentForAnnexBOut = null();
+
+        if (!bodyLevel) {
+            // Block-scoped functions cannot yet be parsed lazily.
+            return abortIfSyntaxParser();
+        }
+
         /*
          * Handle redeclaration and optimize cases where we can statically bind the
          * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup).
          */
+
         if (DefinitionNode dn = pc->decls().lookupFirst(funName)) {
             if (dn == Definition::CONSTANT || dn == Definition::LET) {
                 JSAutoByteString name;
                 if (!AtomToPrintableString(context, funName, &name) ||
                     !report(ParseError, false, null(), JSMSG_REDECLARED_VAR,
                             Definition::kindString(dn), name.ptr()))
                 {
                     return false;
                 }
             }
-        } else if (bodyLevel) {
+        } else {
             if (pc->lexdeps.lookupDefn<SyntaxParseHandler>(funName))
                 pc->lexdeps->remove(funName);
 
             if (!pc->define(tokenStream, funName, *pn, Definition::VAR))
                 return false;
         }
-
-        if (!bodyLevel && funName == context->names().arguments)
-            pc->sc->setBindingsAccessedDynamically();
     }
 
     if (kind == Arrow) {
         /* Arrow functions cannot yet be parsed lazily. */
         return abortIfSyntaxParser();
     }
 
     return true;
@@ -2582,30 +2642,32 @@ Parser<ParseHandler>::templateLiteral(Yi
     } while (tt == TOK_TEMPLATE_HEAD);
     return nodeList;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::functionDef(InHandling inHandling, YieldHandling yieldHandling,
                                   HandlePropertyName funName, FunctionSyntaxKind kind,
-                                  GeneratorKind generatorKind, InvokedPrediction invoked)
+                                  GeneratorKind generatorKind, InvokedPrediction invoked,
+                                  Node* assignmentForAnnexBOut)
 {
     MOZ_ASSERT_IF(kind == Statement, funName);
 
     /* Make a TOK_FUNCTION node. */
     Node pn = handler.newFunctionDefinition();
     if (!pn)
         return null();
+    handler.setBlockId(pn, pc->blockid());
 
     if (invoked)
         pn = handler.setLikelyIIFE(pn);
 
     bool bodyProcessed;
-    if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed))
+    if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed, assignmentForAnnexBOut))
         return null();
 
     if (bodyProcessed)
         return pn;
 
     RootedObject proto(context);
     if (generatorKind == StarGenerator) {
         // If we are off the main thread, the generator meta-objects have
@@ -2782,17 +2844,16 @@ Parser<FullParseHandler>::functionArgsAn
 
             // Update the end position of the parse node.
             pn->pn_pos.end = tokenStream.currentToken().pos.end;
         }
 
         if (!addFreeVariablesFromLazyFunction(fun, pc))
             return false;
 
-        pn->pn_blockid = outerpc->blockid();
         PropagateTransitiveParseFlags(funbox, outerpc->sc);
         return true;
     } while (false);
 
     blockScopes.resize(oldBlockScopesLength);
 
     // Continue doing a full parse for this inner function.
     ParseContext<FullParseHandler> funpc(this, pc, pn, funbox, newDirectives);
@@ -2800,18 +2861,16 @@ Parser<FullParseHandler>::functionArgsAn
         return false;
 
     if (!functionArgsAndBodyGeneric(inHandling, yieldHandling, pn, fun, kind))
         return false;
 
     if (!leaveFunction(pn, outerpc, kind))
         return false;
 
-    pn->pn_blockid = outerpc->blockid();
-
     /*
      * Fruit of the poisonous tree: if a closure contains a dynamic name access
      * (eval, with, etc), we consider the parent to do the same. The reason is
      * that the deoptimizing effects of dynamic name access apply equally to
      * parents: any local can be read at runtime.
      */
     PropagateTransitiveParseFlags(funbox, outerpc->sc);
     return true;
@@ -3043,16 +3102,34 @@ Parser<ParseHandler>::checkYieldNameVali
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling)
 {
     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();
+    }
+
     RootedPropertyName name(context);
     GeneratorKind generatorKind = NotGenerator;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
     if (tt == TOK_MUL) {
         generatorKind = StarGenerator;
@@ -3070,22 +3147,45 @@ Parser<ParseHandler>::functionStmt(Yield
         name = context->names().starDefaultStar;
         tokenStream.ungetToken();
     } else {
         /* Unnamed function expressions are forbidden in statement context. */
         report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT);
         return null();
     }
 
-    /* We forbid function statements in strict mode code. */
-    if (!pc->atBodyLevel() && pc->sc->needStrictChecks() &&
-        !report(ParseStrictError, pc->sc->strict(), null(), JSMSG_STRICT_FUNCTION_STATEMENT))
-        return null();
-
-    return functionDef(InAllowed, yieldHandling, name, Statement, generatorKind);
+    Node assignmentForAnnexB;
+    Node fun = functionDef(InAllowed, yieldHandling, name, Statement, generatorKind,
+                           PredictUninvoked, &assignmentForAnnexB);
+    if (!fun)
+        return null();
+
+    if (assignmentForAnnexB) {
+        fun = handler.newFunctionDefinitionForAnnexB(fun, assignmentForAnnexB);
+        if (!fun)
+            return null();
+    }
+
+    // Note that we may have synthesized a block for Annex B.3.4 without
+    // having synthesized an assignment for Annex B.3.3, e.g.,
+    //
+    //   let f = 1;
+    //   {
+    //     if (1) function f() {}
+    //   }
+    if (synthesizedBlockForAnnexB) {
+        Node body = handler.newStatementList(pc->blockid(), handler.getPosition(fun));
+        if (!body)
+            return null();
+        handler.addStatementToList(body, fun, pc);
+        handler.setLexicalScopeBody(synthesizedBlockForAnnexB, body);
+        return synthesizedBlockForAnnexB;
+    }
+
+    return fun;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::functionExpr(InvokedPrediction invoked)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
@@ -3679,29 +3779,34 @@ Parser<ParseHandler>::bindVar(BindData<P
     parser->handler.setOp(pn, JSOP_GETNAME);
 
     if (!parser->checkStrictBinding(name, pn))
         return false;
 
     StmtInfoPC* stmt = LexicalLookup(pc, name);
 
     if (stmt && stmt->type == StmtType::WITH) {
-        parser->handler.setFlag(pn, PND_DEOPTIMIZED);
-        if (pc->sc->isFunctionBox()) {
-            FunctionBox* funbox = pc->sc->asFunctionBox();
-            funbox->setMightAliasLocals();
-        }
-
-        /*
-         * Make sure to indicate the need to deoptimize the script's arguments
-         * object. Mark the function as if it contained a debugger statement,
-         * which will deoptimize arguments as much as possible.
-         */
-        if (name == cx->names().arguments)
-            pc->sc->setHasDebuggerStatement();
+        // Do not deoptimize if we are binding a synthesized 'var' binding for
+        // Annex B.3.3, which states that the synthesized binding is to go on
+        // the nearest VariableEnvironment. Deoptimizing here would
+        // erroneously emit NAME ops when assigning to the Annex B 'var'.
+        if (!data->isAnnexB()) {
+            parser->handler.setFlag(pn, PND_DEOPTIMIZED);
+            if (pc->sc->isFunctionBox()) {
+                FunctionBox* funbox = pc->sc->asFunctionBox();
+                funbox->setMightAliasLocals();
+            }
+
+            // Make sure to indicate the need to deoptimize the script's
+            // arguments object. Mark the function as if it contained a
+            // debugger statement, which will deoptimize arguments as much as
+            // possible.
+            if (name == cx->names().arguments)
+                pc->sc->setHasDebuggerStatement();
+        }
 
         // Find the nearest enclosing non-with scope that defined name, if
         // any, for redeclaration checks below.
         while (stmt && stmt->type == StmtType::WITH) {
             if (stmt->enclosingScope)
                 stmt = LexicalLookup(pc, name, stmt->enclosingScope);
             else
                 stmt = nullptr;
@@ -4423,17 +4528,17 @@ Parser<ParseHandler>::variables(YieldHan
                         }
                     }
                 }
 
                 if (performAssignment) {
                     if (!bindBeforeInitializer && !data.bind(name, this))
                         return null();
 
-                    if (!handler.finishInitializerAssignment(pn2, init, data.op()))
+                    if (!handler.finishInitializerAssignment(pn2, init))
                         return null();
                 }
             }
 
             handler.setLexicalDeclarationOp(pn2, data.op());
             handler.setEndPosition(pn, pn2);
         } while (false);
 
@@ -4447,21 +4552,21 @@ Parser<ParseHandler>::variables(YieldHan
     return pn;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos& errorPos)
 {
     /*
-     * This is a lexical declaration. We must be directly under a block per the
-     * proposed ES4 specs, 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, 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.
      *
      * 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();
     if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
         reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
@@ -4541,17 +4646,17 @@ template <>
 bool
 Parser<FullParseHandler>::prepareAndBindInitializedLexicalWithNode(HandlePropertyName name,
                                                                    bool isConst,
                                                                    ParseNode* pn,
                                                                    const TokenPos& pos)
 {
     BindData<FullParseHandler> data(context);
     if (!checkAndPrepareLexical(isConst, pos))
-        return null();
+        return false;
     data.initLexical(HoistVars, isConst ? JSOP_DEFCONST : JSOP_DEFLET,
                      CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS);
     return bindInitialized(&data, name, pn);
 }
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst,
@@ -4564,16 +4669,26 @@ Parser<FullParseHandler>::makeInitialize
 
     if (!prepareAndBindInitializedLexicalWithNode(name, isConst, 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());
+}
+
+template <>
 ParseNode*
 Parser<FullParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool isConst)
 {
     handler.disableSyntaxParser();
 
     if (!checkAndPrepareLexical(isConst, pos()))
         return null();
 
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -48,17 +48,16 @@ struct StmtInfoPC : public StmtInfoBase
     explicit StmtInfoPC(ExclusiveContext* cx)
       : StmtInfoBase(cx),
         blockid(BlockIdLimit),
         innerBlockScopeDepth(0),
         firstDominatingLexicalInCase(0)
     {}
 };
 
-typedef HashSet<JSAtom*, DefaultHasher<JSAtom*>, LifoAllocPolicy<Fallible>> FuncStmtSet;
 class SharedContext;
 
 typedef Vector<Definition*, 16> DeclVector;
 
 struct GenericParseContext
 {
     // Enclosing function or global context.
     GenericParseContext* parent;
@@ -231,20 +230,16 @@ struct MOZ_STACK_CLASS ParseContext : pu
     // Value for parserPC to restore at the end. Use 'parent' instead for
     // information about the parse chain, this may be nullptr if
     // parent != nullptr.
     ParseContext<ParseHandler>* oldpc;
 
   public:
     OwnedAtomDefnMapPtr lexdeps;    /* unresolved lexical name dependencies */
 
-    FuncStmtSet*    funcStmts;     /* Set of (non-top-level) function statements
-                                       that will alias any top-level bindings with
-                                       the same name. */
-
     // All inner functions in this context. Only filled in when parsing syntax.
     Rooted<TraceableVector<JSFunction*>> innerFunctions;
 
     // In a function context, points to a Directive struct that can be updated
     // to reflect new directives encountered in the Directive Prologue that
     // require reparsing the function. In global/module/generator-tail contexts,
     // we don't need to reparse when encountering a DirectivePrologue so this
     // pointer may be nullptr.
@@ -272,17 +267,16 @@ struct MOZ_STACK_CLASS ParseContext : pu
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
         vars_(prs->context),
         bodyLevelLexicals_(prs->context),
         parserPC(&prs->pc),
         oldpc(prs->pc),
         lexdeps(prs->context),
-        funcStmts(nullptr),
         innerFunctions(prs->context, TraceableVector<JSFunction*>(prs->context)),
         newDirectives(newDirectives),
         inDeclDestructuring(false)
     {
         prs->pc = this;
         if (sc->isFunctionBox())
             parseUsingFunctionBox.emplace(prs->context, sc->asFunctionBox());
     }
@@ -723,17 +717,18 @@ class Parser : private JS::AutoGCRooter,
     /*
      * Additional JS parsers.
      */
     bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
                            Node funcpn, bool* hasRest);
 
     Node functionDef(InHandling inHandling, YieldHandling uieldHandling, HandlePropertyName name,
                      FunctionSyntaxKind kind, GeneratorKind generatorKind,
-                     InvokedPrediction invoked = PredictUninvoked);
+                     InvokedPrediction invoked = PredictUninvoked,
+                     Node* assignmentForAnnexBOut = nullptr);
     bool functionArgsAndBody(InHandling inHandling, Node pn, HandleFunction fun,
                              FunctionSyntaxKind kind, GeneratorKind generatorKind,
                              Directives inheritedDirectives, Directives* newDirectives);
 
     Node unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, JSOp op, uint32_t begin);
 
     Node condition(InHandling inHandling, YieldHandling yieldHandling);
 
@@ -790,18 +785,20 @@ class Parser : private JS::AutoGCRooter,
     bool matchInOrOf(bool* isForInp, bool* isForOfp);
 
     bool checkFunctionArguments();
 
     bool defineFunctionThis();
     Node newThisName();
 
     bool makeDefIntoUse(Definition* dn, Node pn, HandleAtom atom);
+    bool bindLexicalFunctionName(HandlePropertyName funName, ParseNode* pn);
+    bool bindBodyLevelFunctionName(HandlePropertyName funName, ParseNode** pn);
     bool checkFunctionDefinition(HandlePropertyName funName, Node* pn, FunctionSyntaxKind kind,
-                                 bool* pbodyProcessed);
+                                 bool* pbodyProcessed, Node* assignmentForAnnexBOut);
     bool finishFunctionDefinition(Node pn, FunctionBox* funbox, Node body);
     bool addFreeVariablesFromLazyFunction(JSFunction* fun, ParseContext<ParseHandler>* pc);
 
     bool isValidForStatementLHS(Node pn1, JSVersion version, bool forDecl, bool forEach,
                                 ParseNodeKind headKind);
     bool checkForHeadConstInitializers(Node pn1);
 
     // Use when the current token is TOK_NAME and is known to be 'let'.
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -328,16 +328,17 @@ class SyntaxParseHandler
     bool addCatchBlock(Node catchList, Node letBlock,
                        Node catchName, Node catchGuard, Node catchBody) { return true; }
 
     bool setLastFunctionArgumentDefault(Node funcpn, Node pn) { return true; }
     void setLastFunctionArgumentDestructuring(Node funcpn, Node pn) {}
     Node newFunctionDefinition() { return NodeHoistableDeclaration; }
     void setFunctionBody(Node pn, Node kid) {}
     void setFunctionBox(Node pn, FunctionBox* funbox) {}
+    Node newFunctionDefinitionForAnnexB(Node pn, Node assignment) { return NodeHoistableDeclaration; }
     void addFunctionArgument(Node pn, Node argpn) {}
 
     Node newForStatement(uint32_t begin, Node forHead, Node body, unsigned iflags) {
         return NodeGeneric;
     }
 
     Node newComprehensionFor(uint32_t begin, Node forHead, Node body) {
         return NodeGeneric;
@@ -349,17 +350,17 @@ class SyntaxParseHandler
 
     Node newLexicalScope(ObjectBox* blockbox) { return NodeGeneric; }
     void setLexicalScopeBody(Node block, Node body) {}
 
     Node newLetBlock(Node vars, Node block, const TokenPos& pos) {
         return NodeGeneric;
     }
 
-    bool finishInitializerAssignment(Node pn, Node init, JSOp op) { return true; }
+    bool finishInitializerAssignment(Node pn, Node init) { return true; }
     void setLexicalDeclarationOp(Node pn, JSOp op) {}
 
     void setBeginPosition(Node pn, Node oth) {}
     void setBeginPosition(Node pn, uint32_t begin) {}
 
     void setEndPosition(Node pn, Node oth) {}
     void setEndPosition(Node pn, uint32_t end) {}
 
--- a/js/src/jit-test/tests/auto-regress/bug771027.js
+++ b/js/src/jit-test/tests/auto-regress/bug771027.js
@@ -1,9 +1,9 @@
 // |jit-test| error:TypeError
 
 // Binary: cache/js-dbg-32-b6aa44d8f11f-linux
 // Flags:
 //
 
-Array.prototype.iterator = (function() { { while(0) function Uint8ClampedArray() {  } } });
+Array.prototype.iterator = (function() { { while(0) { function Uint8ClampedArray() {  } } } });
 var s = new Set(["testing", "testing", 123]);
 assertEq(s.size(), 2);
deleted file mode 100644
--- a/js/src/jit-test/tests/baseline/bug1081850.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// |jit-test| ion-eager
-
-var ARR = [];
-try {
-    function f() {
-        ARR.push(eval.prototype)
-    }
-    f()
-    function eval()(0)
-    f()
-} catch (e) {}
-
-if (ARR.length !== 2)
-    throw new Error("ERROR 1");
-if (typeof(ARR[0]) !== 'undefined')
-    throw new Error("ERROR 2");
-if (typeof(ARR[1]) !== 'object')
-    throw new Error("ERROR 3");
--- a/js/src/jit-test/tests/basic/bug667504-syntax.js
+++ b/js/src/jit-test/tests/basic/bug667504-syntax.js
@@ -1,2 +1,3 @@
-for (var x in x)
+for (var x in x) {
 function x() {}
+}
--- a/js/src/jit-test/tests/ion/bug1148973-1.js
+++ b/js/src/jit-test/tests/ion/bug1148973-1.js
@@ -3,12 +3,16 @@ try {
     String(b = Proxy.createFunction(function() {
         return {
             get: function(r, z) {
                 return x[z]
             }
         }
     }(), function() {}))
 } catch (e) {};
+var log = "";
+evaluate(`
 try {
     function x() {}
     assertEq(String(b), "function () {}");
-} catch (e) { throw (e); }
+} catch (e) { log += "e"; }
+`);
+assertEq(log, "e");
--- a/js/src/tests/ecma_5/extensions/function-definition-with.js
+++ b/js/src/tests/ecma_5/extensions/function-definition-with.js
@@ -14,38 +14,41 @@ print(BUGNUMBER + ": " + summary);
  * BEGIN TEST *
  **************/
 
 var called, obj;
 
 function inFile1() { return "in file"; }
 called = false;
 obj = { set inFile1(v) { called = true; } };
-with (obj)
+with (obj) {
   function inFile1() { return "in file in with"; };
+}
 assertEq(inFile1(), "in file in with");
 assertEq("set" in Object.getOwnPropertyDescriptor(obj, "inFile1"), true);
 assertEq(called, false);
 
 evaluate("function notInFile1() { return 'not in file'; }");
 called = false;
 obj = { set notInFile1(v) { called = true; return "not in file 2"; } };
-with (obj)
+with (obj) {
   function notInFile1() { return "not in file in with"; };
+}
 assertEq(notInFile1(), "not in file in with");
 assertEq("set" in Object.getOwnPropertyDescriptor(obj, "notInFile1"), true);
 assertEq(called, false);
 
 function inFile2() { return "in file 1"; }
 called = false;
 obj =
   Object.defineProperty({}, "inFile2",
                         { value: 42, configurable: false, enumerable: false });
-with (obj)
+with (obj) {
   function inFile2() { return "in file 2"; };
+}
 assertEq(inFile2(), "in file 2");
 assertEq(obj.inFile2, 42);
 
 
 /******************************************************************************/
 
 if (typeof reportCompare === "function")
   reportCompare(true, true);
--- a/js/src/tests/ecma_5/extensions/strict-function-statements.js
+++ b/js/src/tests/ecma_5/extensions/strict-function-statements.js
@@ -4,97 +4,91 @@
  */
 
 // Ordinary function definitions should be unaffected.
 assertEq(testLenientAndStrict("function f() { }",
                               parsesSuccessfully,
                               parsesSuccessfully),
          true);
 
-// Function statements within blocks are forbidden in strict mode code.
-assertEq(testLenientAndStrict("{ function f() { } }",
-                              parsesSuccessfully,
-                              parseRaisesException(SyntaxError)),
-         true);
-
 // Lambdas are always permitted within blocks.
 assertEq(testLenientAndStrict("{ (function f() { }) }",
                               parsesSuccessfully,
                               parsesSuccessfully),
          true);
 
-// Function statements within any sort of statement are forbidden in strict mode code.
+// Function statements within unbraced blocks are forbidden in strict mode code.
+// They are allowed only under if statements in sloppy mode.
 assertEq(testLenientAndStrict("if (true) function f() { }",
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("while (true) function f() { }",
-                              parsesSuccessfully,
+                              parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("do function f() { } while (true);",
-                              parsesSuccessfully,
+                              parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("for(;;) function f() { }",
-                              parsesSuccessfully,
+                              parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("for(x in []) function f() { }",
-                              parsesSuccessfully,
+                              parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("with(o) function f() { }",
-                              parsesSuccessfully,
+                              parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("switch(1) { case 1: function f() { } }",
                               parsesSuccessfully,
-                              parseRaisesException(SyntaxError)),
+                              parsesSuccessfully),
          true);
 assertEq(testLenientAndStrict("x: function f() { }",
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict("try { function f() { } } catch (x) { }",
                               parsesSuccessfully,
-                              parseRaisesException(SyntaxError)),
+                              parsesSuccessfully),
          true);
 
 // Lambdas are always permitted within any sort of statement.
 assertEq(testLenientAndStrict("if (true) (function f() { })",
                               parsesSuccessfully,
                               parsesSuccessfully),
          true);
 
 // Function statements are permitted in blocks within lenient functions.
 assertEq(parsesSuccessfully("function f() { function g() { } }"),
          true);
 
-// Function statements are permitted in any statement within lenient functions.
+// Function statements are permitted in if statement within lenient functions.
 assertEq(parsesSuccessfully("function f() { if (true) function g() { } }"),
          true);
 
 assertEq(parseRaisesException(SyntaxError)
          ("function f() { 'use strict'; if (true) function g() { } }"),
          true);
 
-assertEq(parseRaisesException(SyntaxError)
-         ("function f() { 'use strict'; { function g() { } } }"),
+assertEq(parsesSuccessfully("function f() { 'use strict'; { function g() { } } }"),
          true);
 
 assertEq(parsesSuccessfully("function f() { 'use strict'; if (true) (function g() { }) }"),
          true);
 
 assertEq(parsesSuccessfully("function f() { 'use strict'; { (function g() { }) } }"),
          true);
 
 // Eval should behave the same way. (The parse-only tests use the Function constructor.)
 assertEq(testLenientAndStrict("function f() { }",
                               completesNormally,
                               completesNormally),
          true);
 assertEq(testLenientAndStrict("{ function f() { } }",
                               completesNormally,
-                              raisesException(SyntaxError)),
+                              completesNormally),
          true);
 
 reportCompare(true, true);
--- a/js/src/tests/ecma_6/extensions/for-loop-with-lexical-declaration-and-nested-function-statement.js
+++ b/js/src/tests/ecma_6/extensions/for-loop-with-lexical-declaration-and-nested-function-statement.js
@@ -13,36 +13,40 @@ var summary =
   "bindings at runtime";
 
 print(BUGNUMBER + ": " + summary);
 
 /**************
  * BEGIN TEST *
  **************/
 
-for (let x = 0; x < 9; ++x)
+for (let x = 0; x < 9; ++x) {
   function q1() {}
+}
 
 {
-  for (let x = 0; x < 9; ++x)
+  for (let x = 0; x < 9; ++x) {
     function q2() {}
+  }
 }
 
 function f1()
 {
-  for (let x = 0; x < 9; ++x)
+  for (let x = 0; x < 9; ++x) {
     function q3() {}
+  }
 }
 f1();
 
 function f2()
 {
   {
-    for (let x = 0; x < 9; ++x)
+    for (let x = 0; x < 9; ++x) {
       function q4() {}
+    }
   }
 }
 f2();
 
 for (let x = 0; x < 9; ++x)
 {
   // deliberately inside a block statement
   function q5() {}
deleted file mode 100644
--- a/js/src/tests/js1_5/Regress/regress-326453.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/*
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/licenses/publicdomain/
- * Contributor: Blake Kaplan
- */
-
-//-----------------------------------------------------------------------------
-var BUGNUMBER = 326453;
-var summary = 'Do not assert: while decompiling';
-var actual = 'No Crash';
-var expect = 'No Crash';
-
-printBugNumber(BUGNUMBER);
-printStatus (summary);
-
-function f() { with({})function g() { }; printStatus(); }
-
-printStatus(f.toString());
-
-reportCompare(expect, actual, summary);
--- a/js/src/tests/js1_5/extensions/regress-245795.js
+++ b/js/src/tests/js1_5/extensions/regress-245795.js
@@ -7,28 +7,25 @@
 var BUGNUMBER = 245795;
 var summary = 'eval(uneval(function)) should be round-trippable';
 var actual = '';
 var expect = '';
 
 printBugNumber(BUGNUMBER);
 printStatus (summary);
 
-if (typeof uneval != 'undefined')
+function a()
 {
-  function a()
-  {
-    b = function() {};
-  }
+  b = function() {};
+}
 
-  var r = "function a() { b = function() {}; }";
-  eval(uneval(a));
+var r = "function a() { b = function() {}; }";
+eval(uneval(a));
 
-  var v = a.toString().replace(/[ \n]+/g, ' ');
-  print(v)
- 
-  printStatus("[" + v + "]");
+var v = a.toString().replace(/[ \n]+/g, ' ');
+print(v)
+
+printStatus("[" + v + "]");
 
-  expect = r;
-  actual = v;
+expect = r;
+actual = v;
 
-  reportCompare(expect, actual, summary);
-}
+reportCompare(expect, actual, summary);
--- a/js/src/tests/js1_5/extensions/regress-406572.js
+++ b/js/src/tests/js1_5/extensions/regress-406572.js
@@ -17,22 +17,24 @@ if (typeof window != 'undefined')
   try {
     actual = "FAIL: Unexpected exception thrown";
 
     var win = window;
     var windowString = String(window);
     window = 1;
     reportCompare(windowString, String(window), "window should be readonly");
 
-    actual = ""; // We should reach this line, and throw an exception after it
-
     if (1)
       function window() { return 1; }
 
-    actual = "FAIL: this line should never be reached";
+    // We should reach this line without throwing. Annex B means the
+    // block-scoped function above gets an assignment to 'window' in the
+    // nearest 'var' environment, but since 'window' is read-only, the
+    // assignment silently fails.
+    actual = "";
 
     // The test harness might rely on window having its original value:
     // restore it.
     window = win;
   } catch (e) {
   }
 }
 else
--- a/js/src/tests/js1_8_5/reflect-parse/declarations.js
+++ b/js/src/tests/js1_8_5/reflect-parse/declarations.js
@@ -1,15 +1,15 @@
 // |reftest| skip-if(!xulRuntime.shell)
 function test() {
 
 // Bug 632056: constant-folding
 program([exprStmt(ident("f")),
          ifStmt(lit(1),
-                funDecl(ident("f"), [], blockStmt([])),
+                blockStmt([funDecl(ident("f"), [], blockStmt([]))]),
                 null)]).assert(Reflect.parse("f; if (1) function f(){}"));
 // declarations
 
 assertDecl("var x = 1, y = 2, z = 3",
            varDecl([{ id: ident("x"), init: lit(1) },
                     { id: ident("y"), init: lit(2) },
                     { id: ident("z"), init: lit(3) }]));
 assertDecl("var x, y, z",
@@ -81,9 +81,17 @@ assertStmt("function g(x) { var x }",
            funDecl(ident("g"), [ident("x")], blockStmt([varDecl[{ id: ident("x"), init: null }]])));
 assertProg("f.p = 1; var f; f.p; function f(){}",
            [exprStmt(aExpr("=", dotExpr(ident("f"), ident("p")), lit(1))),
             varDecl([{ id: ident("f"), init: null }]),
             exprStmt(dotExpr(ident("f"), ident("p"))),
             funDecl(ident("f"), [], blockStmt([]))]);
 }
 
+assertBlockStmt("{ function f(x) {} }",
+                blockStmt([funDecl(ident("f"), [ident("x")], blockStmt([]))]));
+
+// Annex B semantics should not change parse tree.
+assertBlockStmt("{ let f; { function f(x) {} } }",
+                blockStmt([letDecl([{ id: ident("f"), init: null }]),
+                           blockStmt([funDecl(ident("f"), [ident("x")], blockStmt([]))])]));
+
 runtest(test);