diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index b619cf24c..3938d9743 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -10,6 +10,8 @@ #include "mozilla/Attributes.h" #include "mozilla/PodOperations.h" +#include + #include "frontend/ParseNode.h" #include "frontend/SharedContext.h" @@ -870,29 +872,25 @@ class FullParseHandler return node->isKind(PNK_NAME); } - bool nameIsEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); - - return node->pn_atom == cx->names().eval; + bool isEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { + return node->isKind(PNK_NAME) && node->pn_atom == cx->names().eval; } const char* nameIsArgumentsEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { MOZ_ASSERT(isNameAnyParentheses(node), "must only call this function on known names"); - if (nameIsEvalAnyParentheses(node, cx)) + if (isEvalAnyParentheses(node, cx)) return js_eval_str; if (node->pn_atom == cx->names().arguments) return js_arguments_str; return nullptr; } - bool nameIsUnparenthesizedAsync(ParseNode* node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); - - return node->pn_atom == cx->names().async; + bool isAsyncKeyword(ParseNode* node, ExclusiveContext* cx) { + return node->isKind(PNK_NAME) && + node->pn_pos.begin + strlen("async") == node->pn_pos.end && + node->pn_atom == cx->names().async; } bool isCall(ParseNode* pn) { diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 3b7a0e612..7d923420a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -5147,7 +5147,10 @@ Parser::exportDeclaration() return null(); break; default: { - if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) { + if (tt == TOK_NAME && + tokenStream.currentName() == context->names().async && + !tokenStream.currentToken().nameContainsEscape()) + { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); @@ -7045,21 +7048,23 @@ Parser::statementListItem(YieldHandling yieldHandling, if (!tokenStream.peekToken(&next)) return null(); - if (!tokenStream.currentToken().nameContainsEscape() && - tokenStream.currentName() == context->names().let && - nextTokenContinuesLetDeclaration(next, yieldHandling)) - { - return lexicalDeclaration(yieldHandling, /* isConst = */ false); - } + if (!tokenStream.currentToken().nameContainsEscape()) { + if (tokenStream.currentName() == context->names().let && + nextTokenContinuesLetDeclaration(next, yieldHandling)) + { + return lexicalDeclaration(yieldHandling, /* isConst = */ false); + } - if (tokenStream.currentName() == context->names().async) { - TokenKind nextSameLine = TOK_EOF; - if (!tokenStream.peekTokenSameLine(&nextSameLine)) - return null(); - if (nextSameLine == TOK_FUNCTION) { - uint32_t preludeStart = pos().begin; - tokenStream.consumeKnownToken(TOK_FUNCTION); - return functionStmt(preludeStart, yieldHandling, NameRequired, AsyncFunction); + if (tokenStream.currentName() == context->names().async) { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + if (nextSameLine == TOK_FUNCTION) { + uint32_t preludeStart = pos().begin; + tokenStream.consumeKnownToken(TOK_FUNCTION); + return functionStmt(preludeStart, yieldHandling, NameRequired, AsyncFunction); + } } } @@ -7516,7 +7521,10 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl return yieldExpression(inHandling); bool maybeAsyncArrow = false; - if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) { + if (tt == TOK_NAME && + tokenStream.currentName() == context->names().async && + !tokenStream.currentToken().nameContainsEscape()) + { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); @@ -7537,6 +7545,7 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (maybeAsyncArrow) { tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand); MOZ_ASSERT(tokenStream.currentName() == context->names().async); + MOZ_ASSERT(!tokenStream.currentToken().nameContainsEscape()); TokenKind tt; if (!tokenStream.getToken(&tt)) @@ -7613,7 +7622,9 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (next == TOK_NAME) { tokenStream.consumeKnownToken(next, TokenStream::Operand); - if (tokenStream.currentName() == context->names().async) { + if (tokenStream.currentName() == context->names().async && + !tokenStream.currentToken().nameContainsEscape()) + { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); @@ -8448,8 +8459,27 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling JSOp op = JSOP_CALL; bool maybeAsyncArrow = false; - if (tt == TOK_LP && handler.isNameAnyParentheses(lhs)) { - if (handler.nameIsEvalAnyParentheses(lhs, context)) { + if (PropertyName* prop = handler.maybeDottedProperty(lhs)) { + // Use the JSOP_FUN{APPLY,CALL} optimizations given the + // right syntax. + if (prop == context->names().apply) { + op = JSOP_FUNAPPLY; + if (pc->isFunctionBox()) { + pc->functionBox()->usesApply = true; + } + } else if (prop == context->names().call) { + op = JSOP_FUNCALL; + } + } else if (tt == TOK_LP) { + if (handler.isAsyncKeyword(lhs, context)) { + // |async (| can be the start of an async arrow + // function, so we need to defer reporting possible + // errors from destructuring syntax. To give better + // error messages, we only allow the AsyncArrowHead + // part of the CoverCallExpressionAndAsyncArrowHead + // syntax when the initial name is "async". + maybeAsyncArrow = true; + } else if (handler.isEvalAnyParentheses(lhs, context)) { // Select the right EVAL op and flag pc as having a // direct eval. op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL; @@ -8466,24 +8496,6 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling // it. (If we're not in a method, that's fine, so // ignore the return value.) checkAndMarkSuperScope(); - } else if (handler.nameIsUnparenthesizedAsync(lhs, context)) { - // |async (| can be the start of an async arrow - // function, so we need to defer reporting possible - // errors from destructuring syntax. To give better - // error messages, we only allow the AsyncArrowHead - // part of the CoverCallExpressionAndAsyncArrowHead - // syntax when the initial name is "async". - maybeAsyncArrow = true; - } - } else if (PropertyName* prop = handler.maybeDottedProperty(lhs)) { - // Use the JSOP_FUN{APPLY,CALL} optimizations given the - // right syntax. - if (prop == context->names().apply) { - op = JSOP_FUNAPPLY; - if (pc->isFunctionBox()) - pc->functionBox()->usesApply = true; - } else if (prop == context->names().call) { - op = JSOP_FUNCALL; } } @@ -8816,7 +8828,10 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, return null(); } - if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) { + if (ltok == TOK_NAME && + tokenStream.currentName() == context->names().async && + !tokenStream.currentToken().nameContainsEscape()) + { // AsyncMethod[Yield, Await]: // async [no LineTerminator here] PropertyName[?Yield, ?Await] ... // @@ -9404,7 +9419,9 @@ Parser::primaryExpr(YieldHandling yieldHandling, TripledotHandling case TOK_YIELD: case TOK_NAME: { - if (tokenStream.currentName() == context->names().async) { + if (tokenStream.currentName() == context->names().async && + !tokenStream.currentToken().nameContainsEscape()) + { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 00ea9d35d..9dc3c1072 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -9,6 +9,8 @@ #include "mozilla/Attributes.h" +#include + #include "frontend/ParseNode.h" #include "frontend/TokenStream.h" @@ -94,10 +96,13 @@ class SyntaxParseHandler // Nodes representing unparenthesized names. NodeUnparenthesizedArgumentsName, - NodeUnparenthesizedAsyncName, NodeUnparenthesizedEvalName, NodeUnparenthesizedName, + // Node representing the "async" name, which may actually be a + // contextual keyword. + NodePotentialAsyncKeyword, + // Valuable for recognizing potential destructuring patterns. NodeUnparenthesizedArray, NodeUnparenthesizedObject, @@ -183,8 +188,8 @@ class SyntaxParseHandler lastAtom = name; if (name == cx->names().arguments) return NodeUnparenthesizedArgumentsName; - if (name == cx->names().async) - return NodeUnparenthesizedAsyncName; + if (pos.begin + strlen("async") == pos.end && name == cx->names().async) + return NodePotentialAsyncKeyword; if (name == cx->names().eval) return NodeUnparenthesizedEvalName; return NodeUnparenthesizedName; @@ -497,7 +502,7 @@ class SyntaxParseHandler return NodeParenthesizedArgumentsName; if (node == NodeUnparenthesizedEvalName) return NodeParenthesizedEvalName; - if (node == NodeUnparenthesizedName || node == NodeUnparenthesizedAsyncName) + if (node == NodeUnparenthesizedName || node == NodePotentialAsyncKeyword) return NodeParenthesizedName; if (node == NodeUnparenthesizedArray) @@ -528,9 +533,9 @@ class SyntaxParseHandler bool isUnparenthesizedName(Node node) { return node == NodeUnparenthesizedArgumentsName || - node == NodeUnparenthesizedAsyncName || node == NodeUnparenthesizedEvalName || - node == NodeUnparenthesizedName; + node == NodeUnparenthesizedName || + node == NodePotentialAsyncKeyword; } bool isNameAnyParentheses(Node node) { @@ -541,9 +546,7 @@ class SyntaxParseHandler node == NodeParenthesizedName; } - bool nameIsEvalAnyParentheses(Node node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); + bool isEvalAnyParentheses(Node node, ExclusiveContext* cx) { return node == NodeUnparenthesizedEvalName || node == NodeParenthesizedEvalName; } @@ -551,17 +554,15 @@ class SyntaxParseHandler MOZ_ASSERT(isNameAnyParentheses(node), "must only call this method on known names"); - if (nameIsEvalAnyParentheses(node, cx)) + if (isEvalAnyParentheses(node, cx)) return js_eval_str; if (node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName) return js_arguments_str; return nullptr; } - bool nameIsUnparenthesizedAsync(Node node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); - return node == NodeUnparenthesizedAsyncName; + bool isAsyncKeyword(Node node, ExclusiveContext* cx) { + return node == NodePotentialAsyncKeyword; } PropertyName* maybeDottedProperty(Node node) { diff --git a/js/src/tests/ecma_7/AsyncFunctions/async-contains-unicode-escape.js b/js/src/tests/ecma_7/AsyncFunctions/async-contains-unicode-escape.js new file mode 100644 index 000000000..d53dff696 --- /dev/null +++ b/js/src/tests/ecma_7/AsyncFunctions/async-contains-unicode-escape.js @@ -0,0 +1,54 @@ +var BUGNUMBER = 1315815; +var summary = "async/await containing escapes"; + +print(BUGNUMBER + ": " + summary); + +// Using "eval" as the argument name is fugly, but it means evals below are +// *direct* evals, and so their effects in the unescaped case won't extend +// past each separate |test| call (as would happen if we used a different name +// that made each eval into an indirect eval, affecting code in the global +// scope). +function test(code, eval) +{ + var unescaped = code.replace("###", "async"); + var escaped = code.replace("###", "\\u0061"); + + assertThrowsInstanceOf(() => eval(escaped), SyntaxError); + eval(unescaped); +} + +test("### function f() {}", eval); +test("var x = ### function f() {}", eval); +test("### x => {};", eval); +test("var x = ### x => {}", eval); +test("### () => {};", eval); +test("var x = ### () => {}", eval); +test("### (y) => {};", eval); +test("var x = ### (y) => {}", eval); +test("({ ### x() {} })", eval); +test("var x = ### function f() {}", eval); + +if (typeof parseModule === "function") + test("export default ### function f() {}", parseModule); + +assertThrowsInstanceOf(() => eval("async await => 1;"), + SyntaxError); +assertThrowsInstanceOf(() => eval("async aw\\u0061it => 1;"), + SyntaxError); + +var async = 0; +assertEq(\u0061sync, 0); + +var obj = { \u0061sync() { return 1; } }; +assertEq(obj.async(), 1); + +async = function() { return 42; }; + +var z = async(obj); +assertEq(z, 42); + +var w = async(obj)=>{}; +assertEq(typeof w, "function"); + +if (typeof reportCompare === "function") + reportCompare(true, true);