Bug 1071646 - Support labelled function declarations in sloppy mode per Annex B.3.2. (r=jorendorff)

This commit is contained in:
Shu-yu Guo 2015-12-18 13:18:19 -08:00 committed by Cameron Kaiser
parent 25844de1b9
commit 37bf861a92
9 changed files with 186 additions and 67 deletions

View File

@ -1372,20 +1372,20 @@ BytecodeEmitter::emitVarIncDec(ParseNode* pn)
}
bool
BytecodeEmitter::atBodyLevel() const
BytecodeEmitter::atBodyLevel(StmtInfoBCE* stmt) const
{
// 'eval' and non-syntactic scripts are always under an invisible lexical
// scope, but since it is not syntactic, it should still be considered at
// body level.
if (sc->staticScope()->is<StaticEvalObject>()) {
bool bl = !innermostStmt()->enclosing;
MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK);
MOZ_ASSERT_IF(bl, innermostStmt()->staticScope
->as<StaticBlockObject>()
.enclosingStaticScope() == sc->staticScope());
bool bl = !stmt->enclosing;
MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
MOZ_ASSERT_IF(bl, stmt->staticScope
->as<StaticBlockObject>()
.enclosingStaticScope() == sc->staticScope());
return bl;
}
return !innermostStmt() || sc->isModuleBox();
return !stmt || sc->isModuleBox();
}
uint32_t
@ -3098,11 +3098,23 @@ BytecodeEmitter::emitSwitch(ParseNode* pn)
if (!enterBlockScope(&stmtInfo, cases->pn_objbox, JSOP_UNINITIALIZED, 0))
return false;
stmtInfo.type = StmtType::SWITCH;
stmtInfo.update = top = offset();
// Advance |cases| to refer to the switch case list.
cases = cases->expr();
// A switch statement may contain hoisted functions inside its
// cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
// bodies of the cases to the case list.
if (cases->pn_xflags & PNX_FUNCDEFS) {
for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
if (!emitHoistedFunctionsInList(caseNode->pn_right))
return false;
}
}
}
stmtInfo.type = StmtType::SWITCH;
stmtInfo.update = top = offset();
} else {
MOZ_ASSERT(cases->isKind(PNK_STATEMENTLIST));
top = offset();
@ -5336,6 +5348,11 @@ BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list)
MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS);
for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) {
if (!sc->strict()) {
while (pn->isKind(PNK_LABEL))
pn = pn->as<LabeledStatement>().statement();
}
if (pn->isKind(PNK_ANNEXB_FUNCTION) ||
(pn->isKind(PNK_FUNCTION) && pn->functionIsHoisted()))
{
@ -6370,7 +6387,18 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
* For modules, we record the function and instantiate the binding during
* ModuleDeclarationInstantiation(), before the script is run.
*/
if (!atBodyLevel()) {
// Check for functions that were parsed under labeled statements per ES6
// Annex B.3.2.
bool blockScopedFunction = !atBodyLevel();
if (!sc->strict() && blockScopedFunction) {
StmtInfoBCE* stmt = innermostStmt();
while (stmt && stmt->type == StmtType::LABEL)
stmt = stmt->enclosing;
blockScopedFunction = !atBodyLevel(stmt);
}
if (blockScopedFunction) {
if (!emitIndexOp(JSOP_LAMBDA, index))
return false;
MOZ_ASSERT(pn->getOp() == JSOP_INITLEXICAL);
@ -6381,7 +6409,6 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
} 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;

View File

@ -249,7 +249,10 @@ struct BytecodeEmitter
return parser->blockScopes[dn->pn_blockid];
}
bool atBodyLevel() const;
bool atBodyLevel(StmtInfoBCE* stmt) const;
bool atBodyLevel() const {
return atBodyLevel(innermostStmt());
}
uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut);
bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn);
bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op);

View File

@ -444,18 +444,40 @@ class FullParseHandler
return pn;
}
template <typename PC>
bool isFunctionStmt(ParseNode* stmt, PC* pc) {
if (!pc->sc->strict()) {
while (stmt->isKind(PNK_LABEL))
stmt = stmt->as<LabeledStatement>().statement();
}
return stmt->isKind(PNK_FUNCTION) || stmt->isKind(PNK_ANNEXB_FUNCTION);
}
template <typename PC>
void addStatementToList(ParseNode* list, ParseNode* stmt, PC* pc) {
MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
if (stmt->isKind(PNK_FUNCTION) || stmt->isKind(PNK_ANNEXB_FUNCTION)) {
list->append(stmt);
if (isFunctionStmt(stmt, pc)) {
// PNX_FUNCDEFS notifies the emitter that the block contains
// body-level function definitions that should be processed
// before the rest of nodes.
list->pn_xflags |= PNX_FUNCDEFS;
}
}
list->append(stmt);
template <typename PC>
void addCaseStatementToList(ParseNode* list, ParseNode* casepn, PC* pc) {
MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
MOZ_ASSERT(casepn->isKind(PNK_CASE));
MOZ_ASSERT(casepn->pn_right->isKind(PNK_STATEMENTLIST));
list->append(casepn);
if (casepn->pn_right->pn_xflags & PNX_FUNCDEFS)
list->pn_xflags |= PNX_FUNCDEFS;
}
bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {

View File

@ -2334,7 +2334,30 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
MOZ_ASSERT(assignmentForAnnexBOut);
*assignmentForAnnexBOut = nullptr;
if (pc->atBodyLevel()) {
// In sloppy mode, ES6 Annex B.3.2 allows labelled function
// declarations. Otherwise it is a parse error.
bool bodyLevelFunction = pc->atBodyLevel();
if (!bodyLevelFunction) {
StmtInfoPC* stmt = pc->innermostStmt();
if (stmt->type == StmtType::LABEL) {
if (pc->sc->strict()) {
report(ParseError, false, null(), JSMSG_FUNCTION_LABEL);
return false;
}
stmt = pc->innermostNonLabelStmt();
// A switch statement is always braced, so it's okay to label
// functions in sloppy mode under switch.
if (stmt && stmt->type != StmtType::BLOCK && stmt->type != StmtType::SWITCH) {
report(ParseError, false, null(), JSMSG_SLOPPY_FUNCTION_LABEL);
return false;
}
bodyLevelFunction = pc->atBodyLevel(stmt);
}
}
if (bodyLevelFunction) {
if (!bindBodyLevelFunctionName(funName, pn_))
return false;
} else {
@ -2342,11 +2365,11 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
Node synthesizedDeclarationList = null();
if (!pc->sc->strict()) {
// Under non-strict mode, try Annex B.3.3 semantics. If making
// an additional 'var' binding of the same name does not throw
// an early error, do so. This 'var' binding would be assigned
// the function object in situ, e.g., when its declaration is
// reached, not at the start of the block.
// Under non-strict mode, try ES6 Annex B.3.3 semantics. If
// making an additional 'var' binding of the same name does
// not throw an early error, do so. This 'var' binding would
// be assigned the function object in situ, e.g., when its
// declaration is reached, not at the start of the block.
annexDef = pc->decls().lookupFirst(funName);
if (annexDef) {
@ -3117,16 +3140,16 @@ Parser<ParseHandler>::functionStmt(YieldHandling yieldHandling, DefaultHandling
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();
if (!pc->sc->strict() && stmt) {
if (stmt->type == StmtType::IF || stmt->type == StmtType::ELSE) {
if (!abortIfSyntaxParser())
return null();
synthesizedStmtInfoForAnnexB.emplace(*this, StmtType::BLOCK);
synthesizedBlockForAnnexB = pushLexicalScope(*synthesizedStmtInfoForAnnexB);
if (!synthesizedBlockForAnnexB)
return null();
synthesizedStmtInfoForAnnexB.emplace(*this, StmtType::BLOCK);
synthesizedBlockForAnnexB = pushLexicalScope(*synthesizedStmtInfoForAnnexB);
if (!synthesizedBlockForAnnexB)
return null();
}
}
RootedPropertyName name(context);
@ -4558,28 +4581,45 @@ Parser<ParseHandler>::variables(YieldHandling yieldHandling,
template <>
bool
Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos& errorPos)
Parser<FullParseHandler>::checkAndPrepareLexical(PrepareLexicalKind prepareWhat,
const TokenPos& errorPos)
{
/*
* This is a lexical declaration. We must be directly under a block, but
* not an implicit block created due to 'for (let ...)'. If we pass this
* error test, make the enclosing StmtInfoPC be our scope. Further let
* declarations in this block will find this scope statement and use the
* same block object.
* This is a lexical declaration. We must be directly under a block for
* 'let' and 'const' declarations. If we pass this error test, make the
* enclosing StmtInfoPC be our scope. Further let declarations in this
* block will find this scope statement and use the same block object.
*
* Function declarations behave like 'let', except that they are allowed
* per ES6 Annex B.3.2 to be labeled, unlike plain 'let' and 'const'
* declarations.
*
* If we are the first let declaration in this block (i.e., when the
* enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then
* we also need to set pc->blockNode to be our PNK_LEXICALSCOPE.
*/
StmtInfoPC* stmt = pc->innermostStmt();
// ES6 Annex B.3.2 does not apply in strict mode, and labeled functions in
// strict mode should have been rejected by checkFunctionDefinition.
MOZ_ASSERT_IF(pc->innermostStmt() &&
pc->innermostStmt()->type == StmtType::LABEL &&
prepareWhat == PrepareFunction,
!pc->sc->strict());
StmtInfoPC* stmt = prepareWhat == PrepareFunction
? pc->innermostNonLabelStmt()
: pc->innermostStmt();
if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
isConst ? "const" : "lexical");
reportWithOffset(ParseError, false, errorPos.begin,
stmt->type == StmtType::LABEL
? JSMSG_LEXICAL_DECL_LABEL
: JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
prepareWhat == PrepareConst ? "const" : "lexical");
return false;
}
if (!stmt) {
MOZ_ASSERT(pc->atBodyLevel());
MOZ_ASSERT_IF(prepareWhat != PrepareFunction, pc->atBodyLevel());
/*
* Self-hosted code must be usable against *any* global object,
@ -4590,7 +4630,7 @@ Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos& e
bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->innermostScopeStmt();
if (options().selfHostingMode && isGlobal) {
report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL,
isConst ? "'const'" : "'let'");
prepareWhat == PrepareConst ? "'const'" : "'let'");
return false;
}
return true;
@ -4616,8 +4656,12 @@ Parser<FullParseHandler>::checkAndPrepareLexical(bool isConst, const TokenPos& e
* catch block (catch is a lexical scope by definition).
*/
MOZ_ASSERT(stmt->canBeBlockScope() && stmt->type != StmtType::CATCH);
pc->stmtStack.makeInnermostLexicalScope(*blockObj);
if (prepareWhat == PrepareFunction) {
stmt->isBlockScope = true;
pc->stmtStack.linkAsInnermostScopeStmt(stmt, *blockObj);
} else {
pc->stmtStack.makeInnermostLexicalScope(*blockObj);
}
MOZ_ASSERT(!blockScopes[stmt->blockid]);
blockScopes[stmt->blockid].set(blockObj);
@ -4649,21 +4693,22 @@ CurrentLexicalStaticBlock(ParseContext<FullParseHandler>* pc)
template <>
bool
Parser<FullParseHandler>::prepareAndBindInitializedLexicalWithNode(HandlePropertyName name,
bool isConst,
PrepareLexicalKind prepareWhat,
ParseNode* pn,
const TokenPos& pos)
{
BindData<FullParseHandler> data(context);
if (!checkAndPrepareLexical(isConst, pos))
if (!checkAndPrepareLexical(prepareWhat, pos))
return false;
data.initLexical(HoistVars, isConst ? JSOP_DEFCONST : JSOP_DEFLET,
data.initLexical(HoistVars, prepareWhat == PrepareConst ? JSOP_DEFCONST : JSOP_DEFLET,
CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS);
return bindInitialized(&data, name, pn);
}
template <>
ParseNode*
Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst,
Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name,
PrepareLexicalKind prepareWhat,
const TokenPos& pos)
{
ParseNode* dn = newBindingNode(name, false);
@ -4671,7 +4716,7 @@ Parser<FullParseHandler>::makeInitializedLexicalBinding(HandlePropertyName name,
return null();
handler.setPosition(dn, pos);
if (!prepareAndBindInitializedLexicalWithNode(name, isConst, dn, pos))
if (!prepareAndBindInitializedLexicalWithNode(name, prepareWhat, dn, pos))
return null();
return dn;
@ -4684,7 +4729,7 @@ Parser<FullParseHandler>::bindLexicalFunctionName(HandlePropertyName funName,
{
MOZ_ASSERT(!pc->atBodyLevel());
pn->pn_blockid = pc->blockid();
return prepareAndBindInitializedLexicalWithNode(funName, /* isConst = */ false, pn, pos());
return prepareAndBindInitializedLexicalWithNode(funName, PrepareFunction, pn, pos());
}
template <>
@ -4693,7 +4738,7 @@ Parser<FullParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool i
{
handler.disableSyntaxParser();
if (!checkAndPrepareLexical(isConst, pos()))
if (!checkAndPrepareLexical(isConst ? PrepareConst : PrepareLet, pos()))
return null();
/*
@ -5205,7 +5250,7 @@ Parser<FullParseHandler>::exportDeclaration()
default:
tokenStream.ungetToken();
RootedPropertyName name(context, context->names().starDefaultStar);
binding = makeInitializedLexicalBinding(name, true, pos());
binding = makeInitializedLexicalBinding(name, PrepareConst, pos());
if (!binding)
return null();
kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
@ -5952,7 +5997,7 @@ Parser<ParseHandler>::switchStatement(YieldHandling yieldHandling)
afterReturn = true;
}
}
handler.addList(body, stmt);
handler.addStatementToList(body, stmt, pc);
}
// In ES6, lexical bindings cannot be accessed until initialized. If
@ -5971,7 +6016,7 @@ Parser<ParseHandler>::switchStatement(YieldHandling yieldHandling)
Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body);
if (!casepn)
return null();
handler.addList(caseList, casepn);
handler.addCaseStatementToList(caseList, casepn, pc);
}
/*
@ -6799,7 +6844,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
ParseNode* nameNode = null();
ParseNode* methodsOrBlock = classMethods;
if (name) {
ParseNode* innerBinding = makeInitializedLexicalBinding(name, true, namePos);
ParseNode* innerBinding = makeInitializedLexicalBinding(name, PrepareConst, namePos);
if (!innerBinding)
return null();
@ -6810,7 +6855,7 @@ Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
ParseNode* outerBinding = null();
if (classContext == ClassStatement) {
outerBinding = makeInitializedLexicalBinding(name, false, namePos);
outerBinding = makeInitializedLexicalBinding(name, PrepareLet, namePos);
if (!outerBinding)
return null();
}

View File

@ -289,6 +289,7 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext
StmtInfoPC* innermostStmt() const { return stmtStack.innermost(); }
StmtInfoPC* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
StmtInfoPC* innermostNonLabelStmt() const { return stmtStack.innermostNonLabel(); }
JSObject* innermostStaticScope() const {
if (StmtInfoPC* stmt = innermostScopeStmt())
return stmt->staticScope;
@ -302,19 +303,23 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext
// function f1() { function f2() { } }
// if (cond) { function f3() { if (cond) { function f4() { } } } }
//
bool atBodyLevel() {
bool atBodyLevel(StmtInfoPC* stmt) {
// 'eval' and non-syntactic scripts are always under an invisible
// lexical scope, but since it is not syntactic, it should still be
// considered at body level.
if (sc->staticScope()->is<StaticEvalObject>()) {
bool bl = !innermostStmt()->enclosing;
MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK);
MOZ_ASSERT_IF(bl, innermostStmt()->staticScope
->template as<StaticBlockObject>()
.enclosingStaticScope() == sc->staticScope());
bool bl = !stmt->enclosing;
MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
MOZ_ASSERT_IF(bl, stmt->staticScope
->template as<StaticBlockObject>()
.enclosingStaticScope() == sc->staticScope());
return bl;
}
return !innermostStmt();
return !stmt;
}
bool atBodyLevel() {
return atBodyLevel(innermostStmt());
}
bool atGlobalLevel() {
@ -841,10 +846,17 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
Node objectLiteral(YieldHandling yieldHandling);
bool checkAndPrepareLexical(bool isConst, const TokenPos& errorPos);
bool prepareAndBindInitializedLexicalWithNode(HandlePropertyName name, bool isConst,
enum PrepareLexicalKind {
PrepareLet,
PrepareConst,
PrepareFunction
};
bool checkAndPrepareLexical(PrepareLexicalKind prepareWhat, const TokenPos& errorPos);
bool prepareAndBindInitializedLexicalWithNode(HandlePropertyName name,
PrepareLexicalKind prepareWhat,
ParseNode* pn, const TokenPos& pos);
Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos& pos);
Node makeInitializedLexicalBinding(HandlePropertyName name, PrepareLexicalKind prepareWhat,
const TokenPos& pos);
Node newBindingNode(PropertyName* name, bool functionScope, VarContext varContext = HoistVars);

View File

@ -605,6 +605,12 @@ class MOZ_STACK_CLASS StmtInfoStack
StmtInfo* innermost() const { return innermostStmt_; }
StmtInfo* innermostScopeStmt() const { return innermostScopeStmt_; }
StmtInfo* innermostNonLabel() const {
StmtInfo* stmt = innermost();
while (stmt && stmt->type == StmtType::LABEL)
stmt = stmt->enclosing;
return stmt;
}
void push(StmtInfo* stmt, StmtType type) {
stmt->type = type;

View File

@ -290,6 +290,7 @@ class SyntaxParseHandler
Node newStatementList(unsigned blockid, const TokenPos& pos) { return NodeGeneric; }
void addStatementToList(Node list, Node stmt, ParseContext<SyntaxParseHandler>* pc) {}
void addCaseStatementToList(Node list, Node stmt, ParseContext<SyntaxParseHandler>* pc) {}
bool prependInitialYield(Node stmtList, Node gen) { return true; }
Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; }

View File

@ -269,6 +269,9 @@ MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found")
MSG_DEF(JSMSG_LET_CLASS_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a class")
MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block")
MSG_DEF(JSMSG_LEXICAL_DECL_LABEL, 1, JSEXN_SYNTAXERR, "{0} declarations cannot be labelled")
MSG_DEF(JSMSG_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions cannot be labelled")
MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks")
MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression")
MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")

View File

@ -29,11 +29,11 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 330;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 331;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 421,
static_assert(JSErr_Limit == 424,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "