1315815 - Don't treat async or await as a keyword when they contain escapes.

master
Fedor 2019-09-05 20:04:59 +03:00
parent b90c7abc6a
commit 58fb36de9e
4 changed files with 134 additions and 64 deletions

View File

@ -10,6 +10,8 @@
#include "mozilla/Attributes.h"
#include "mozilla/PodOperations.h"
#include <string.h>
#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) {

View File

@ -5147,7 +5147,10 @@ Parser<FullParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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();

View File

@ -9,6 +9,8 @@
#include "mozilla/Attributes.h"
#include <string.h>
#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) {

View File

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