Bug 1288460 - Allow escape sequences in the keyword-like but non-reserved 'let' Identifier (in non-strict code). r=arai

--HG--
extra : rebase_source : 953fc9fa2747a031360db5405d83e551ef2e1a96
This commit is contained in:
Jeff Walden 2016-08-30 09:37:26 -07:00
parent f222967fd3
commit 327cce50c3
8 changed files with 131 additions and 71 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1287426 - Clobber required because of Linux Chromium sandbox file moves.
Bug 1288460 requires a clobber due to bug 1298779.

View File

@ -845,7 +845,11 @@ Parser<ParseHandler>::checkStrictBinding(PropertyName* name, TokenPos pos)
if (!pc->sc()->needStrictChecks())
return true;
if (name == context->names().eval || name == context->names().arguments || IsKeyword(name)) {
if (name == context->names().eval ||
name == context->names().arguments ||
name == context->names().let ||
IsKeyword(name))
{
JSAutoByteString bytes;
if (!AtomToPrintableString(context, name, &bytes))
return false;
@ -4859,15 +4863,28 @@ Parser<FullParseHandler>::exportDeclaration()
return node;
}
case TOK_LET:
case TOK_CONST:
kid = lexicalDeclaration(YieldIsName, tt == TOK_CONST);
kid = lexicalDeclaration(YieldIsName, /* isConst = */ true);
if (!kid)
return null();
if (!checkExportedNamesForDeclaration(kid))
return null();
break;
case TOK_NAME:
if (tokenStream.currentName() == context->names().let) {
if (!checkUnescapedName())
return null();
kid = lexicalDeclaration(YieldIsName, /* isConst = */ false);
if (!kid)
return null();
if (!checkExportedNamesForDeclaration(kid))
return null();
break;
}
MOZ_FALLTHROUGH;
default:
report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_EXPORT);
return null();
@ -5113,16 +5130,15 @@ Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling,
// For-in loop backwards compatibility requires that |let| starting a
// for-loop that's not a (new to ES6) for-of loop, in non-strict mode code,
// parse as an identifier. (|let| in for-of is always a declaration.)
// Thus we must can't just sniff out TOK_CONST/TOK_LET here. :-(
bool parsingLexicalDeclaration = false;
bool letIsIdentifier = false;
if (tt == TOK_LET || tt == TOK_CONST) {
if (tt == TOK_CONST) {
parsingLexicalDeclaration = true;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
} else if (tt == TOK_NAME && tokenStream.nextName() == context->names().let) {
MOZ_ASSERT(!pc->sc()->strict(),
"should parse |let| as TOK_LET in strict mode code");
} else if (tt == TOK_NAME &&
tokenStream.nextName() == context->names().let &&
!tokenStream.nextNameContainsEscape())
{
// We could have a {For,Lexical}Declaration, or we could have a
// LeftHandSideExpression with lookahead restrictions so it's not
// ambiguous with the former. Check for a continuation of the former
@ -6420,10 +6436,9 @@ template <class ParseHandler>
bool
Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME),
"TOK_LET should have been summarily considered a "
"LexicalDeclaration");
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME));
MOZ_ASSERT(tokenStream.currentName() == context->names().let);
MOZ_ASSERT(!tokenStream.currentToken().nameContainsEscape());
#ifdef DEBUG
TokenKind verify;
@ -6531,25 +6546,21 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling)
if (!tokenStream.peekToken(&next))
return null();
#ifdef DEBUG
if (tokenStream.currentName() == context->names().let) {
MOZ_ASSERT(!pc->sc()->strict(),
"observing |let| as TOK_NAME and not TOK_LET implies "
"non-strict code (and the edge case of 'use strict' "
"immediately followed by |let| on a new line only "
"applies to StatementListItems, not to Statements)");
}
#endif
// Statement context forbids LexicalDeclaration.
if ((next == TOK_LB || next == TOK_LC || next == TOK_NAME) &&
// |let| here can only be an Identifier, not a declaration. Give nicer
// errors for declaration-looking typos.
if (!tokenStream.currentToken().nameContainsEscape() &&
tokenStream.currentName() == context->names().let)
{
bool forbiddenLetDeclaration = false;
if (next == TOK_LB) {
// ExpressionStatement has a 'let [' lookahead restriction.
if (pc->sc()->strict() || versionNumber() >= JSVERSION_1_7) {
// |let| can't be an Identifier in strict mode code. Ditto for
// non-standard JavaScript 1.7+.
forbiddenLetDeclaration = true;
} else {
} else if (next == TOK_LB) {
// Enforce ExpressionStatement's 'let [' lookahead restriction.
forbiddenLetDeclaration = true;
} else if (next == TOK_LC || next == TOK_NAME) {
// 'let {' and 'let foo' aren't completely forbidden, if ASI
// causes 'let' to be the entire Statement. But if they're
// same-line, we can aggressively give a better error message.
@ -6677,14 +6688,6 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling)
report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY);
return null();
// TOK_LET implies we're in strict mode code where static semantics
// forbid IdentifierName to be "let": a stronger restriction than
// Statement's lookahead restriction on |let [|. Provide a better error
// message here than the default case would.
case TOK_LET:
report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "let declarations");
return null();
// NOTE: default case handled in the ExpressionStatement section.
}
}
@ -6752,23 +6755,11 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
if (!tokenStream.peekToken(&next))
return null();
if (tokenStream.currentName() == context->names().let) {
if (nextTokenContinuesLetDeclaration(next, yieldHandling))
return lexicalDeclaration(yieldHandling, /* isConst = */ false);
// IdentifierName can't be "let" in strict mode code. |let| in
// strict mode code is usually TOK_LET, but in this one weird case
// in global code it's TOK_NAME:
//
// "use strict" // ExpressionStatement ended by ASI
// let <...whatever else...> // a fresh StatementListItem
//
// Carefully reject strict mode |let| non-declarations.
if (pc->sc()->strict()) {
report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
"declaration pattern", TokenKindToDesc(next));
return null();
}
if (!tokenStream.currentToken().nameContainsEscape() &&
tokenStream.currentName() == context->names().let &&
nextTokenContinuesLetDeclaration(next, yieldHandling))
{
return lexicalDeclaration(yieldHandling, /* isConst = */ false);
}
if (next == TOK_COLON)
@ -6854,11 +6845,10 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
// LexicalDeclaration[In, ?Yield]
// LetOrConst BindingList[?In, ?Yield]
case TOK_LET:
case TOK_CONST:
// [In] is the default behavior, because for-loops specially parse
// their heads to handle |in| in this situation.
return lexicalDeclaration(yieldHandling, /* isConst = */ tt == TOK_CONST);
return lexicalDeclaration(yieldHandling, /* isConst = */ true);
// ImportDeclaration (only inside modules)
case TOK_IMPORT:
@ -8254,6 +8244,16 @@ Parser<ParseHandler>::labelOrIdentifierReference(YieldHandling yieldHandling)
ident = tok.name();
MOZ_ASSERT(ident != context->names().yield,
"tokenizer should have treated 'yield' as TOK_YIELD");
if (pc->sc()->strict()) {
const char* badName = ident == context->names().let
? "let"
: nullptr;
if (badName) {
report(ParseError, false, null(), JSMSG_RESERVED_ID, badName);
return nullptr;
}
}
} else {
MOZ_ASSERT(tok.type == TOK_YIELD);
@ -8293,6 +8293,14 @@ Parser<ParseHandler>::bindingIdentifier(YieldHandling yieldHandling)
report(ParseError, false, null(), JSMSG_BAD_STRICT_ASSIGN, badName);
return nullptr;
}
badName = ident == context->names().let
? "let"
: nullptr;
if (badName) {
report(ParseError, false, null(), JSMSG_RESERVED_ID, badName);
return nullptr;
}
}
} else {
MOZ_ASSERT(tok.type == TOK_YIELD);

View File

@ -107,7 +107,6 @@
macro(THROW, "keyword 'throw'") \
macro(DEBUGGER, "keyword 'debugger'") \
macro(YIELD, "keyword 'yield'") \
macro(LET, "keyword 'let'") \
macro(EXPORT, "keyword 'export'") \
macro(IMPORT, "keyword 'import'") \
macro(CLASS, "keyword 'class'") \
@ -239,12 +238,6 @@ TokenKindIsAssignment(TokenKind tt)
return TOK_ASSIGNMENT_START <= tt && tt <= TOK_ASSIGNMENT_LAST;
}
inline bool
TokenKindIsDecl(TokenKind tt)
{
return tt == TOK_VAR || tt == TOK_LET;
}
} // namespace frontend
} // namespace js

View File

@ -981,11 +981,6 @@ TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp)
if (kw->tokentype == TOK_STRICT_RESERVED)
return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
// Treat 'let' as an identifier and contextually a keyword in sloppy mode.
// It is always a keyword in strict mode.
if (kw->tokentype == TOK_LET && !strictMode())
return true;
// Working keyword.
if (ttp) {
*ttp = kw->tokentype;

View File

@ -351,6 +351,13 @@ class MOZ_STACK_CLASS TokenStream
return nextToken().name();
}
bool nextNameContainsEscape() const {
if (nextToken().type == TOK_YIELD)
return false;
MOZ_ASSERT(nextToken().type == TOK_NAME);
return nextToken().nameContainsEscape();
}
bool isCurrentTokenAssignment() const {
return TokenKindIsAssignment(currentToken().type);
}

View File

@ -0,0 +1,52 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
//-----------------------------------------------------------------------------
var BUGNUMBER = 1288460;
var summary =
"|let| and |static| are forbidden as Identifier only in strict mode code, " +
"and it's permissible to use them as Identifier (with or without " +
"containing escapes) in non-strict code";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
function t(code)
{
var strictSemi = " 'use strict'; " + code;
var strictASI = " 'use strict' \n " + code;
var creationFunctions = [Function];
if (typeof evaluate === "function")
creationFunctions.push(evaluate);
if (typeof parseModule === "function")
creationFunctions.push(parseModule);
for (var func of creationFunctions)
{
if (typeof parseModule === "function" && func === parseModule)
assertThrowsInstanceOf(() => func(code), SyntaxError);
else
func(code);
assertThrowsInstanceOf(() => func(strictSemi), SyntaxError);
assertThrowsInstanceOf(() => func(strictASI), SyntaxError);
}
}
t("l\\u0065t: 42;");
t("if (1) l\\u0065t: 42;");
t("l\\u0065t = 42;");
t("if (1) l\\u0065t = 42;");
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete");

View File

@ -13,13 +13,19 @@ print(BUGNUMBER + ": " + summary);
* BEGIN TEST *
**************/
Function("let: 42")
Function("let: 42");
Function("l\\u0065t: 42");
assertThrowsInstanceOf(() => Function(" 'use strict'; let: 42"), SyntaxError);
assertThrowsInstanceOf(() => Function(" 'use strict' \n let: 42"), SyntaxError);
assertThrowsInstanceOf(() => Function(" 'use strict'; l\\u0065t: 42"), SyntaxError);
assertThrowsInstanceOf(() => Function(" 'use strict' \n l\\u0065t: 42"), SyntaxError);
eval("let: 42")
eval("let: 42");
eval("l\\u0065t: 42");
assertThrowsInstanceOf(() => eval(" 'use strict'; let: 42"), SyntaxError);
assertThrowsInstanceOf(() => eval(" 'use strict' \n let: 42;"), SyntaxError);
assertThrowsInstanceOf(() => eval(" 'use strict'; l\\u0065t: 42"), SyntaxError);
assertThrowsInstanceOf(() => eval(" 'use strict' \n l\\u0065t: 42;"), SyntaxError);
/******************************************************************************/

View File

@ -61,7 +61,6 @@
* future reserved keyword in strict mode, but a keyword in JS1.7 even \
* when strict. Punt logic to parser. \
*/ \
macro(yield, yield, TOK_YIELD) \
macro(let, let, TOK_LET)
macro(yield, yield, TOK_YIELD)
#endif /* vm_Keywords_h */