From eaf93e3b75047dff56c3b11f612ab7660a1fcbf6 Mon Sep 17 00:00:00 2001 From: Dan Hecht Date: Wed, 10 Jul 2024 15:53:24 -0700 Subject: [PATCH] [JSC] Disallow yield/await expressions in class field initializers https://bugs.webkit.org/show_bug.cgi?id=276438 rdar://119044881 Reviewed by Yusuke Suzuki. The language spec doesn't explictly disallow yield and await expressions in class field initializers, however it implicitly does given that the expression is effectively evaluated as if it's inside a method. Additionally, the consensus seems to be that these expressions should not be allowed, see: https://github.com/tc39/ecma262/issues/3333 The yield and await expressions are now handled differently, but similar to how they are each handled in other contexts. This also seems to be the least disruptive way to handle existing scripts, as well as consistent with other engines: yield: raise syntax error await: parse as an identifier Also adding a new test that generates and verifies scripts containing a class with a field initializer containing yield and await, where the class is either not nested or nested inside generator or async functions to verify the behavior of various combinations. * Source/JavaScriptCore/parser/Parser.cpp: (JSC::Parser::parseYieldExpression): (JSC::Parser::parseAwaitExpression): (JSC::Parser::parsePrimaryExpression): (JSC::Parser::parseUnaryExpression): Canonical link: https://commits.webkit.org/280837@main --- ...ield-await-class-field-initializer-expr.js | 89 +++++++++++++++++++ Source/JavaScriptCore/parser/Parser.cpp | 8 +- 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 JSTests/stress/yield-await-class-field-initializer-expr.js diff --git a/JSTests/stress/yield-await-class-field-initializer-expr.js b/JSTests/stress/yield-await-class-field-initializer-expr.js new file mode 100644 index 0000000000000..a9beef76ff14a --- /dev/null +++ b/JSTests/stress/yield-await-class-field-initializer-expr.js @@ -0,0 +1,89 @@ +// Tests for 'yield' and 'await' inside class field initializers, where the class is declared inside +// async and generator functions. +const verbose = false; + +const wrappers = ['none', 'generator', 'async']; +const fieldModifiers = ['', 'static']; +const fieldInitExprs = ['yield', 'yield 42', 'await', 'await 3']; + +function genTestCases() { + let cases = []; + for (const wrapper of wrappers) { + for (const fieldModifier of fieldModifiers) { + for (const fieldInitExpr of fieldInitExprs) { + cases.push({ wrapper, fieldModifier, fieldInitExpr }); + } + } + } + return cases; +} + +function genTestScript(c) { + let tabs = 0; + let script = ""; + + function append(line) { + for (t = 0; t < tabs; t++) + script += ' '; + script += line + '\n'; + } + + switch (c.wrapper) { + case 'generator': + append('function * g() {'); + break; + case 'async': + append('async function f() {'); + break; + case 'none': + break; + } + tabs++; + append('class C {'); + tabs++; + append(`${c.fieldModifier} f = ${c.fieldInitExpr};`); + tabs--; + append('}'); + tabs--; + if (c.wrapper !== 'none') append('}'); + return script; +} + +function expected(c, result, error) { + if (c.fieldInitExpr === 'await') { + // 'await' will parse as an identifier. + if (c.wrapper === 'none' && c.fieldModifier === 'static') { + // In this case, 'await' as identifier produces a ReferenceError. + return result === null && error instanceof ReferenceError; + } + // In these cases, 'await' as identifier has value 'undefined'). + return result === undefined && error === null; + } + // All other cases should result in a SyntaxError. + return result === null && error instanceof SyntaxError; +} + +cases = genTestCases(); + +for (const c of cases) { + let script = genTestScript(c); + let result = null; + let error = null; + try { + result = eval(script); + } catch (e) { + error = e; + } + + if (verbose || !expected(c, result, error)) { + print(`Case: ${c.wrapper}:${c.fieldModifier}:${c.fieldInitExpr}`); + print(`Script:\n${script}`); + if (result != null) { + print("Result: " + result); + } else if (error != null) { + print("Error: " + error); + } else { + print("Expecting either result or error!") + } + } +} diff --git a/Source/JavaScriptCore/parser/Parser.cpp b/Source/JavaScriptCore/parser/Parser.cpp index ee8255d2a40a3..410d8d0e126ff 100644 --- a/Source/JavaScriptCore/parser/Parser.cpp +++ b/Source/JavaScriptCore/parser/Parser.cpp @@ -4331,6 +4331,9 @@ template TreeExpression Parser::parseYieldExpress // http://ecma-international.org/ecma-262/6.0/#sec-generator-function-definitions-static-semantics-early-errors failIfTrue(m_parserState.functionParsePhase == FunctionParsePhase::Parameters, "Cannot use yield expression within parameters"); + // https://github.com/tc39/ecma262/issues/3333 + failIfTrue(m_parserState.isParsingClassFieldInitializer, "Cannot use yield expression inside class field initializer expression"); + JSTokenLocation location(tokenLocation()); JSTextPosition divotStart = tokenStartPosition(); ASSERT(match(YIELD)); @@ -4357,6 +4360,7 @@ template TreeExpression Parser::parseAwaitExpress ASSERT(currentScope()->isAsyncFunction() || isModuleParseMode(sourceParseMode())); ASSERT(isAsyncFunctionParseMode(sourceParseMode()) || isModuleParseMode(sourceParseMode())); ASSERT(m_parserState.functionParsePhase != FunctionParsePhase::Parameters); + ASSERT(!m_parserState.isParsingClassFieldInitializer); JSTokenLocation location(tokenLocation()); JSTextPosition divotStart = tokenStartPosition(); next(); @@ -5091,7 +5095,7 @@ template TreeExpression Parser::parsePrimaryExpre semanticFailIfTrue(currentScope()->isStaticBlock(), "The 'await' keyword is disallowed in the IdentifierReference position within static block"); if (m_parserState.functionParsePhase == FunctionParsePhase::Parameters) semanticFailIfFalse(m_parserState.allowAwait, "Cannot use 'await' within a parameter default expression"); - else if (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())) + else if (!m_parserState.isParsingClassFieldInitializer && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode()))) return parseAwaitExpression(context); goto identifierExpression; @@ -5588,7 +5592,7 @@ template TreeExpression Parser::parseUnaryExpress bool hasPrefixUpdateOp = false; unsigned lastOperator = 0; - if (UNLIKELY(match(AWAIT) && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) { + if (UNLIKELY(match(AWAIT) && !m_parserState.isParsingClassFieldInitializer && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) { semanticFailIfTrue(currentScope()->isStaticBlock(), "Cannot use 'await' within static block"); return parseAwaitExpression(context); }