Skip to content

Commit 819dd72

Browse files
dhechtjustinmichaud
authored andcommitted
[JSC] Disallow yield/await expressions in class field initializers
https://bugs.webkit.org/show_bug.cgi?id=278589 rdar://132338331 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 However, if the field initializer expression is an async function expression, then 'await' is allowed within that expression. 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. * JSTests/stress/yield-await-class-field-initializer-expr.js: Added. (genTestCases): (genTestScript.append): (genTestScript): (expected): (testNegativeCases): (testPositiveCases.assertEq): (testPositiveCases.yieldFn0): * Source/JavaScriptCore/parser/Parser.cpp: (JSC::Parser<LexerType>::parseFunctionBody): (JSC::Parser<LexerType>::parseClass): (JSC::Parser<LexerType>::parseYieldExpression): (JSC::Parser<LexerType>::parseAwaitExpression): (JSC::Parser<LexerType>::parsePrimaryExpression): (JSC::Parser<LexerType>::parseUnaryExpression): * Source/JavaScriptCore/parser/Parser.h: Canonical link: https://commits.webkit.org/282819@main
1 parent e554b75 commit 819dd72

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Tests for 'yield' and 'await' inside class field initializers, where the class is declared inside
2+
// async and generator functions.
3+
const verbose = false;
4+
5+
const wrappers = ['none', 'generator', 'async'];
6+
const fieldModifiers = ['', 'static'];
7+
const fieldInitExprs = ['yield', 'yield 42', 'function() { yield 21; }', 'await', 'await 3', '() => await 7', 'function() { await 9; }'];
8+
9+
function genTestCases() {
10+
let cases = [];
11+
for (const wrapper of wrappers) {
12+
for (const fieldModifier of fieldModifiers) {
13+
for (const fieldInitExpr of fieldInitExprs) {
14+
cases.push({ wrapper, fieldModifier, fieldInitExpr });
15+
}
16+
}
17+
}
18+
return cases;
19+
}
20+
21+
function genTestScript(c) {
22+
let tabs = 0;
23+
let script = "";
24+
25+
function append(line) {
26+
for (t = 0; t < tabs; t++)
27+
script += ' ';
28+
script += line + '\n';
29+
}
30+
31+
switch (c.wrapper) {
32+
case 'generator':
33+
append('function * g() {');
34+
break;
35+
case 'async':
36+
append('async function f() {');
37+
break;
38+
case 'none':
39+
break;
40+
}
41+
tabs++;
42+
append('class C {');
43+
tabs++;
44+
append(`${c.fieldModifier} f = ${c.fieldInitExpr};`);
45+
tabs--;
46+
append('}');
47+
tabs--;
48+
if (c.wrapper !== 'none') append('}');
49+
return script;
50+
}
51+
52+
function expected(c, result, error) {
53+
if (c.fieldInitExpr === 'await') {
54+
// 'await' will parse as an identifier.
55+
if (c.wrapper === 'none' && c.fieldModifier === 'static') {
56+
// In this case, 'await' as identifier produces a ReferenceError.
57+
return result === null && error instanceof ReferenceError;
58+
}
59+
// In these cases, 'await' as identifier has value 'undefined').
60+
return result === undefined && error === null;
61+
}
62+
// All other cases should result in a SyntaxError.
63+
return result === null && error instanceof SyntaxError;
64+
}
65+
66+
// Verify that 'await' and 'yield' do not parse as keywords (directly) inside class field initializers.
67+
function testNegativeCases() {
68+
cases = genTestCases();
69+
70+
for (const c of cases) {
71+
let script = genTestScript(c);
72+
let result = null;
73+
let error = null;
74+
try {
75+
result = eval(script);
76+
} catch (e) {
77+
error = e;
78+
}
79+
80+
if (verbose || !expected(c, result, error)) {
81+
print(`Case: ${c.wrapper}:${c.fieldModifier}:${c.fieldInitExpr}`);
82+
print(`Script:\n${script}`);
83+
print(`Result: ${result}`);
84+
print(`Error: ${error}\n`);
85+
}
86+
}
87+
}
88+
89+
// Verify that 'await' and 'yield' work inside anonymous async / generator functions
90+
// used as class field initializers.
91+
function testPositiveCases() {
92+
function assertEq(got, expected) {
93+
if (got !== expected) {
94+
throw Error(`Got: ${got}, Expected: ${expected}`);
95+
}
96+
}
97+
98+
class C {
99+
asyncFn0 = async y => await y;
100+
asyncFn1 = async () => { return await 5 };
101+
asyncFn2 = async function(x) { return await x; };
102+
103+
yieldFn0 = function* () { yield 9; };
104+
yieldFn1 = async function* () { yield await 11; };
105+
};
106+
let c = new C();
107+
108+
c.asyncFn0(3).then(x => assertEq(x, 3));
109+
c.asyncFn1().then(x => assertEq(x, 5));
110+
c.asyncFn2(7).then(x => assertEq(x, 7));
111+
112+
assertEq(c.yieldFn0().next().value, 9);
113+
c.yieldFn1().next().then(x => assertEq(x.value, 11));
114+
}
115+
116+
testNegativeCases();
117+
testPositiveCases();

Source/JavaScriptCore/parser/Parser.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -2239,6 +2239,7 @@ template <class TreeBuilder> TreeFunctionBody Parser<LexerType>::parseFunctionBo
22392239
ConstructorKind constructorKind, SuperBinding superBinding, FunctionBodyType bodyType, unsigned parameterCount)
22402240
{
22412241
SetForScope overrideParsingClassFieldInitializer(m_parserState.isParsingClassFieldInitializer, bodyType == StandardFunctionBodyBlock ? false : m_parserState.isParsingClassFieldInitializer);
2242+
SetForScope maybeUnmaskAsync(m_parserState.classFieldInitMasksAsync, isAsyncFunctionParseMode(m_parseMode) ? false : m_parserState.classFieldInitMasksAsync);
22422243
bool isArrowFunctionBodyExpression = bodyType == ArrowFunctionBodyExpression;
22432244
if (!isArrowFunctionBodyExpression) {
22442245
next();
@@ -3224,6 +3225,7 @@ template <class TreeBuilder> TreeClassExpression Parser<LexerType>::parseClass(T
32243225
size_t usedVariablesSize = currentScope()->currentUsedVariablesSize();
32253226
currentScope()->pushUsedVariableSet();
32263227
SetForScope overrideParsingClassFieldInitializer(m_parserState.isParsingClassFieldInitializer, true);
3228+
SetForScope maskAsync(m_parserState.classFieldInitMasksAsync, true);
32273229
classScope->setExpectedSuperBinding(SuperBinding::Needed);
32283230
initializer = parseAssignmentExpression(context);
32293231
classScope->setExpectedSuperBinding(SuperBinding::NotNeeded);
@@ -4331,6 +4333,9 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseYieldExpress
43314333
// http://ecma-international.org/ecma-262/6.0/#sec-generator-function-definitions-static-semantics-early-errors
43324334
failIfTrue(m_parserState.functionParsePhase == FunctionParsePhase::Parameters, "Cannot use yield expression within parameters");
43334335

4336+
// https://github.com/tc39/ecma262/issues/3333
4337+
failIfTrue(m_parserState.isParsingClassFieldInitializer, "Cannot use yield expression inside class field initializer expression");
4338+
43344339
JSTokenLocation location(tokenLocation());
43354340
JSTextPosition divotStart = tokenStartPosition();
43364341
ASSERT(match(YIELD));
@@ -4357,6 +4362,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseAwaitExpress
43574362
ASSERT(currentScope()->isAsyncFunction() || isModuleParseMode(sourceParseMode()));
43584363
ASSERT(isAsyncFunctionParseMode(sourceParseMode()) || isModuleParseMode(sourceParseMode()));
43594364
ASSERT(m_parserState.functionParsePhase != FunctionParsePhase::Parameters);
4365+
ASSERT(!m_parserState.classFieldInitMasksAsync);
43604366
JSTokenLocation location(tokenLocation());
43614367
JSTextPosition divotStart = tokenStartPosition();
43624368
next();
@@ -5091,7 +5097,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parsePrimaryExpre
50915097
semanticFailIfTrue(currentScope()->isStaticBlock(), "The 'await' keyword is disallowed in the IdentifierReference position within static block");
50925098
if (m_parserState.functionParsePhase == FunctionParsePhase::Parameters)
50935099
semanticFailIfFalse(m_parserState.allowAwait, "Cannot use 'await' within a parameter default expression");
5094-
else if (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode()))
5100+
else if (!m_parserState.classFieldInitMasksAsync && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))
50955101
return parseAwaitExpression(context);
50965102

50975103
goto identifierExpression;
@@ -5588,7 +5594,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseUnaryExpress
55885594
bool hasPrefixUpdateOp = false;
55895595
unsigned lastOperator = 0;
55905596

5591-
if (UNLIKELY(match(AWAIT) && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) {
5597+
if (UNLIKELY(match(AWAIT) && !m_parserState.classFieldInitMasksAsync && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) {
55925598
semanticFailIfTrue(currentScope()->isStaticBlock(), "Cannot use 'await' within static block");
55935599
return parseAwaitExpression(context);
55945600
}

Source/JavaScriptCore/parser/Parser.h

+1
Original file line numberDiff line numberDiff line change
@@ -2018,6 +2018,7 @@ class Parser {
20182018
const Identifier* lastPrivateName { nullptr };
20192019
bool allowAwait { true };
20202020
bool isParsingClassFieldInitializer { false };
2021+
bool classFieldInitMasksAsync { false };
20212022
};
20222023

20232024
// If you're using this directly, you probably should be using

0 commit comments

Comments
 (0)