From 2897b3438265400591a3fbca32fe0cc714b4a609 Mon Sep 17 00:00:00 2001 From: Aakash Patel Date: Mon, 25 Nov 2024 17:25:12 -0800 Subject: [PATCH] Early error on redeclaration of restricted global properties Summary: JS has a concept of "restricted global properties" which must never be shadowed in the global scope because they are non-configurable. There are only 3 of these, so add a special check to handle restricted global properties. Reviewed By: tmikov Differential Revision: D66466325 fbshipit-source-id: faf39e42e3ba3f4660a98da3eb34fce0e8774a34 --- lib/Sema/SemanticResolver.cpp | 30 ++++++++++++++++++- lib/Sema/SemanticResolver.h | 7 +++++ test/Sema/restricted-global-error.js | 25 ++++++++++++++++ test/Sema/restricted-global-nested.js | 37 ++++++++++++++++++++++++ test/Sema/restricted-global-var.js | 33 +++++++++++++++++++++ test/hermes/regress-restricted-global.js | 21 ++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 test/Sema/restricted-global-error.js create mode 100644 test/Sema/restricted-global-nested.js create mode 100644 test/Sema/restricted-global-var.js create mode 100644 test/hermes/regress-restricted-global.js diff --git a/lib/Sema/SemanticResolver.cpp b/lib/Sema/SemanticResolver.cpp index 632c088e4f2..92831332197 100644 --- a/lib/Sema/SemanticResolver.cpp +++ b/lib/Sema/SemanticResolver.cpp @@ -34,7 +34,14 @@ SemanticResolver::SemanticResolver( ambientDecls_(ambientDecls), saveDecls_(saveDecls), bindingTable_(semCtx.getBindingTable()), - compile_(compile) {} + compile_(compile) { + // ES14.0 19.1 Value properties of the global object + // https://262.ecma-international.org/14.0/#sec-value-properties-of-the-global-object + // These are the only non-configurable properties. + restrictedGlobalProperties_.insert(kw_.identNaN); + restrictedGlobalProperties_.insert(kw_.identUndefined); + restrictedGlobalProperties_.insert(kw_.identInfinity); +} bool SemanticResolver::run(ESTree::ProgramNode *rootNode) { if (sm_.getErrorCount()) @@ -1885,6 +1892,27 @@ void SemanticResolver::validateAndDeclareIdentifier( } } + // Special case: this is a lexically-scoped declaration in global scope + // which is a restricted global. + // ES14.0 16.1.7 GlobalDeclarationInstantiation + // For each element name of lexNames, do + // a. If env.HasVarDeclaration(name) is true, + // throw a SyntaxError exception. + // b. If env.HasLexicalDeclaration(name) is true, + // throw a SyntaxError exception. + // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). + // d. If hasRestrictedGlobal is true, + // throw a SyntaxError exception. + // (a-b) are handled by the checks above, so just do (c-d) here. + if (curScope_ == semCtx_.getGlobalScope() && Decl::isKindLetLike(kind) && + restrictedGlobalProperties_.count(ident->_name)) { + sm_.error( + ident->getSourceRange(), + llvh::Twine( + "Can't create duplicate variable that shadows a global property: '") + + ident->_name->str() + "'"); + } + // Create new decl. if (!decl) { if (Decl::isKindGlobal(kind)) diff --git a/lib/Sema/SemanticResolver.h b/lib/Sema/SemanticResolver.h index fe69e037364..c2552079cc2 100644 --- a/lib/Sema/SemanticResolver.h +++ b/lib/Sema/SemanticResolver.h @@ -47,6 +47,13 @@ class SemanticResolver /// be inserted in the global scope. const DeclarationFileListTy &ambientDecls_; + /// A set of names that are restricted in the global scope. + /// https://262.ecma-international.org/14.0/#sec-hasrestrictedglobalproperty + /// ES14.0 9.1.1.4.14 HasRestrictedGlobalProperty: + /// Any global properties that are defined to be non-configurable + /// are restricted. + llvh::SmallDenseSet restrictedGlobalProperties_{}; + /// If not null, store all instances of DeclCollector here, for use by other /// passes. DeclCollectorMapTy *const saveDecls_; diff --git a/test/Sema/restricted-global-error.js b/test/Sema/restricted-global-error.js new file mode 100644 index 00000000000..78c0618937f --- /dev/null +++ b/test/Sema/restricted-global-error.js @@ -0,0 +1,25 @@ +/** + * 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: (! %shermes -dump-sema %s 2>&1) | %FileCheckOrRegen %s --match-full-lines + +let undefined; +let NaN; +let Infinity; + +// Auto-generated content below. Please do not modify manually. + +// CHECK:{{.*}}restricted-global-error.js:10:5: error: Can't create duplicate variable that shadows a global property: 'undefined' +// CHECK-NEXT:let undefined; +// CHECK-NEXT: ^~~~~~~~~ +// CHECK-NEXT:{{.*}}restricted-global-error.js:11:5: error: Can't create duplicate variable that shadows a global property: 'NaN' +// CHECK-NEXT:let NaN; +// CHECK-NEXT: ^~~ +// CHECK-NEXT:{{.*}}restricted-global-error.js:12:5: error: Can't create duplicate variable that shadows a global property: 'Infinity' +// CHECK-NEXT:let Infinity; +// CHECK-NEXT: ^~~~~~~~ +// CHECK-NEXT:Emitted 3 errors. exiting. diff --git a/test/Sema/restricted-global-nested.js b/test/Sema/restricted-global-nested.js new file mode 100644 index 00000000000..2fa64d15669 --- /dev/null +++ b/test/Sema/restricted-global-nested.js @@ -0,0 +1,37 @@ +/** + * 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: %shermes -dump-sema -fno-std-globals %s | %FileCheckOrRegen %s --match-full-lines + +// These can be declared in block scope +{ +let undefined; +let NaN; +let Infinity; +} + +// Auto-generated content below. Please do not modify manually. + +// CHECK:SemContext +// CHECK-NEXT:Func loose +// CHECK-NEXT: Scope %s.1 +// CHECK-NEXT: Scope %s.2 +// CHECK-NEXT: Decl %d.1 'undefined' Let +// CHECK-NEXT: Decl %d.2 'NaN' Let +// CHECK-NEXT: Decl %d.3 'Infinity' Let + +// CHECK:Program Scope %s.1 +// CHECK-NEXT: BlockStatement Scope %s.2 +// CHECK-NEXT: VariableDeclaration +// CHECK-NEXT: VariableDeclarator +// CHECK-NEXT: Id 'undefined' [D:E:%d.1 'undefined'] +// CHECK-NEXT: VariableDeclaration +// CHECK-NEXT: VariableDeclarator +// CHECK-NEXT: Id 'NaN' [D:E:%d.2 'NaN'] +// CHECK-NEXT: VariableDeclaration +// CHECK-NEXT: VariableDeclarator +// CHECK-NEXT: Id 'Infinity' [D:E:%d.3 'Infinity'] diff --git a/test/Sema/restricted-global-var.js b/test/Sema/restricted-global-var.js new file mode 100644 index 00000000000..2b7f3c532c5 --- /dev/null +++ b/test/Sema/restricted-global-var.js @@ -0,0 +1,33 @@ +/** + * 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: %shermes -dump-sema -fno-std-globals %s | %FileCheckOrRegen %s --match-full-lines + +// These can be redeclared as 'var'. +var undefined; +var NaN; +var Infinity; + +// Auto-generated content below. Please do not modify manually. + +// CHECK:SemContext +// CHECK-NEXT:Func loose +// CHECK-NEXT: Scope %s.1 +// CHECK-NEXT: Decl %d.1 'undefined' GlobalProperty +// CHECK-NEXT: Decl %d.2 'NaN' GlobalProperty +// CHECK-NEXT: Decl %d.3 'Infinity' GlobalProperty + +// CHECK:Program Scope %s.1 +// CHECK-NEXT: VariableDeclaration +// CHECK-NEXT: VariableDeclarator +// CHECK-NEXT: Id 'undefined' [D:E:%d.1 'undefined'] +// CHECK-NEXT: VariableDeclaration +// CHECK-NEXT: VariableDeclarator +// CHECK-NEXT: Id 'NaN' [D:E:%d.2 'NaN'] +// CHECK-NEXT: VariableDeclaration +// CHECK-NEXT: VariableDeclarator +// CHECK-NEXT: Id 'Infinity' [D:E:%d.3 'Infinity'] diff --git a/test/hermes/regress-restricted-global.js b/test/hermes/regress-restricted-global.js new file mode 100644 index 00000000000..5af4706a356 --- /dev/null +++ b/test/hermes/regress-restricted-global.js @@ -0,0 +1,21 @@ +/** + * 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: %hermes -target=HBC %s | %FileCheck --match-full-lines %s +// RUN: %shermes --exec %s | %FileCheck --match-full-lines %s + +{ + const NaN = 1; + const Infinity = 1; + const undefined = 1; + print([ + NaN === 1 ? 'PASS NaN' : 'FAIL NaN', + Infinity === 1 ? 'PASS Infinity' : 'FAIL Infinity', + undefined === 1 ? 'PASS undefined' : 'FAIL undefined', + ].join(', ')); +} +// CHECK: PASS NaN, PASS Infinity, PASS undefined