From 2d25f717b9c3cae9c1b0a67cdd4bb37cf5fd142f Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Mon, 19 Aug 2019 14:23:50 -0700 Subject: [PATCH] #521: fix yield handling (includes M1305566 pts 4-7) --- js/src/frontend/Parser.cpp | 83 ++++++-- js/src/tests/ecma_6/Destructuring/shell.js | 0 .../yield-in-object-destr-function.js | 182 ++++++++++++++++ .../yield-in-object-destr-generator.js | 200 ++++++++++++++++++ .../yield-in-object-destr-script.js | 123 +++++++++++ js/src/tests/ecma_6/Generators/syntax.js | 17 +- .../extensions/for-in-with-assignments.js | 6 + 7 files changed, 597 insertions(+), 14 deletions(-) create mode 100644 js/src/tests/ecma_6/Destructuring/shell.js create mode 100644 js/src/tests/ecma_6/Destructuring/yield-in-object-destr-function.js create mode 100644 js/src/tests/ecma_6/Destructuring/yield-in-object-destr-generator.js create mode 100644 js/src/tests/ecma_6/Destructuring/yield-in-object-destr-script.js diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 453ddea83..ab0b945ee 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2221,9 +2221,13 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn } case TOK_YIELD: + // If |yield| is a keyword, this is a syntax error. + if (yieldHandling != YieldIsName) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return false; + } if (!checkYieldNameValidity()) return false; - MOZ_ASSERT(yieldHandling == YieldIsName); goto TOK_NAME; case TOK_TRIPLEDOT: @@ -3263,9 +3267,11 @@ template bool Parser::checkYieldNameValidity() { - // In star generators and in JS >= 1.7, yield is a keyword. Otherwise in - // strict mode, yield is a future reserved word. - if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict()) { + // We might use this when |yield| isn't a name, so don't assert here. + // In JS >= 1.7, yield is a keyword. Otherwise in + // strict mode, yield is a future reserved word. See bug 1305566 + // and TenFourFox issue 521. + if (versionNumber() >= JSVERSION_1_7 || pc->sc->strict()) { report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); return false; } @@ -3316,6 +3322,11 @@ Parser::functionStmt(YieldHandling yieldHandling, DefaultHandling if (tt == TOK_NAME) { name = tokenStream.currentName(); } else if (tt == TOK_YIELD) { + // |yield| must be a name, or this is a syntax error. + if (yieldHandling == YieldIsKeyword) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } if (!checkYieldNameValidity()) return null(); name = tokenStream.currentName(); @@ -3388,6 +3399,10 @@ Parser::functionExpr(InvokedPrediction invoked, FunctionAsyncKind if (tt == TOK_NAME) { name = tokenStream.currentName(); } else if (tt == TOK_YIELD) { + if (yieldHandling == YieldIsKeyword) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } if (!checkYieldNameValidity()) return null(); name = tokenStream.currentName(); @@ -3662,6 +3677,7 @@ Parser::matchLabel(YieldHandling yieldHandling, MutableHandle::declarationName(Node decl, TokenKind tt, BindData::yieldExpression(InHandling inHandling) case TOK_RP: case TOK_COLON: case TOK_COMMA: + case TOK_IN: // No value. exprNode = null(); tokenStream.addModifierException(TokenStream::NoneIsOperand); @@ -6885,6 +6903,7 @@ Parser::tryStatement(YieldHandling yieldHandling) // Even if yield is *not* necessarily a keyword, we still must // check its validity for legacy generators. + MOZ_ASSERT(yieldHandling == YieldIsName); if (!checkYieldNameValidity()) return null(); // Fall through. @@ -7063,9 +7082,12 @@ Parser::classDefinition(YieldHandling yieldHandling, if (tt == TOK_NAME) { name = tokenStream.currentName(); } else if (tt == TOK_YIELD) { + if (yieldHandling == YieldIsKeyword) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } if (!checkYieldNameValidity()) return null(); - MOZ_ASSERT(yieldHandling != YieldIsKeyword); name = tokenStream.currentName(); } else if (classContext == ClassStatement) { if (defaultHandling == AllowDefaultName) { @@ -7375,6 +7397,10 @@ Parser::statement(YieldHandling yieldHandling, bool canHaveDirecti if (!tokenStream.peekToken(&next, modifier)) return null(); if (next == TOK_COLON) { + if (yieldHandling == YieldIsKeyword) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } if (!checkYieldNameValidity()) return null(); return labeledStatement(yieldHandling); @@ -7902,9 +7928,13 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); MOZ_ASSERT(tt == TOK_NAME || tt == TOK_YIELD); - // Check yield validity here. - if (!checkYieldNameValidity()) - return null(); + // Allow constructs like (async event => {}). In this case only + // check for yield validity if the token actually is |yield|. + if (tt == TOK_YIELD) { + MOZ_ASSERT(yieldHandling == YieldIsName); + if (!checkYieldNameValidity()) + return null(); + } if (!tokenStream.getToken(&tt)) return null(); @@ -10086,11 +10116,24 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* } else if (propType == PropertyType::Shorthand) { /* * Support, e.g., |var {x, y} = o| as destructuring shorthand - * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8. + * for |var {x: x, y: y} = o|, and |var o = {x, y}| as initializer + * shorthand for |var o = {x: x, y: y}|. */ - if (!tokenStream.checkForKeyword(propAtom, nullptr)) + TokenKind propToken = TOK_NAME; + if (!tokenStream.checkForKeyword(propAtom, &propToken)) return null(); + // Allow |yield|, as per bug 1305566 part 5 (TenFourFox issue 521). + if (propToken != TOK_NAME && propToken != TOK_YIELD) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, TokenKindToDesc(propToken)); + return null(); + } + if (propToken == TOK_YIELD) { + // Use the same rules whether a name or keyword. + if (!checkYieldNameValidity()) + return null(); + } + Node nameExpr = identifierName(yieldHandling); if (!nameExpr) return null(); @@ -10100,11 +10143,23 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* } else if (propType == PropertyType::CoverInitializedName) { /* * Support, e.g., |var {x=1, y=2} = o| as destructuring shorthand - * with default values, as per ES6 12.14.5 (2016/2/4) + * with default values, as per ES6 12.14.5 */ - if (!tokenStream.checkForKeyword(propAtom, nullptr)) + TokenKind propToken = TOK_NAME; + if (!tokenStream.checkForKeyword(propAtom, &propToken)) return null(); + // Allow |yield|, as per bug 1305566 part 5 (TenFourFox issue 521). + if (propToken != TOK_NAME && propToken != TOK_YIELD) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, TokenKindToDesc(propToken)); + return null(); + } + if (propToken == TOK_YIELD) { + // Use the same rules whether a name or keyword. + if (!checkYieldNameValidity()) + return null(); + } + Node lhs = identifierName(yieldHandling); if (!lhs) return null(); @@ -10303,6 +10358,10 @@ Parser::primaryExpr(YieldHandling yieldHandling, TripledotHandling return stringLiteral(); case TOK_YIELD: + if (yieldHandling == YieldIsKeyword) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } if (!checkYieldNameValidity()) return null(); // Fall through. diff --git a/js/src/tests/ecma_6/Destructuring/shell.js b/js/src/tests/ecma_6/Destructuring/shell.js new file mode 100644 index 000000000..e69de29bb diff --git a/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-function.js b/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-function.js new file mode 100644 index 000000000..9f5eed834 --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-function.js @@ -0,0 +1,182 @@ +/* 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/. */ + + +// Destructuring binding patterns with var. +(function() { + var {a: yield} = {a: "yield-with-name"}; + assertEq(yield, "yield-with-name"); + + var {yield} = {yield: "yield-with-shorthand"}; + assertEq(yield, "yield-with-shorthand"); + + var {yield = 0} = {yield: "yield-with-coverinitname"}; + assertEq(yield, "yield-with-coverinitname"); +})(); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + var {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + var {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + var {yield = 0} = {}; + } +`), SyntaxError); + + +// Destructuring binding patterns with let. +(function(){ + let {a: yield} = {a: "yield-with-name"}; + assertEq(yield, "yield-with-name"); +})(); + +(function() { + let {yield} = {yield: "yield-with-shorthand"}; + assertEq(yield, "yield-with-shorthand"); +})(); + +(function() { + let {yield = 0} = {yield: "yield-with-coverinitname"}; + assertEq(yield, "yield-with-coverinitname"); +})(); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + let {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + let {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + let {yield = 0} = {}; + } +`), SyntaxError); + + +// Destructuring binding patterns with const. +(function() { + const {a: yield} = {a: "yield-with-name"}; + assertEq(yield, "yield-with-name"); +})(); + +(function() { + const {yield} = {yield: "yield-with-shorthand"}; + assertEq(yield, "yield-with-shorthand"); +})(); + +(function() { + const {yield = 0} = {yield: "yield-with-coverinitname"}; + assertEq(yield, "yield-with-coverinitname"); +})(); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + const {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + const {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + const {yield = 0} = {}; + } +`), SyntaxError); + + +// Destructuring binding patterns in parameters. +(function({a: yield} = {a: "yield-with-name"}) { + assertEq(yield, "yield-with-name"); +})(); + +(function({yield} = {yield: "yield-with-shorthand"}) { + assertEq(yield, "yield-with-shorthand"); +})(); + +(function({yield = 0} = {yield: "yield-with-coverinitname"}) { + assertEq(yield, "yield-with-coverinitname"); +})(); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f({a: yield} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f({yield} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f({yield = 0} = {}) { } +`), SyntaxError); + + +// Destructuring assignment pattern. +(function() { + var a, yield; + + ({a: yield} = {a: "yield-with-name"}); + assertEq(yield, "yield-with-name"); + + ({yield} = {yield: "yield-with-shorthand"}); + assertEq(yield, "yield-with-shorthand"); + + ({yield = 0} = {yield: "yield-with-coverinitname"}); + assertEq(yield, "yield-with-coverinitname"); +})(); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + ({a: yield} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + ({yield} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function f() { + ({yield = 0} = {}); + } +`), SyntaxError); + + +if (typeof reportCompare === "function") + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-generator.js b/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-generator.js new file mode 100644 index 000000000..4423f8190 --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-generator.js @@ -0,0 +1,200 @@ +/* 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/. */ + + +// Destructuring binding patterns with var. +assertThrowsInstanceOf(() => eval(` + function* g() { + var {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + var {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + var {yield = 0} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + var {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + var {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + var {yield = 0} = {}; + } +`), SyntaxError); + + +// Destructuring binding patterns with let. +assertThrowsInstanceOf(() => eval(` + function* g() { + let {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + let {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + let {yield = 0} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + let {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + let {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + let {yield = 0} = {}; + } +`), SyntaxError); + + +// Destructuring binding patterns with const. +assertThrowsInstanceOf(() => eval(` + function* g() { + const {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + const {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + const {yield = 0} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + const {a: yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + const {yield} = {}; + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + const {yield = 0} = {}; + } +`), SyntaxError); + + +// Destructuring binding patterns in parameters. +assertThrowsInstanceOf(() => eval(` + function* g({a: yield} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g({yield} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g({yield = 0} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g({a: yield} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g({yield} = {}) { } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g({yield = 0} = {}) { } +`), SyntaxError); + + +// Destructuring assignment pattern. +assertThrowsInstanceOf(() => eval(` + function* g() { + ({a: yield} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + ({yield} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + function* g() { + ({yield = 0} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + ({a: yield} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + ({yield} = {}); + } +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + function* g() { + ({yield = 0} = {}); + } +`), SyntaxError); + + +if (typeof reportCompare === "function") + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-script.js b/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-script.js new file mode 100644 index 000000000..99b48dd41 --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/yield-in-object-destr-script.js @@ -0,0 +1,123 @@ +/* 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/. */ + + +// Destructuring binding patterns with var. +var {a: yield} = {a: "yield-with-name"}; +assertEq(yield, "yield-with-name"); + +var {yield} = {yield: "yield-with-shorthand"}; +assertEq(yield, "yield-with-shorthand"); + +var {yield = 0} = {yield: "yield-with-coverinitname"}; +assertEq(yield, "yield-with-coverinitname"); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + var {a: yield} = {}; +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + var {yield} = {}; +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + var {yield = 0} = {}; +`), SyntaxError); + + +// Destructuring binding patterns with let. +{ + let {a: yield} = {a: "yield-with-name"}; + assertEq(yield, "yield-with-name"); +} + +{ + let {yield} = {yield: "yield-with-shorthand"}; + assertEq(yield, "yield-with-shorthand"); +} + +{ + let {yield = 0} = {yield: "yield-with-coverinitname"}; + assertEq(yield, "yield-with-coverinitname"); +} + +assertThrowsInstanceOf(() => eval(` + "use strict"; + let {a: yield} = {}; +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + let {yield} = {}; +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + let {yield = 0} = {}; +`), SyntaxError); + + +// Destructuring binding patterns with const. +{ + const {a: yield} = {a: "yield-with-name"}; + assertEq(yield, "yield-with-name"); +} + +{ + const {yield} = {yield: "yield-with-shorthand"}; + assertEq(yield, "yield-with-shorthand"); +} + +{ + const {yield = 0} = {yield: "yield-with-coverinitname"}; + assertEq(yield, "yield-with-coverinitname"); +} + +assertThrowsInstanceOf(() => eval(` + "use strict"; + const {a: yield} = {}; +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + const {yield} = {}; +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + const {yield = 0} = {}; +`), SyntaxError); + + +// Destructuring assignment pattern. +({a: yield} = {a: "yield-with-name"}); +assertEq(yield, "yield-with-name"); + +({yield} = {yield: "yield-with-shorthand"}); +assertEq(yield, "yield-with-shorthand"); + +({yield = 0} = {yield: "yield-with-coverinitname"}); +assertEq(yield, "yield-with-coverinitname"); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + ({a: yield} = {}); +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + ({yield} = {}); +`), SyntaxError); + +assertThrowsInstanceOf(() => eval(` + "use strict"; + ({yield = 0} = {}); +`), SyntaxError); + + +if (typeof reportCompare === "function") + reportCompare(0, 0, "ok"); diff --git a/js/src/tests/ecma_6/Generators/syntax.js b/js/src/tests/ecma_6/Generators/syntax.js index 98af71420..b2fec3bcc 100644 --- a/js/src/tests/ecma_6/Generators/syntax.js +++ b/js/src/tests/ecma_6/Generators/syntax.js @@ -117,8 +117,21 @@ assertSyntaxError("function* g() { yield 3 + yield 4; }"); // Yield is still a future-reserved-word in strict mode assertSyntaxError("function f() { 'use strict'; var yield = 13; }"); -// The name of the NFE is let-bound in G, so is invalid. -assertSyntaxError("function* g() { yield (function yield() {}); }"); +// The name of the NFE isn't let-bound in F/G, so this is valid. +function f() { (function yield() {}); } +function* g() { (function yield() {}); } + +// The name of the NFE is let-bound in the function/generator expression, so this is invalid. +assertSyntaxError("function f() { (function* yield() {}); }"); +assertSyntaxError("function* g() { (function* yield() {}); }"); + +// The name of the declaration is let-bound in F, so this is valid. +function f() { function yield() {} } +function f() { function* yield() {} } + +// The name of the declaration is let-bound in G, so this is invalid. +assertSyntaxError("function* g() { function yield() {} }"); +assertSyntaxError("function* g() { function* yield() {} }"); // In generators, yield is invalid as a formal argument name. assertSyntaxError("function* g(yield) { yield (10); }"); diff --git a/js/src/tests/ecma_6/extensions/for-in-with-assignments.js b/js/src/tests/ecma_6/extensions/for-in-with-assignments.js index 5b1443c1c..993630bfd 100644 --- a/js/src/tests/ecma_6/extensions/for-in-with-assignments.js +++ b/js/src/tests/ecma_6/extensions/for-in-with-assignments.js @@ -68,6 +68,12 @@ with (0) /******************************************************************************/ +function* g1() { + for (var x = yield in {}) ; +} +var it = g1(); +assertEq(it.next().done, true); + if (typeof reportCompare === "function") reportCompare(true, true);