From 7ecc50d90d13690d610f26d0056a326e52bc834c Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sat, 8 Jun 2019 14:51:46 -0400 Subject: 1317379 - Disallow generator functions and async functions as direct children of if/else. --- js/src/frontend/Parser.cpp | 83 ++++++++++++++++------ .../AsyncFunctions/forbidden-as-consequent.js | 14 ++++ .../ecma_6/Generators/forbidden-as-consequent.js | 11 +++ 3 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 js/src/tests/ecma_2017/AsyncFunctions/forbidden-as-consequent.js create mode 100644 js/src/tests/ecma_6/Generators/forbidden-as-consequent.js diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 8f52f1d27..a7b1f3a14 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -5248,33 +5248,74 @@ Parser::consequentOrAlternative(YieldHandling yieldHandling) if (!tokenStream.peekToken(&next, TokenStream::Operand)) return null(); - if (next == TOK_FUNCTION) { - // Annex B.3.4 says that unbraced function declarations under if/else - // in non-strict code act as if they were braced. That is, - // |if (x) function f() {}| is parsed as |if (x) { function f() {} }|. - if (!pc->sc()->strict()) { - tokenStream.consumeKnownToken(next, TokenStream::Operand); + // Annex B.3.4 says that unbraced FunctionDeclarations under if/else in + // non-strict code act as if they were braced: |if (x) function f() {}| + // parses as |if (x) { function f() {} }|. + // + // Careful! FunctionDeclaration doesn't include generators or async + // functions. + if (next == TOK_NAME && + !tokenStream.nextNameContainsEscape() && + tokenStream.nextName() == context->names().async) + { + tokenStream.consumeKnownToken(next, TokenStream::Operand); - ParseContext::Statement stmt(pc, StatementKind::Block); - ParseContext::Scope scope(this); - if (!scope.init(pc)) - return null(); + // Peek only on the same line: ExpressionStatement's lookahead + // restriction is phrased as + // + // [lookahead ∉ { {, function, async [no LineTerminator here] function, class, let [ }] + // + // meaning that code like this is valid: + // + // if (true) + // async // ASI opportunity + // function clownshoes() {} + TokenKind maybeFunction; + if (!tokenStream.peekTokenSameLine(&maybeFunction)) + return null(); - TokenPos funcPos = pos(); - Node fun = functionStmt(pos().begin, yieldHandling, NameRequired); - if (!fun) - return null(); + if (maybeFunction == TOK_FUNCTION) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "async function declarations"); + return null(); + } - Node block = handler.newStatementList(funcPos); - if (!block) - return null(); + // Otherwise this |async| begins an ExpressionStatement. + tokenStream.ungetToken(); + } else if (next == TOK_FUNCTION) { + tokenStream.consumeKnownToken(next, TokenStream::Operand); - handler.addStatementToList(block, fun); - return finishLexicalScope(scope, block); + // Parser::statement would handle this, but as this function handles + // every other error case, it seems best to handle this. + if (pc->sc()->strict()) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations"); + return null(); + } + + TokenKind maybeStar; + if (!tokenStream.peekToken(&maybeStar)) + return null(); + + if (maybeStar == TOK_MUL) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "generator declarations"); + return null(); } - // Function declarations are a syntax error in strict mode code. - // Parser::statement reports that error. + ParseContext::Statement stmt(pc, StatementKind::Block); + ParseContext::Scope scope(this); + if (!scope.init(pc)) + return null(); + + TokenPos funcPos = pos(); + Node fun = functionStmt(pos().begin, yieldHandling, NameRequired); + if (!fun) + return null(); + + Node block = handler.newStatementList(funcPos); + if (!block) + return null(); + + handler.addStatementToList(block, fun); + return finishLexicalScope(scope, block); } return statement(yieldHandling); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/forbidden-as-consequent.js b/js/src/tests/ecma_2017/AsyncFunctions/forbidden-as-consequent.js new file mode 100644 index 000000000..656ed46de --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/forbidden-as-consequent.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +assertThrowsInstanceOf(() => eval("if (1) async function foo() {}"), + SyntaxError); +assertThrowsInstanceOf(() => eval("'use strict'; if (1) async function foo() {}"), + SyntaxError); + +var async = 42; +assertEq(eval("if (1) async \n function foo() {}"), 42); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Generators/forbidden-as-consequent.js b/js/src/tests/ecma_6/Generators/forbidden-as-consequent.js new file mode 100644 index 000000000..13647e154 --- /dev/null +++ b/js/src/tests/ecma_6/Generators/forbidden-as-consequent.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +assertThrowsInstanceOf(() => eval("if (1) function* foo() {}"), + SyntaxError); +assertThrowsInstanceOf(() => eval("'use strict'; if (1) function* foo() {}"), + SyntaxError); + +if (typeof reportCompare === "function") + reportCompare(true, true); -- cgit v1.2.3