diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 5317b0b225f..25262c60d50 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1051,6 +1051,8 @@ Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType typ JS_ASSERT(pc->sc->isFunctionBox()); JS_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid); + uint32_t startYieldOffset = pc->lastYieldOffset; + Node pn; if (type == StatementListBody) { pn = statements(); @@ -1067,13 +1069,24 @@ Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType typ pn = handler.newReturnStatement(kid, handler.getPosition(kid)); if (!pn) return null(); + } - if (pc->sc->asFunctionBox()->isLegacyGenerator()) { + if (pc->lastYieldOffset != startYieldOffset) { + JS_ASSERT(pc->isLegacyGenerator()); + if (kind == Arrow) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_YIELD_IN_ARROW, js_yield_str); + return null(); + } + if (type == ExpressionBody) { reportBadReturn(pn, ParseError, JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); return null(); } + pc->sc->asFunctionBox()->setIsLegacyGenerator(); + } else { + JS_ASSERT(!pc->isLegacyGenerator()); } /* Check for falling off the end of a function that returns a value. */ @@ -2253,10 +2266,6 @@ Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu } // Parse the function body. - Maybe > yieldGuard; - if (kind == Arrow) - yieldGuard.construct(this); - FunctionBodyType bodyType = StatementListBody; if (tokenStream.getToken(TokenStream::Operand) != TOK_LC) { tokenStream.ungetToken(); @@ -2268,9 +2277,6 @@ Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu if (!body) return false; - if (!yieldGuard.empty() && !yieldGuard.ref().checkValidBody(body, JSMSG_YIELD_IN_ARROW)) - return false; - if (fun->name() && !checkStrictBinding(fun->name(), pn)) return false; @@ -4410,18 +4416,31 @@ Parser::returnStatementOrYieldExpression() // Legacy generators are identified by the presence of "yield" in their // bodies. We only see "yield" as TOK_YIELD in JS 1.7+. if (isYield) { + JS_ASSERT(tokenStream.versionNumber() >= JSVERSION_1_7); if (!abortIfSyntaxParser()) return null(); - // If we're within parens, we won't know if this is a generator - // expression until we see a |for| token, so we have to delay flagging - // the current function. - if (pc->parenDepth == 0) { - pc->sc->asFunctionBox()->setIsLegacyGenerator(); + if (pc->isLegacyGenerator()) { + // We are in a legacy generator: a function that has already seen a + // yield. + JS_ASSERT(pc->sc->isFunctionBox()); + JS_ASSERT(pc->lastYieldOffset != ParseContext::NoYieldOffset); } else { - pc->yieldCount++; - pc->yieldOffset = begin; + // We are in a code that has not seen a yield, and in JS 1.8 so + // "yield" parsed as TOK_YIELD. Try to transition to being a legacy + // generator. + JS_ASSERT(tokenStream.currentToken().type == TOK_YIELD); + JS_ASSERT(pc->lastYieldOffset == ParseContext::NoYieldOffset); + + if (!pc->sc->isFunctionBox()) { + report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); + return null(); + } + + pc->generatorParseMode = ParseContext::LegacyGenerator; } + + pc->lastYieldOffset = begin; } // Parse an optional operand. @@ -4466,7 +4485,7 @@ Parser::returnStatementOrYieldExpression() if (!pn) return null(); - if (pc->funHasReturnExpr && pc->sc->asFunctionBox()->isLegacyGenerator()) { + if (pc->funHasReturnExpr && pc->isLegacyGenerator()) { /* As in Python (see PEP-255), disallow return v; in generators. */ reportBadReturn(pn, ParseError, JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); @@ -5415,117 +5434,6 @@ class CompExprTransplanter bool transplant(ParseNode *pn); }; -/* - * A helper for lazily checking for the presence of illegal |yield| or - * |arguments| tokens inside of legacy generator expressions. This must be done - * lazily since we don't know whether we're in a legacy generator expression - * until we see the "for" token after we've already parsed the body expression. - * (For ES6 generator comprehensions, we won't need this lazy check because the - * |for| is at the beginning.) - * - * Use in any context which may turn out to be inside a legacy generator - * expression. This includes parenthesized expressions and argument lists, and - * it includes the tail of legacy generator expressions. - * - * The guard will keep track of any |yield| or |arguments| tokens that occur while - * parsing the body. As soon as the parser reaches the end of the body expression, - * call endBody() to reset the context's state, and then immediately call: - * - * - checkValidBody() if this *did* turn out to be a legacy generator expression - * - maybeNoteLegacyGenerator() if this *did not* turn out to be a legacy - * generator expression - */ -template -class GenexpGuard -{ - Parser *parser; - uint32_t startYieldCount; - - typedef typename ParseHandler::Node Node; - - public: - explicit GenexpGuard(Parser *parser) - : parser(parser) - { - ParseContext *pc = parser->pc; - if (pc->parenDepth == 0) { - pc->yieldCount = 0; - pc->yieldOffset = 0; - } - startYieldCount = pc->yieldCount; - pc->parenDepth++; - } - - void endBody(); - bool checkValidBody(Node pn, unsigned err = JSMSG_BAD_GENEXP_BODY); - bool maybeNoteLegacyGenerator(Node pn); -}; - -template -void -GenexpGuard::endBody() -{ - parser->pc->parenDepth--; -} - -/* - * Check whether a |yield| or |arguments| token has been encountered in the - * body expression, and if so, report an error. - * - * Call this after endBody() when determining that the body *was* in a - * generator expression. - */ -template -bool -GenexpGuard::checkValidBody(Node pn, unsigned err) -{ - ParseContext *pc = parser->pc; - if (pc->yieldCount > startYieldCount) { - uint32_t offset = pc->yieldOffset - ? pc->yieldOffset - : (pn ? parser->handler.getPosition(pn) - : parser->pos()).begin; - - parser->reportWithOffset(ParseError, false, offset, err, js_yield_str); - return false; - } - - return true; -} - -/* - * Check whether a |yield| token has been encountered in the body expression, - * and if so, note that the current function is a generator function. - * - * Call this after endBody() when determining that the body *was not* in a - * generator expression. - */ -template -bool -GenexpGuard::maybeNoteLegacyGenerator(Node pn) -{ - ParseContext *pc = parser->pc; - // yieldCount is only incremented when we see yield in JS 1.7+ code. - if (pc->yieldCount > 0) { - if (!pc->sc->isFunctionBox()) { - // FIXME: This error should be detected eagerly, when the yield is - // seen. - parser->report(ParseError, false, ParseHandler::null(), - JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); - return false; - } - pc->sc->asFunctionBox()->setIsLegacyGenerator(); - if (pc->funHasReturnExpr) { - // At the time we saw the yield, we might not have set - // isLegacyGenerator yet. - parser->reportBadReturn(pn, ParseError, - JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); - return false; - } - } - return true; -} - /* * Any definitions nested within the comprehension expression of a generator * expression must move "down" one static level, which of course increases the @@ -5817,7 +5725,7 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bo pn2->pn_iflags |= JSITER_FOREACH; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); - GenexpGuard guard(this); + uint32_t startYieldOffset = pc->lastYieldOffset; RootedPropertyName name(context); tt = tokenStream.getToken(); @@ -5874,14 +5782,10 @@ Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bo return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); - guard.endBody(); - - if (isGenexp) { - if (!guard.checkValidBody(pn2)) - return null(); - } else { - if (!guard.maybeNoteLegacyGenerator(pn2)) - return null(); + if (isGenexp && pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return null(); } switch (tt) { @@ -6111,12 +6015,12 @@ template typename ParseHandler::Node Parser::assignExprWithoutYield(unsigned msg) { - GenexpGuard yieldGuard(this); + uint32_t startYieldOffset = pc->lastYieldOffset; Node res = assignExpr(); - yieldGuard.endBody(); - if (res) { - if (!yieldGuard.checkValidBody(res, msg)) - return null(); + if (res && pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + msg, js_yield_str); + return null(); } return res; } @@ -6128,15 +6032,13 @@ Parser::argumentList(Node listNode) if (tokenStream.matchToken(TOK_RP, TokenStream::Operand)) return true; - GenexpGuard guard(this); + uint32_t startYieldOffset = pc->lastYieldOffset; bool arg0 = true; do { Node argNode = assignExpr(); if (!argNode) return false; - if (arg0) - guard.endBody(); if (handler.isOperationWithoutParens(argNode, PNK_YIELD) && tokenStream.peekToken() == TOK_COMMA) { @@ -6145,8 +6047,11 @@ Parser::argumentList(Node listNode) } #if JS_HAS_GENERATOR_EXPRS if (tokenStream.matchToken(TOK_FOR)) { - if (!guard.checkValidBody(argNode)) + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); return false; + } argNode = generatorExpr(argNode); if (!argNode) return false; @@ -6154,11 +6059,8 @@ Parser::argumentList(Node listNode) report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); return false; } - } else + } #endif - if (arg0 && !guard.maybeNoteLegacyGenerator(argNode)) - return false; - arg0 = false; handler.addList(listNode, argNode); @@ -6784,7 +6686,7 @@ Parser::parenExpr(bool *genexp) if (genexp) *genexp = false; - GenexpGuard guard(this); + uint32_t startYieldOffset = pc->lastYieldOffset; /* * Always accept the 'in' operator in a parenthesized expression, @@ -6798,12 +6700,14 @@ Parser::parenExpr(bool *genexp) if (!pn) return null(); - guard.endBody(); #if JS_HAS_GENERATOR_EXPRS if (tokenStream.matchToken(TOK_FOR)) { - if (!guard.checkValidBody(pn)) + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); return null(); + } if (handler.isOperationWithoutParens(pn, PNK_COMMA)) { report(ParseError, false, null(), JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); @@ -6822,12 +6726,9 @@ Parser::parenExpr(bool *genexp) handler.setEndPosition(pn, pos().end); *genexp = true; } - } else + } #endif /* JS_HAS_GENERATOR_EXPRS */ - if (!guard.maybeNoteLegacyGenerator(pn)) - return null(); - return pn; } diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 856760d6231..7a882ce7c2c 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -105,10 +105,19 @@ struct ParseContext : public GenericParseContext const unsigned staticLevel; /* static compilation unit nesting level */ - uint32_t parenDepth; /* nesting depth of parens that might turn out - to be generator expressions */ - uint32_t yieldCount; /* number of |yield| tokens encountered at - non-zero depth in current paren tree */ + // Functions start off being parsed as NotGenerator. + // NotGenerator transitions to LegacyGenerator on parsing "yield" in JS 1.7. + enum GeneratorParseMode { NotGenerator, LegacyGenerator }; + GeneratorParseMode generatorParseMode; + + // lastYieldOffset stores the offset of the last yield that was parsed. + // NoYieldOffset is its initial value. + static const uint32_t NoYieldOffset = UINT32_MAX; + uint32_t lastYieldOffset; + + bool isGenerator() const { return generatorParseMode != NotGenerator; } + bool isLegacyGenerator() const { return generatorParseMode == LegacyGenerator; } + Node blockNode; /* parse node for a block with let declarations (block with its own lexical scope) */ private: @@ -192,11 +201,6 @@ struct ParseContext : public GenericParseContext bool generateFunctionBindings(ExclusiveContext *cx, LifoAlloc &alloc, InternalHandle bindings) const; - public: - uint32_t yieldOffset; /* offset of a yield expression that might - be an error if we turn out to be inside - a generator expression. Zero means - there isn't one. */ private: ParseContext **parserPC; /* this points to the Parser's active pc and holds either |this| or one of @@ -246,13 +250,12 @@ struct ParseContext : public GenericParseContext blockChain(prs->context), maybeFunction(maybeFunction), staticLevel(staticLevel), - parenDepth(0), - yieldCount(0), + generatorParseMode(NotGenerator), + lastYieldOffset(NoYieldOffset), blockNode(ParseHandler::null()), decls_(prs->context, prs->alloc), args_(prs->context), vars_(prs->context), - yieldOffset(0), parserPC(&prs->pc), oldpc(prs->pc), lexdeps(prs->context), diff --git a/js/src/tests/js1_8/genexps/regress-634472.js b/js/src/tests/js1_8/genexps/regress-634472.js index c7cc2330774..52680d230f6 100644 --- a/js/src/tests/js1_8/genexps/regress-634472.js +++ b/js/src/tests/js1_8/genexps/regress-634472.js @@ -58,7 +58,7 @@ const cases = [ { expr: "(yield, 1 for (x in []))", top: JSMSG_TOP_YIELD, fun: JSMSG_YIELD_PAREN, gen: JSMSG_YIELD_PAREN, desc: "simple yield in list in genexp" }, { expr: "(yield 1, 2 for (x in []))", top: JSMSG_TOP_YIELD, fun: JSMSG_YIELD_PAREN, gen: JSMSG_YIELD_PAREN, desc: "yield w/ arg in list in genexp" }, { expr: "(1, yield for (x in []))", top: JSMSG_TOP_YIELD, fun: JSMSG_GENERIC, gen: JSMSG_GENERIC, desc: "simple yield at end of list in genexp" }, - { expr: "(1, yield 2 for (x in []))", top: JSMSG_TOP_YIELD, fun: { simple: JSMSG_GENEXP_YIELD, call: JSMSG_BAD_GENERATOR_SYNTAX }, + { expr: "(1, yield 2 for (x in []))", top: JSMSG_TOP_YIELD, fun: { simple: JSMSG_GENEXP_YIELD, call: JSMSG_GENEXP_YIELD }, gen: JSMSG_GENEXP_YIELD, desc: "yield w/ arg at end of list in genexp" }, { expr: "((yield) for (x in []))", top: JSMSG_TOP_YIELD, fun: JSMSG_GENEXP_YIELD, gen: JSMSG_GENEXP_YIELD, desc: "simple yield, parenthesized in genexp" }, { expr: "((yield 1) for (x in []))", top: JSMSG_TOP_YIELD, fun: JSMSG_GENEXP_YIELD, gen: JSMSG_GENEXP_YIELD, desc: "yield w/ arg, parenthesized in genexp" },