Bug 1071646 - Make functions block-scoped in JS and implement Annex B semantics for compatibility. (r=jorendorff)

This commit is contained in:
Shu-yu Guo 2015-12-18 13:18:19 -08:00 committed by Cameron Kaiser
parent 167ad8564e
commit 25844de1b9
22 changed files with 461 additions and 295 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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<FullParseHandler>& 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);

View File

@ -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_<BinaryNode>(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);

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -1203,18 +1203,6 @@ 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;
@ -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<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn
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());
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<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 (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_<FuncStmtSet>(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<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);
}
}
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<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
Node* pn, FunctionSyntaxKind kind,
bool* pbodyProcessed)
bool* pbodyProcessed,
Node* assignmentForAnnexBOut)
{
*pbodyProcessed = false;
@ -2487,10 +2542,18 @@ Parser<SyntaxParseHandler>::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<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
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) {
@ -2591,7 +2651,8 @@ 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);
@ -2599,12 +2660,13 @@ Parser<ParseHandler>::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<FullParseHandler>::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<FullParseHandler>::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<ParseHandler>::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<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;
@ -3079,12 +3156,35 @@ Parser<ParseHandler>::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 <typename ParseHandler>
@ -3688,19 +3788,24 @@ Parser<ParseHandler>::bindVar(BindData<ParseHandler>* 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<ParseHandler>::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<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
@ -4550,7 +4655,7 @@ Parser<FullParseHandler>::prepareAndBindInitializedLexicalWithNode(HandlePropert
{
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);
@ -4572,6 +4677,16 @@ Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name,
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)

View File

@ -53,7 +53,6 @@ struct StmtInfoPC : public StmtInfoBase
{}
};
typedef HashSet<JSAtom*, DefaultHasher<JSAtom*>, LifoAllocPolicy<Fallible>> FuncStmtSet;
class SharedContext;
typedef Vector<Definition*, 16> 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<TraceableVector<JSFunction*>> 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<JSFunction*>(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<ParseHandler>* pc);

View File

@ -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) {}

View File

@ -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);

View File

@ -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");

View File

@ -1,2 +1,3 @@
for (var x in x)
for (var x in x) {
function x() {}
}

View File

@ -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");

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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.

View File

@ -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);