Skip to content

Commit

Permalink
Forbid await/yield/arguments in class field inits
Browse files Browse the repository at this point in the history
Summary:
Certain language features aren't allowed in class field initializers.
'arguments' is forbidden by the spec.

Yield/await should be forbidden:
tc39/ecma262#3333

Reviewed By: fbmal7

Differential Revision: D66266912

fbshipit-source-id: 859ab611add5b7e4cdd01a3167113255de4bff61
  • Loading branch information
avp authored and facebook-github-bot committed Dec 3, 2024
1 parent d1b0f84 commit a642414
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 4 deletions.
40 changes: 36 additions & 4 deletions lib/AST/SemanticValidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,11 @@ void SemanticValidator::visit(IdentifierNode *identifier) {
if (identifier->_name == kw_.identEval && !astContext_.getEnableEval())
sm_.error(identifier->getSourceRange(), "'eval' is disabled");

if (identifier->_name == kw_.identArguments)
if (identifier->_name == kw_.identArguments) {
if (forbidArguments_)
sm_.error(identifier->getSourceRange(), "invalid use of 'arguments'");
curFunction()->semInfo->usesArguments = true;
}
}

/// Process a function declaration by creating a new FunctionContext.
Expand Down Expand Up @@ -605,8 +608,7 @@ void SemanticValidator::visit(YieldExpressionNode *yieldExpr) {
}

void SemanticValidator::visit(AwaitExpressionNode *awaitExpr) {
if (curFunction()->isGlobalScope() ||
(curFunction()->node && !ESTree::isAsync(curFunction()->node)))
if (forbidAwaitExpression_)
sm_.error(awaitExpr->getSourceRange(), "'await' not in an async function");

visitESTreeChildren(*this, awaitExpr);
Expand Down Expand Up @@ -658,7 +660,27 @@ void SemanticValidator::visit(PrivateNameNode *node) {
void SemanticValidator::visit(ClassPrivatePropertyNode *node) {
if (compile_)
sm_.error(node->getSourceRange(), "private properties are not supported");
visitESTreeChildren(*this, node);
visitESTreeNode(*this, node->_key);
{
SaveAndRestore<bool> oldForbidAwait{forbidAwaitExpression_, true};
// ES14.0 15.7.1
// It is a Syntax Error if Initializer is present and ContainsArguments of
// Initializer is true.
SaveAndRestore<bool> oldForbidArguments{forbidArguments_, true};
visitESTreeNode(*this, node->_value);
}
}

void SemanticValidator::visit(ClassPropertyNode *node) {
visitESTreeNode(*this, node->_key);
{
SaveAndRestore<bool> oldForbidAwait{forbidAwaitExpression_, true};
// ES14.0 15.7.1
// It is a Syntax Error if Initializer is present and ContainsArguments of
// Initializer is true.
SaveAndRestore<bool> oldForbidArguments{forbidArguments_, true};
visitESTreeNode(*this, node->_value);
}
}

void SemanticValidator::visit(ImportDeclarationNode *importDecl) {
Expand Down Expand Up @@ -884,6 +906,16 @@ void SemanticValidator::visitFunction(
}
}

// 'await' forbidden outside async functions.
llvh::SaveAndRestore<bool> oldForbidAwait{
forbidAwaitExpression_, !ESTree::isAsync(node)};
// Forbidden-ness of 'arguments' passes through arrow functions because they
// use the same 'arguments'.
llvh::SaveAndRestore<bool> oldForbidArguments{
forbidArguments_,
llvh::isa<ESTree::ArrowFunctionExpressionNode>(node) ? forbidArguments_
: false};

visitParamsAndBody(node);
}

Expand Down
7 changes: 7 additions & 0 deletions lib/AST/SemanticValidator.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ class SemanticValidator {
/// The current function context.
FunctionContext *funcCtx_{};

/// True if we are forbidding await expressions.
bool forbidAwaitExpression_{false};

/// True if we are forbidding 'arguments' identifier.
bool forbidArguments_{false};

/// True if we are validating a formal parameter list.
bool isFormalParams_{false};

Expand Down Expand Up @@ -207,6 +213,7 @@ class SemanticValidator {
void visit(ClassDeclarationNode *node);
void visit(PrivateNameNode *node);
void visit(ClassPrivatePropertyNode *node);
void visit(ClassPropertyNode *node);

void visit(ImportDeclarationNode *importDecl);
void visit(ImportDefaultSpecifierNode *importDecl);
Expand Down
6 changes: 6 additions & 0 deletions lib/Parser/JSParserImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4964,6 +4964,12 @@ Optional<ESTree::Node *> JSParserImpl::parseClassElement(
if (checkAndEat(TokenKind::equal)) {
// ClassElementName Initializer[opt]
// ^
// NOTE: This is technically non-compliant, but having yield/await in the
// field initializer doesn't make sense.
// See https://github.com/tc39/ecma262/issues/3333
// Do [~Yield, +Await, ~Return] as suggested and error in resolution.
llvh::SaveAndRestore<bool> saveParamYield{paramYield_, false};
llvh::SaveAndRestore<bool> saveParamAwait{paramAwait_, true};
auto optValue = parseAssignmentExpression();
if (!optValue)
return None;
Expand Down
20 changes: 20 additions & 0 deletions test/Parser/arguments-field-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast %s 2>&1 ) | %FileCheckOrRegen --match-full-lines %s

function* foo() {
class C {
x = () => arguments;
}
}

// Auto-generated content below. Please do not modify manually.

// CHECK:{{.*}}arguments-field-error.js:12:15: error: invalid use of 'arguments'
// CHECK-NEXT: x = () => arguments;
// CHECK-NEXT: ^~~~~~~~~
26 changes: 26 additions & 0 deletions test/Parser/await-field-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast %s 2>&1 ) | %FileCheck --match-full-lines %s

async function foo() {
class C {
x = await 1;
}

class D {
x = class { [await 1] = 1; };
}
}


// CHECK:{{.*}}await-field-error.js:12:9: error: 'await' not in an async function
// CHECK-NEXT: x = await 1;
// CHECK-NEXT: ^~~~~~~
// CHECK-NEXT:{{.*}}await-field-error.js:16:18: error: 'await' not in an async function
// CHECK-NEXT: x = class { [await 1] = 1; };
// CHECK-NEXT: ^~~~~~~
93 changes: 93 additions & 0 deletions test/Parser/class-props-yield-await.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: %hermesc -dump-ast -pretty-json %s | %FileCheck %s --match-full-lines

// CHECK-LABEL: {
// CHECK-NEXT: "type": "Program",
// CHECK-NEXT: "body": [
// CHECK-NEXT: {

async function* foo() {
// CHECK-NEXT: "type": "FunctionDeclaration",
// CHECK-NEXT: "id": {
// CHECK-NEXT: "type": "Identifier",
// CHECK-NEXT: "name": "foo"
// CHECK-NEXT: },
// CHECK-NEXT: "params": [],
// CHECK-NEXT: "body": {
// CHECK-NEXT: "type": "BlockStatement",
// CHECK-NEXT: "body": [

class C {
// CHECK-NEXT: {
// CHECK-NEXT: "type": "ClassDeclaration",
// CHECK-NEXT: "id": {
// CHECK-NEXT: "type": "Identifier",
// CHECK-NEXT: "name": "C"
// CHECK-NEXT: },
// CHECK-NEXT: "superClass": null,
// CHECK-NEXT: "body": {
// CHECK-NEXT: "type": "ClassBody",
// CHECK-NEXT: "body": [

[yield 1] = 1;
// CHECK-NEXT: {
// CHECK-NEXT: "type": "ClassProperty",
// CHECK-NEXT: "key": {
// CHECK-NEXT: "type": "YieldExpression",
// CHECK-NEXT: "argument": {
// CHECK-NEXT: "type": "NumericLiteral",
// CHECK-NEXT: "value": 1,
// CHECK-NEXT: "raw": "1"
// CHECK-NEXT: },
// CHECK-NEXT: "delegate": false
// CHECK-NEXT: },
// CHECK-NEXT: "value": {
// CHECK-NEXT: "type": "NumericLiteral",
// CHECK-NEXT: "value": 1,
// CHECK-NEXT: "raw": "1"
// CHECK-NEXT: },
// CHECK-NEXT: "computed": true,
// CHECK-NEXT: "static": false,
// CHECK-NEXT: "declare": false
// CHECK-NEXT: },

[await 1] = 1;
// CHECK-NEXT: {
// CHECK-NEXT: "type": "ClassProperty",
// CHECK-NEXT: "key": {
// CHECK-NEXT: "type": "AwaitExpression",
// CHECK-NEXT: "argument": {
// CHECK-NEXT: "type": "NumericLiteral",
// CHECK-NEXT: "value": 1,
// CHECK-NEXT: "raw": "1"
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "value": {
// CHECK-NEXT: "type": "NumericLiteral",
// CHECK-NEXT: "value": 1,
// CHECK-NEXT: "raw": "1"
// CHECK-NEXT: },
// CHECK-NEXT: "computed": true,
// CHECK-NEXT: "static": false,
// CHECK-NEXT: "declare": false
// CHECK-NEXT: }

}
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]

}
// CHECK-NEXT: },
// CHECK-NEXT: "generator": true,
// CHECK-NEXT: "async": true
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
20 changes: 20 additions & 0 deletions test/Parser/yield-field-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-ast %s 2>&1 ) | %FileCheckOrRegen --match-full-lines %s

function* foo() {
class C {
x = yield;
}
}

// Auto-generated content below. Please do not modify manually.

// CHECK:{{.*}}yield-field-error.js:12:9: error: invalid expression
// CHECK-NEXT: x = yield;
// CHECK-NEXT: ^

0 comments on commit a642414

Please sign in to comment.