Skip to content

Commit

Permalink
Early error on redeclaration of restricted global properties
Browse files Browse the repository at this point in the history
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
  • Loading branch information
avp authored and facebook-github-bot committed Nov 26, 2024
1 parent ab480d1 commit 2897b34
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 1 deletion.
30 changes: 29 additions & 1 deletion lib/Sema/SemanticResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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))
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/SemanticResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<UniqueString *, 4> restrictedGlobalProperties_{};

/// If not null, store all instances of DeclCollector here, for use by other
/// passes.
DeclCollectorMapTy *const saveDecls_;
Expand Down
25 changes: 25 additions & 0 deletions test/Sema/restricted-global-error.js
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 37 additions & 0 deletions test/Sema/restricted-global-nested.js
Original file line number Diff line number Diff line change
@@ -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']
33 changes: 33 additions & 0 deletions test/Sema/restricted-global-var.js
Original file line number Diff line number Diff line change
@@ -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']
21 changes: 21 additions & 0 deletions test/hermes/regress-restricted-global.js
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 2897b34

Please sign in to comment.