diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index abe869be4..53edb08d7 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2040,6 +2040,9 @@ ASTSerializer::declaration(ParseNode* pn, MutableHandleValue dst) 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); @@ -2411,6 +2414,9 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) case PNK_VAR: return declaration(pn, dst); + case PNK_ANNEXB_FUNCTION: + return declaration(pn->pn_left, dst); + case PNK_LETBLOCK: return letBlock(pn, dst); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 0a6cc35ad..4fdd307cc 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2344,6 +2344,10 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) 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; @@ -5326,6 +5330,23 @@ BytecodeEmitter::emitLetBlock(ParseNode* pnLet) 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 @@ -5337,7 +5358,17 @@ BytecodeEmitter::emitLexicalScope(ParseNode* pn) 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)) @@ -6198,6 +6229,12 @@ BytecodeEmitter::emitComprehensionFor(ParseNode* 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()); @@ -6208,9 +6245,24 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) * 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; } @@ -6318,7 +6370,15 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) * 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()); @@ -7958,7 +8018,6 @@ BytecodeEmitter::emitArgsBody(ParseNode *pn) // 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; @@ -8019,21 +8078,11 @@ BytecodeEmitter::emitArgsBody(ParseNode *pn) } } 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); } @@ -8252,6 +8301,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) switch (pn->getKind()) { case PNK_FUNCTION: + case PNK_ANNEXB_FUNCTION: if (!emitFunction(pn)) return false; break; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 57837e275..81269d35a 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -464,6 +464,8 @@ struct BytecodeEmitter 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 diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index f226310c2..1c80e99db 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -100,6 +100,11 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) *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; @@ -1783,6 +1788,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo 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); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 031f78ec7..4368f6dd0 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -448,17 +448,11 @@ class FullParseHandler 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); @@ -656,6 +650,11 @@ class FullParseHandler 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_(PNK_ANNEXB_FUNCTION, JSOP_NOP, pos(), pn, assignment); + } void addFunctionArgument(ParseNode* pn, ParseNode* argpn) { pn->pn_body->append(argpn); } @@ -727,7 +726,7 @@ class FullParseHandler 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) { @@ -990,7 +989,7 @@ FullParseHandler::setLastFunctionArgumentDestructuring(ParseNode* funcpn, ParseN } inline bool -FullParseHandler::finishInitializerAssignment(ParseNode* pn, ParseNode* init, JSOp op) +FullParseHandler::finishInitializerAssignment(ParseNode* pn, ParseNode* init) { if (pn->isUsed()) { pn = makeAssignment(pn, init); diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index cbc6578b1..d9f97c1c5 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -791,6 +791,12 @@ class NameResolver 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 diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 245831c07..0dfeb0cc4 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -283,7 +283,8 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) 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); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index d70a06892..f39437867 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -165,6 +165,7 @@ class PackedScopeCoordinate F(FORIN) \ F(FOROF) \ F(FORHEAD) \ + F(ANNEXB_FUNCTION) \ F(ARGSBODY) \ F(SPREAD) \ F(MUTATEPROTO) \ @@ -271,6 +272,9 @@ IsDeleteKind(ParseNodeKind kind) * 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 @@ -780,7 +784,8 @@ class ParseNode 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); } @@ -1615,6 +1620,8 @@ struct Definition : public ParseNode if (getKind() == PNK_FUNCTION) { if (isOp(JSOP_GETARG)) return ARG; + if (isOp(JSOP_INITLEXICAL)) + return LET; return VAR; } MOZ_ASSERT(getKind() == PNK_NAME); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 7ab53d311..347baffd9 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1203,18 +1203,6 @@ template <> bool Parser::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; @@ -1495,18 +1483,22 @@ struct BindData 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) { @@ -1529,6 +1521,11 @@ struct BindData return isConst_; } + bool isAnnexB() { + MOZ_ASSERT(isInitialized()); + return isAnnexB_; + } + const LetData& letData() { MOZ_ASSERT(kind_ == LexicalBinding); return letData_; @@ -1565,17 +1562,19 @@ struct BindData 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; } }; @@ -2239,138 +2238,193 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn return true; } +template <> +bool +Parser::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(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::bindLexicalFunctionName(HandlePropertyName funName, + ParseNode* pn); + template <> bool Parser::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()); + MOZ_ASSERT(assignmentForAnnexBOut); + *assignmentForAnnexBOut = nullptr; - 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(); - } else { - if (!makeDefIntoUse(dn, pn, funName)) - return false; - } - } - } 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(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 (pc->atBodyLevel()) { + if (!bindBodyLevelFunctionName(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); + Definition* annexDef = nullptr; + Node synthesizedDeclarationList = null(); - /* - * 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_(alloc); - if (!pc->funcStmts || !pc->funcStmts->init()) { - ReportOutOfMemory(context); - return false; + 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 { + // 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 data(context); + data.initAnnexBVar(); + data.setNameNode(varNode); + if (!data.bind(funName, this)) + return false; + + MOZ_ASSERT(varNode->isDefn()); + annexDef = static_cast(varNode); + + synthesizedDeclarationList = handler.newDeclarationList(PNK_VAR, JSOP_DEFVAR); + if (!synthesizedDeclarationList) + return false; + handler.addList(synthesizedDeclarationList, annexDef); } } - if (!pc->funcStmts->put(funName)) + + if (!bindLexicalFunctionName(funName, pn)) 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(); - } + if (annexDef) { + MOZ_ASSERT(!pc->sc->strict()); - /* No further binding (in BindNameToSlot) is needed for functions. */ - pn->pn_dflags |= PND_BOUND; + // 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; + } + } + } } else { /* A function expression does not introduce any binding. */ pn->setOp(kind == Arrow ? JSOP_LAMBDA_ARROW : JSOP_LAMBDA); @@ -2479,7 +2533,8 @@ template <> bool Parser::checkFunctionDefinition(HandlePropertyName funName, Node* pn, FunctionSyntaxKind kind, - bool* pbodyProcessed) + bool* pbodyProcessed, + Node* assignmentForAnnexBOut) { *pbodyProcessed = false; @@ -2487,10 +2542,18 @@ Parser::checkFunctionDefinition(HandlePropertyName funName, 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; @@ -2501,16 +2564,13 @@ Parser::checkFunctionDefinition(HandlePropertyName funName, return false; } } - } else if (bodyLevel) { + } else { if (pc->lexdeps.lookupDefn(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) { @@ -2591,7 +2651,8 @@ template typename ParseHandler::Node Parser::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); @@ -2599,12 +2660,13 @@ Parser::functionDef(InHandling inHandling, YieldHandling yieldHand 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) @@ -2791,7 +2853,6 @@ Parser::functionArgsAndBody(InHandling inHandling, ParseNode* if (!addFreeVariablesFromLazyFunction(fun, pc)) return false; - pn->pn_blockid = outerpc->blockid(); PropagateTransitiveParseFlags(funbox, outerpc->sc); return true; } while (false); @@ -2809,8 +2870,6 @@ Parser::functionArgsAndBody(InHandling inHandling, ParseNode* 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 @@ -3052,6 +3111,24 @@ Parser::functionStmt(YieldHandling yieldHandling, 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 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; @@ -3079,12 +3156,35 @@ Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling 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)) + Node assignmentForAnnexB; + Node fun = functionDef(InAllowed, yieldHandling, name, Statement, generatorKind, + PredictUninvoked, &assignmentForAnnexB); + if (!fun) return null(); - return functionDef(InAllowed, yieldHandling, name, Statement, generatorKind); + 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 @@ -3688,19 +3788,24 @@ Parser::bindVar(BindData* data, 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(); - } + // 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(); + // 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. @@ -4432,7 +4537,7 @@ Parser::variables(YieldHandling yieldHandling, if (!bindBeforeInitializer && !data.bind(name, this)) return null(); - if (!handler.finishInitializerAssignment(pn2, init, data.op())) + if (!handler.finishInitializerAssignment(pn2, init)) return null(); } } @@ -4456,11 +4561,11 @@ bool Parser::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 @@ -4550,7 +4655,7 @@ Parser::prepareAndBindInitializedLexicalWithNode(HandlePropert { BindData 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); @@ -4572,6 +4677,16 @@ Parser::makeInitializedLexicalBinding(HandlePropertyName name, return dn; } +template <> +bool +Parser::bindLexicalFunctionName(HandlePropertyName funName, + ParseNode* pn) +{ + MOZ_ASSERT(!pc->atBodyLevel()); + pn->pn_blockid = pc->blockid(); + return prepareAndBindInitializedLexicalWithNode(funName, /* isConst = */ false, pn, pos()); +} + template <> ParseNode* Parser::lexicalDeclaration(YieldHandling yieldHandling, bool isConst) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 6b2c0e339..2dd941664 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -53,7 +53,6 @@ struct StmtInfoPC : public StmtInfoBase {} }; -typedef HashSet, LifoAllocPolicy> FuncStmtSet; class SharedContext; typedef Vector DeclVector; @@ -236,10 +235,6 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext 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> innerFunctions; @@ -277,7 +272,6 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext parserPC(&prs->pc), oldpc(prs->pc), lexdeps(prs->context), - funcStmts(nullptr), innerFunctions(prs->context, TraceableVector(prs->context)), newDirectives(newDirectives), inDeclDestructuring(false) @@ -728,7 +722,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter 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); @@ -794,8 +789,10 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter 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* pc); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 3785ffd18..2aea01e46 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -333,6 +333,7 @@ class SyntaxParseHandler 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) { @@ -354,7 +355,7 @@ class SyntaxParseHandler 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) {} diff --git a/js/src/jit-test/tests/auto-regress/bug771027.js b/js/src/jit-test/tests/auto-regress/bug771027.js index 8c7b9a81a..d9038ad85 100644 --- a/js/src/jit-test/tests/auto-regress/bug771027.js +++ b/js/src/jit-test/tests/auto-regress/bug771027.js @@ -4,6 +4,6 @@ // 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); diff --git a/js/src/jit-test/tests/baseline/bug1081850.js b/js/src/jit-test/tests/baseline/bug1081850.js deleted file mode 100644 index e77158a1c..000000000 --- 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"); diff --git a/js/src/jit-test/tests/basic/bug667504-syntax.js b/js/src/jit-test/tests/basic/bug667504-syntax.js index c2e926d96..606922020 100644 --- 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() {} +} diff --git a/js/src/jit-test/tests/ion/bug1148973-1.js b/js/src/jit-test/tests/ion/bug1148973-1.js index b8fb36768..34d067144 100644 --- a/js/src/jit-test/tests/ion/bug1148973-1.js +++ b/js/src/jit-test/tests/ion/bug1148973-1.js @@ -8,7 +8,11 @@ try { } }(), function() {})) } catch (e) {}; +var log = ""; +evaluate(` try { function x() {} assertEq(String(b), "function () {}"); -} catch (e) { throw (e); } +} catch (e) { log += "e"; } +`); +assertEq(log, "e"); diff --git a/js/src/tests/ecma_5/extensions/function-definition-with.js b/js/src/tests/ecma_5/extensions/function-definition-with.js index df0ab9e0b..2277ad45f 100644 --- a/js/src/tests/ecma_5/extensions/function-definition-with.js +++ b/js/src/tests/ecma_5/extensions/function-definition-with.js @@ -19,8 +19,9 @@ 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); @@ -28,8 +29,9 @@ 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); @@ -39,8 +41,9 @@ 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); diff --git a/js/src/tests/ecma_5/extensions/strict-function-statements.js b/js/src/tests/ecma_5/extensions/strict-function-statements.js index 95b872faf..e6dc15139 100644 --- a/js/src/tests/ecma_5/extensions/strict-function-statements.js +++ b/js/src/tests/ecma_5/extensions/strict-function-statements.js @@ -9,46 +9,41 @@ assertEq(testLenientAndStrict("function f() { }", 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, @@ -56,7 +51,7 @@ assertEq(testLenientAndStrict("x: function f() { }", true); assertEq(testLenientAndStrict("try { function f() { } } catch (x) { }", parsesSuccessfully, - parseRaisesException(SyntaxError)), + parsesSuccessfully), true); // Lambdas are always permitted within any sort of statement. @@ -69,7 +64,7 @@ assertEq(testLenientAndStrict("if (true) (function f() { })", 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); @@ -77,8 +72,7 @@ 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() { }) }"), @@ -94,7 +88,7 @@ assertEq(testLenientAndStrict("function f() { }", true); assertEq(testLenientAndStrict("{ function f() { } }", completesNormally, - raisesException(SyntaxError)), + completesNormally), true); reportCompare(true, true); diff --git 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 index ea2494bda..9f6fd6467 100644 --- 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 @@ -18,26 +18,30 @@ 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(); diff --git a/js/src/tests/js1_5/Regress/regress-326453.js b/js/src/tests/js1_5/Regress/regress-326453.js deleted file mode 100644 index c9e39f9d6..000000000 --- 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); diff --git a/js/src/tests/js1_5/extensions/regress-245795.js b/js/src/tests/js1_5/extensions/regress-245795.js index af77f5419..a823fc2f6 100644 --- a/js/src/tests/js1_5/extensions/regress-245795.js +++ b/js/src/tests/js1_5/extensions/regress-245795.js @@ -12,23 +12,20 @@ var expect = ''; printBugNumber(BUGNUMBER); printStatus (summary); -if (typeof uneval != 'undefined') +function a() { - function a() - { - b = function() {}; - } - - var r = "function a() { b = function() {}; }"; - eval(uneval(a)); - - var v = a.toString().replace(/[ \n]+/g, ' '); - print(v) - - printStatus("[" + v + "]"); - - expect = r; - actual = v; - - reportCompare(expect, actual, summary); + b = function() {}; } + +var r = "function a() { b = function() {}; }"; +eval(uneval(a)); + +var v = a.toString().replace(/[ \n]+/g, ' '); +print(v) + +printStatus("[" + v + "]"); + +expect = r; +actual = v; + +reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-406572.js b/js/src/tests/js1_5/extensions/regress-406572.js index e93448c8d..4911d05bc 100644 --- a/js/src/tests/js1_5/extensions/regress-406572.js +++ b/js/src/tests/js1_5/extensions/regress-406572.js @@ -22,12 +22,14 @@ if (typeof window != 'undefined') 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. diff --git a/js/src/tests/js1_8_5/reflect-parse/declarations.js b/js/src/tests/js1_8_5/reflect-parse/declarations.js index 86f524fd3..480f58777 100644 --- a/js/src/tests/js1_8_5/reflect-parse/declarations.js +++ b/js/src/tests/js1_8_5/reflect-parse/declarations.js @@ -4,7 +4,7 @@ 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 @@ -86,4 +86,12 @@ assertProg("f.p = 1; var f; f.p; function f(){}", 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);