Skip to content

Commit

Permalink
[JSC] Disallow yield/await expressions in class field initializers
Browse files Browse the repository at this point in the history
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:

tc39/ecma262#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<LexerType>::parseYieldExpression):
(JSC::Parser<LexerType>::parseAwaitExpression):
(JSC::Parser<LexerType>::parsePrimaryExpression):
(JSC::Parser<LexerType>::parseUnaryExpression):

Canonical link: https://commits.webkit.org/280837@main
  • Loading branch information
dhecht committed Aug 21, 2024
1 parent 8e565ca commit eaf93e3
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 2 deletions.
89 changes: 89 additions & 0 deletions JSTests/stress/yield-await-class-field-initializer-expr.js
Original file line number Diff line number Diff line change
@@ -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!")
}
}
}
8 changes: 6 additions & 2 deletions Source/JavaScriptCore/parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4331,6 +4331,9 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::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));
Expand All @@ -4357,6 +4360,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::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();
Expand Down Expand Up @@ -5091,7 +5095,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::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;
Expand Down Expand Up @@ -5588,7 +5592,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::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);
}
Expand Down

0 comments on commit eaf93e3

Please sign in to comment.