From a6424143fa006cc98a7fdc21e051b8982fdc81d9 Mon Sep 17 00:00:00 2001 From: Aakash Patel Date: Mon, 2 Dec 2024 18:02:40 -0800 Subject: [PATCH] Forbid await/yield/arguments in class field inits Summary: Certain language features aren't allowed in class field initializers. 'arguments' is forbidden by the spec. Yield/await should be forbidden: https://github.com/tc39/ecma262/issues/3333 Reviewed By: fbmal7 Differential Revision: D66266912 fbshipit-source-id: 859ab611add5b7e4cdd01a3167113255de4bff61 --- lib/AST/SemanticValidator.cpp | 40 +++++++++-- lib/AST/SemanticValidator.h | 7 ++ lib/Parser/JSParserImpl.cpp | 6 ++ test/Parser/arguments-field-error.js | 20 ++++++ test/Parser/await-field-error.js | 26 +++++++ test/Parser/class-props-yield-await.js | 93 ++++++++++++++++++++++++++ test/Parser/yield-field-error.js | 20 ++++++ 7 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 test/Parser/arguments-field-error.js create mode 100644 test/Parser/await-field-error.js create mode 100644 test/Parser/class-props-yield-await.js create mode 100644 test/Parser/yield-field-error.js diff --git a/lib/AST/SemanticValidator.cpp b/lib/AST/SemanticValidator.cpp index 99d8325ff93..1a11a74a086 100644 --- a/lib/AST/SemanticValidator.cpp +++ b/lib/AST/SemanticValidator.cpp @@ -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. @@ -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); @@ -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 oldForbidAwait{forbidAwaitExpression_, true}; + // ES14.0 15.7.1 + // It is a Syntax Error if Initializer is present and ContainsArguments of + // Initializer is true. + SaveAndRestore oldForbidArguments{forbidArguments_, true}; + visitESTreeNode(*this, node->_value); + } +} + +void SemanticValidator::visit(ClassPropertyNode *node) { + visitESTreeNode(*this, node->_key); + { + SaveAndRestore oldForbidAwait{forbidAwaitExpression_, true}; + // ES14.0 15.7.1 + // It is a Syntax Error if Initializer is present and ContainsArguments of + // Initializer is true. + SaveAndRestore oldForbidArguments{forbidArguments_, true}; + visitESTreeNode(*this, node->_value); + } } void SemanticValidator::visit(ImportDeclarationNode *importDecl) { @@ -884,6 +906,16 @@ void SemanticValidator::visitFunction( } } + // 'await' forbidden outside async functions. + llvh::SaveAndRestore oldForbidAwait{ + forbidAwaitExpression_, !ESTree::isAsync(node)}; + // Forbidden-ness of 'arguments' passes through arrow functions because they + // use the same 'arguments'. + llvh::SaveAndRestore oldForbidArguments{ + forbidArguments_, + llvh::isa(node) ? forbidArguments_ + : false}; + visitParamsAndBody(node); } diff --git a/lib/AST/SemanticValidator.h b/lib/AST/SemanticValidator.h index 21078819bbb..4b9e67bbd89 100644 --- a/lib/AST/SemanticValidator.h +++ b/lib/AST/SemanticValidator.h @@ -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}; @@ -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); diff --git a/lib/Parser/JSParserImpl.cpp b/lib/Parser/JSParserImpl.cpp index 366ad33aa5b..e397efb19cc 100644 --- a/lib/Parser/JSParserImpl.cpp +++ b/lib/Parser/JSParserImpl.cpp @@ -4964,6 +4964,12 @@ Optional 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 saveParamYield{paramYield_, false}; + llvh::SaveAndRestore saveParamAwait{paramAwait_, true}; auto optValue = parseAssignmentExpression(); if (!optValue) return None; diff --git a/test/Parser/arguments-field-error.js b/test/Parser/arguments-field-error.js new file mode 100644 index 00000000000..ab56541d63f --- /dev/null +++ b/test/Parser/arguments-field-error.js @@ -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: ^~~~~~~~~ diff --git a/test/Parser/await-field-error.js b/test/Parser/await-field-error.js new file mode 100644 index 00000000000..af4f81caf73 --- /dev/null +++ b/test/Parser/await-field-error.js @@ -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: ^~~~~~~ diff --git a/test/Parser/class-props-yield-await.js b/test/Parser/class-props-yield-await.js new file mode 100644 index 00000000000..46fc70972b1 --- /dev/null +++ b/test/Parser/class-props-yield-await.js @@ -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: } diff --git a/test/Parser/yield-field-error.js b/test/Parser/yield-field-error.js new file mode 100644 index 00000000000..a57e31d1eda --- /dev/null +++ b/test/Parser/yield-field-error.js @@ -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: ^