Parcourir la source

LibJS/Bytecode: Check for lexical bindings only in current scope

BlockDeclarationInstantiation takes as input the new lexical
environment that was created and checks if there is a binding for the
current name only in this new scope.

This allows shadowing lexical variables and prevents us crashing due to
an already initialized lexical variable in this case:
```js
let x = 1;
{
    let x = 1;
}
```
Luke Wilde il y a 3 ans
Parent
commit
3a48c7fdaf

+ 3 - 1
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -45,7 +45,9 @@ Bytecode::CodeGenerationErrorOr<void> ScopeNode::generate_bytecode(Bytecode::Gen
             auto is_constant_declaration = declaration.is_constant_declaration();
             auto is_constant_declaration = declaration.is_constant_declaration();
             declaration.for_each_bound_name([&](auto const& name) {
             declaration.for_each_bound_name([&](auto const& name) {
                 auto index = generator.intern_identifier(name);
                 auto index = generator.intern_identifier(name);
-                if (is_constant_declaration || !generator.has_binding(index)) {
+                // NOTE: BlockDeclarationInstantiation takes as input the new lexical environment that was created and checks if there is a binding for the current name only in this new scope.
+                //       For example: `{ let a = 1; { let a = 2; } }`. The second `a` will shadow the first `a` instead of re-initializing or setting it.
+                if (is_constant_declaration || !generator.has_binding_in_current_scope(index)) {
                     generator.register_binding(index);
                     generator.register_binding(index);
                     generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_constant_declaration);
                     generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_constant_declaration);
                 }
                 }

+ 8 - 1
Userland/Libraries/LibJS/Bytecode/Generator.h

@@ -138,7 +138,7 @@ public:
     {
     {
         m_variable_scopes.last_matching([&](auto& x) { return x.mode == BindingMode::Global || x.mode == mode; })->known_bindings.set(identifier);
         m_variable_scopes.last_matching([&](auto& x) { return x.mode == BindingMode::Global || x.mode == mode; })->known_bindings.set(identifier);
     }
     }
-    bool has_binding(IdentifierTableIndex identifier, Optional<BindingMode> const& specific_binding_mode = {})
+    bool has_binding(IdentifierTableIndex identifier, Optional<BindingMode> const& specific_binding_mode = {}) const
     {
     {
         for (auto index = m_variable_scopes.size(); index > 0; --index) {
         for (auto index = m_variable_scopes.size(); index > 0; --index) {
             auto& scope = m_variable_scopes[index - 1];
             auto& scope = m_variable_scopes[index - 1];
@@ -151,6 +151,13 @@ public:
         }
         }
         return false;
         return false;
     }
     }
+    bool has_binding_in_current_scope(IdentifierTableIndex identifier) const
+    {
+        if (m_variable_scopes.is_empty())
+            return false;
+
+        return m_variable_scopes.last().known_bindings.contains(identifier);
+    }
 
 
     void begin_variable_scope(BindingMode mode = BindingMode::Lexical, SurroundingScopeKind kind = SurroundingScopeKind::Block);
     void begin_variable_scope(BindingMode mode = BindingMode::Lexical, SurroundingScopeKind kind = SurroundingScopeKind::Block);
     void end_variable_scope();
     void end_variable_scope();