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.
This commit is contained in:
parent
c8e6a95988
commit
dbc2f7ed48
Notes:
sideshowbarker
2024-07-17 22:55:25 +09:00
Author: https://github.com/braydnm Commit: https://github.com/LadybirdBrowser/ladybird/commit/dbc2f7ed48f Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/675
2 changed files with 57 additions and 2 deletions
Userland/Libraries/LibJS
|
@ -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,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");
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue