Bug 932517 - Treat let as a contextual keyword in sloppy mode and make it versionless. (r=jorendorff)

This commit is contained in:
Shu-yu Guo 2015-10-27 20:13:17 -07:00
parent 3a242f3099
commit 456707fc56
12 changed files with 182 additions and 82 deletions

View File

@ -84,13 +84,6 @@ exports['test exceptions'] = function(assert) {
}
};
exports['test opt version'] = function(assert) {
let fixture = sandbox();
assert.throws(function() {
evaluate(fixture, 'let a = 2;', 'test.js', 1, '1.5');
}, 'No let in js 1.5');
};
exports['test load'] = function(assert) {
let fixture = sandbox();
load(fixture, fixturesURI + 'sandbox-normal.js');

View File

@ -116,10 +116,10 @@ function report(testName, success) {
<script type="text/javascript"><![CDATA[
try {
eval("let x = 1;");
var success = false;
var success = true;
}
catch (e) { success = true; }
is(success, true, "JS 1.7 should not work in versionless HTML script tags");
catch (e) { success = false; }
is(success, true, "let should work in versionless HTML script tags");
]]></script>
</pre>
</body>

View File

@ -5195,6 +5195,10 @@ Parser<FullParseHandler>::forStatement(YieldHandling yieldHandling)
*/
bool isForDecl = false;
// True if a 'let' token at the head is parsed as an identifier instead of
// as starting a declaration.
bool letIsIdentifier = false;
/* Non-null when isForDecl is true for a 'for (let ...)' statement. */
RootedStaticBlockObject blockObj(context);
@ -5222,19 +5226,38 @@ Parser<FullParseHandler>::forStatement(YieldHandling yieldHandling)
isForDecl = true;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
pn1 = variables(yieldHandling, PNK_VAR, InForInit);
} else if (tt == TOK_LET || tt == TOK_CONST) {
} else if (tt == TOK_LET || tt == TOK_CONST ||
(tt == TOK_NAME && tokenStream.nextName() == context->names().let)) {
handler.disableSyntaxParser();
bool constDecl = tt == TOK_CONST;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
isForDecl = true;
blockObj = StaticBlockObject::create(context);
if (!blockObj)
return null();
// Initialize the enclosing scope manually for the call to
// |variables| below.
blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit,
nullptr, blockObj, DontHoistVars);
// Check for the backwards-compatibility corner case in sloppy
// mode like |for (let in e)| where the 'let' token should be
// parsed as an identifier.
bool parseDecl;
if (tt == TOK_NAME) {
if (!peekShouldParseLetDeclaration(&parseDecl, TokenStream::Operand))
return null();
letIsIdentifier = !parseDecl;
} else {
parseDecl = true;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
}
if (parseDecl) {
bool constDecl = tt == TOK_CONST;
isForDecl = true;
blockObj = StaticBlockObject::create(context);
if (!blockObj)
return null();
// Initialize the enclosing scope manually for the call to
// |variables| below.
blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit,
nullptr, blockObj, DontHoistVars);
} else {
pn1 = expr(InProhibited, yieldHandling, TripledotProhibited);
}
} else {
// Pass |InProhibited| when parsing an expression so that |in|
// isn't parsed in a RelationalExpression as a binary operator.
@ -5309,9 +5332,17 @@ Parser<FullParseHandler>::forStatement(YieldHandling yieldHandling)
bool isForIn, isForOf;
if (!matchInOrOf(&isForIn, &isForOf))
return null();
// In for-in loops, a 'let' token may be used as an identifier for
// backwards-compatibility reasons, e.g., |for (let in e)|. In for-of
// loops, a 'let' token is never parsed as an identifier. Forbid
// trying to parse a for-of loop if we have parsed a 'let' token as an
// identifier above.
//
// See ES6 13.7.5.1.
if (isForIn)
headKind = PNK_FORIN;
else if (isForOf)
else if (isForOf && !letIsIdentifier)
headKind = PNK_FOROF;
}
@ -5567,12 +5598,12 @@ Parser<SyntaxParseHandler>::forStatement(YieldHandling yieldHandling)
isForDecl = true;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
lhsNode = variables(yieldHandling, PNK_VAR, InForInit, &simpleForDecl);
}
else if (tt == TOK_CONST || tt == TOK_LET) {
} else if (tt == TOK_CONST || tt == TOK_LET ||
(tt == TOK_NAME && tokenStream.nextName() == context->names().let))
{
JS_ALWAYS_FALSE(abortIfSyntaxParser());
return null();
}
else {
} else {
lhsNode = expr(InProhibited, yieldHandling, TripledotProhibited);
}
if (!lhsNode)
@ -6627,6 +6658,71 @@ Parser<SyntaxParseHandler>::classDefinition(YieldHandling yieldHandling,
return SyntaxParseHandler::NodeFailure;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::shouldParseLetDeclaration(bool* parseDeclOut)
{
// 'let' is a reserved keyword in strict mode and we shouldn't get here.
MOZ_ASSERT(!pc->sc->strict());
TokenKind tt;
*parseDeclOut = false;
if (!tokenStream.peekToken(&tt))
return false;
switch (tt) {
case TOK_NAME:
// |let let| is disallowed per ES6 13.3.1.1.
*parseDeclOut = tokenStream.nextName() != context->names().let;
break;
case TOK_LC:
case TOK_LB:
// A following name is always a declaration.
//
// |let {| and |let [| are destructuring declarations.
*parseDeclOut = true;
break;
case TOK_LP:
// Only parse let blocks for 1.7 and 1.8. Do not expose deprecated let
// blocks to content.
*parseDeclOut = versionNumber() == JSVERSION_1_7 || versionNumber() == JSVERSION_1_8;
break;
default:
break;
}
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::peekShouldParseLetDeclaration(bool* parseDeclOut,
TokenStream::Modifier modifier)
{
*parseDeclOut = false;
#ifdef DEBUG
TokenKind tt;
if (!tokenStream.peekToken(&tt, modifier))
return false;
MOZ_ASSERT(tt == TOK_NAME && tokenStream.nextName() == context->names().let);
#endif
tokenStream.consumeKnownToken(TOK_NAME, modifier);
if (!shouldParseLetDeclaration(parseDeclOut))
return false;
// Unget the TOK_NAME of 'let' if not parsing a declaration.
if (!*parseDeclOut)
tokenStream.ungetToken();
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::statement(YieldHandling yieldHandling, bool canHaveDirectives)
@ -6696,6 +6792,16 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling, bool canHaveDirecti
}
case TOK_NAME: {
// 'let' is a contextual keyword in sloppy node. In strict mode, it is
// always lexed as TOK_LET.
if (tokenStream.currentName() == context->names().let) {
bool parseDecl;
if (!shouldParseLetDeclaration(&parseDecl))
return null();
if (parseDecl)
return lexicalDeclaration(yieldHandling, /* isConst = */ false);
}
TokenKind next;
if (!tokenStream.peekToken(&next))
return null();

View File

@ -800,6 +800,15 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
ParseNodeKind headKind);
bool checkForHeadConstInitializers(Node pn1);
// Use when the current token is TOK_NAME and is known to be 'let'.
bool shouldParseLetDeclaration(bool* parseDeclOut);
// Use when the lookahead token is TOK_NAME and is known to be 'let'. If a
// let declaration should be parsed, the TOK_NAME token of 'let' is
// consumed. Otherwise, the current token remains the TOK_NAME token of
// 'let'.
bool peekShouldParseLetDeclaration(bool* parseDeclOut, TokenStream::Modifier modifier);
public:
enum FunctionCallBehavior {
PermitAssignmentToFunctionCalls,

View File

@ -999,6 +999,11 @@ TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp)
if (kw->tokentype != TOK_STRICT_RESERVED) {
if (kw->version <= versionNumber()) {
// 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;
@ -1006,12 +1011,6 @@ TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp)
}
return reportError(JSMSG_RESERVED_ID, kw->chars);
}
// The keyword is not in this version. Treat it as an identifier, unless
// it is let which we treat as TOK_STRICT_RESERVED by falling through to
// the code below (ES5 forbids it in strict mode).
if (kw->tokentype != TOK_LET)
return true;
}
// Strict reserved word.

View File

@ -365,6 +365,13 @@ class MOZ_STACK_CLASS TokenStream
return currentToken().name();
}
PropertyName* nextName() const {
if (nextToken().type == TOK_YIELD)
return cx->names().yield;
MOZ_ASSERT(nextToken().type == TOK_NAME);
return nextToken().name();
}
bool isCurrentTokenAssignment() const {
return TokenKindIsAssignment(currentToken().type);
}
@ -999,12 +1006,12 @@ class MOZ_STACK_CLASS TokenStream
void updateLineInfoForEOL();
void updateFlagsForEOL();
const Token& nextToken() {
const Token& nextToken() const {
MOZ_ASSERT(hasLookahead());
return tokens[(cursor + 1) & ntokensMask];
}
bool hasLookahead() { return lookahead > 0; }
bool hasLookahead() const { return lookahead > 0; }
// Options used for parsing/tokenizing.
const ReadOnlyCompileOptions& options_;

View File

@ -57,7 +57,7 @@ var strictIdentifiers = [
'static'
];
assertThrowsInstanceOf(() => new Function('[...yield] = []'), SyntaxError);
assertThrowsInstanceOf(() => new Function('[...let] = []'), SyntaxError);
assertThrowsInstanceOf(() => new Function('"use strict"; [...let] = []'), SyntaxError);
strictIdentifiers.forEach(ident =>
assertThrowsInstanceOf(() =>

View File

@ -0,0 +1,29 @@
function expectError(str) {
var log = "";
try {
eval(str);
} catch (e) {
log += "e";
assertEq(e instanceof SyntaxError, true);
}
assertEq(log, "e");
}
eval(`let x = 42; assertEq(x, 42);`);
eval(`var let = 42; assertEq(let, 42);`);
eval(`let;`);
eval(`[...let] = [];`);
eval(`function let() { return 42; } assertEq(let(), 42);`)
eval(`let {x:x} = {x:42}; assertEq(x, 42);`);
eval(`let [x] = [42]; assertEq(x, 42);`);
eval(`for (let x in [1]) { assertEq(x, "0"); }`);
eval(`for (let x of [1]) { assertEq(x, 1); }`);
eval(`for (let i = 0; i < 1; i++) { assertEq(i, 0); }`);
eval(`for (let in [1]) { assertEq(let, "0"); }`);
eval(`for (let of of [1]) { assertEq(of, 1); }`);
eval(`for (let/1;;) { break; }`);
expectError(`for (let of [1]) { }`);
expectError(`let let = 42;`);
expectError(`"use strict"; var let = 42;`);
expectError(`"use strict"; function let() {}`);
expectError(`"use strict"; for (let of [1]) {}`);

View File

@ -6,7 +6,6 @@ var cases = [
"var",
"var x,",
"var x =",
"let",
"let x,",
"let x =",
"const",

View File

@ -1,38 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
//-----------------------------------------------------------------------------
var BUGNUMBER = 352392;
var summary = 'Do not hang/crash |for each| over object with getter set to map';
var actual = 'No Crash';
var expect = 'No Crash';
//-----------------------------------------------------------------------------
test();
//-----------------------------------------------------------------------------
function test()
{
enterFunc ('test');
printBugNumber(BUGNUMBER);
printStatus (summary);
expect = 'SyntaxError: invalid for each loop';
try
{
var obj = { };
Object.defineProperty(obj, "y", { get: Array.prototype.map, enumerable: true, configurable: true });
eval('(function() { for each(let z in obj) { } })()');
}
catch(ex)
{
actual = ex + '';
}
reportCompare(expect, actual, summary);
exitFunc ('test');
}

View File

@ -40,7 +40,7 @@ reportCompare(expect, actual, summary + ': local: yield = 1');
try
{
expect = "SyntaxError";
expect = "No Error";
eval('let = 1;');
actual = 'No Error';
}
@ -83,7 +83,7 @@ function test()
try
{
expect = "SyntaxError";
expect = "No Error";
eval('var let = 1;');
actual = 'No Error';
}

View File

@ -62,10 +62,6 @@
* when strict. Punt logic to parser. \
*/ \
macro(yield, yield, TOK_YIELD, JSVERSION_DEFAULT) \
/* \
* Let is a future reserved keyword in strict mode, and a keyword in \
* JS1.7. \
*/ \
macro(let, let, TOK_LET, JSVERSION_1_7)
macro(let, let, TOK_LET, JSVERSION_DEFAULT)
#endif /* vm_Keywords_h */