Ver Fonte

LibJS: Implement CreatePerIterationEnvironment for 'for' statements

Implement for CreatePerIterationEnvironment for 'for' loops per the Ecma
Standard. This ensures each iteration of a 'for' loop has its own
lexical environment so that variables declared in the loop are scoped to
the current iteration.
Braydn há 11 meses atrás
pai
commit
dbc2f7ed48

+ 41 - 2
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -981,6 +981,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
     Bytecode::BasicBlock* update_block_ptr { nullptr };
 
     bool has_lexical_environment = false;
+    Vector<IdentifierTableIndex> per_iteration_bindings;
 
     if (m_init) {
         if (m_init->is_variable_declaration()) {
@@ -994,8 +995,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
 
             if (variable_declaration.is_lexical_declaration() && has_non_local_variables) {
                 has_lexical_environment = true;
-
-                // FIXME: Is Block correct?
+                // Setup variable scope for bound identifiers
                 generator.begin_variable_scope();
 
                 bool is_const = variable_declaration.is_constant_declaration();
@@ -1005,6 +1005,9 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
                         return;
                     auto index = generator.intern_identifier(identifier.string());
                     generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_const);
+                    if (!is_const) {
+                        per_iteration_bindings.append(index);
+                    }
                 }));
             }
         }
@@ -1012,6 +1015,36 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
         (void)TRY(m_init->generate_bytecode(generator));
     }
 
+    // CreatePerIterationEnvironment (https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-createperiterationenvironment)
+    auto generate_per_iteration_bindings = [&per_iteration_bindings = static_cast<Vector<IdentifierTableIndex> const&>(per_iteration_bindings),
+                                               &generator]() {
+        if (per_iteration_bindings.is_empty()) {
+            return;
+        }
+
+        // Copy all the last values into registers for use in step 1.e.iii
+        // Register copies of bindings are required since the changing of the
+        // running execution context in the final step requires leaving the
+        // current variable scope before creating "thisIterationEnv"
+        Vector<ScopedOperand> registers;
+        for (auto const& binding : per_iteration_bindings) {
+            auto reg = generator.allocate_register();
+            generator.emit<Bytecode::Op::GetBinding>(reg, binding);
+            registers.append(reg);
+        }
+
+        generator.end_variable_scope();
+        generator.begin_variable_scope();
+
+        for (size_t i = 0; i < per_iteration_bindings.size(); ++i) {
+            generator.emit<Bytecode::Op::CreateVariable>(per_iteration_bindings[i], Bytecode::Op::EnvironmentMode::Lexical, false);
+            generator.emit<Bytecode::Op::InitializeLexicalBinding>(per_iteration_bindings[i], registers[i]);
+        }
+    };
+
+    // CreatePerIterationEnvironment where lastIterationEnv is the variable
+    // scope created above for bound identifiers
+    generate_per_iteration_bindings();
     body_block_ptr = &generator.make_block();
 
     if (m_update)
@@ -1049,6 +1082,9 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
     generator.end_breakable_scope();
     generator.end_continuable_scope();
 
+    // CreatePerIterationEnvironment where lastIterationEnv is the environment
+    // created by the previous CreatePerIterationEnvironment setup
+    generate_per_iteration_bindings();
     if (!generator.is_current_block_terminated()) {
         if (m_update) {
             generator.emit<Bytecode::Op::Jump>(Bytecode::Label { *update_block_ptr });
@@ -1059,6 +1095,9 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
 
     generator.switch_to_basic_block(end_block);
 
+    // Leave the environment setup by CreatePerIterationEnvironment or if there
+    // are no perIterationBindings the variable scope created for bound
+    // identifiers
     if (has_lexical_environment)
         generator.end_variable_scope();
 

+ 16 - 0
Userland/Libraries/LibJS/Tests/loops/for-scopes.js

@@ -16,3 +16,19 @@ test("const in for head", () => {
         c;
     }).toThrowWithMessage(ReferenceError, "'c' is not defined");
 });
+
+test("let variables captured by value", () => {
+    let result = "";
+    let functionList = [];
+    for (let i = 0; i < 9; i++) {
+        result += i;
+        functionList.push(function () {
+            return i;
+        });
+    }
+    for (let i = 0; i < functionList.length; i++) {
+        result += functionList[i]();
+    }
+
+    expect(result).toEqual("012345678012345678");
+});