From 70b0d49483e806e0e60d02ae838a60b9d5033939 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Sun, 7 May 2023 23:29:20 +0200 Subject: [PATCH] Implement annexB Block-Level Function Declarations (#2910) * Implement annexB Block-Level Function Declarations * Apply suggestions --- boa_ast/src/operations.rs | 212 +++++++++++++++- boa_engine/src/builtins/eval/mod.rs | 2 +- boa_engine/src/builtins/json/mod.rs | 2 +- boa_engine/src/bytecompiler/class.rs | 4 +- boa_engine/src/bytecompiler/declarations.rs | 234 ++++++++++++++++-- boa_engine/src/bytecompiler/env.rs | 17 ++ boa_engine/src/bytecompiler/function.rs | 2 +- boa_engine/src/bytecompiler/mod.rs | 46 ++-- boa_engine/src/bytecompiler/module.rs | 2 +- .../src/bytecompiler/statement/block.rs | 2 +- .../src/bytecompiler/statement/switch.rs | 2 +- boa_engine/src/context/mod.rs | 2 +- boa_engine/src/environments/compile.rs | 46 ++++ boa_engine/src/environments/runtime.rs | 4 +- boa_engine/src/vm/code_block.rs | 17 +- boa_engine/src/vm/flowgraph/mod.rs | 7 +- boa_engine/src/vm/opcode/define/mod.rs | 49 +--- boa_engine/src/vm/opcode/mod.rs | 26 +- 18 files changed, 554 insertions(+), 122 deletions(-) diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index 3e7e7a3880b..60bc3e0a265 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -9,7 +9,7 @@ use boa_interner::{Interner, Sym}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration, Variable}, + declaration::{Binding, ExportDeclaration, ImportDeclaration, VarDeclaration, Variable}, expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield}, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, @@ -1663,3 +1663,213 @@ impl<'ast> Visitor<'ast> for TopLevelVarScopedDeclarationsVisitor<'_> { } } } + +/// Returns a list function declaration names that are directly contained in a statement lists +/// `Block`, `CaseClause` or `DefaultClause`. +/// If the function declaration would cause an early error it is not included in the list. +/// +/// This behavior is used in the following annexB sections: +/// * [B.3.2.1 Changes to FunctionDeclarationInstantiation][spec0] +/// * [B.3.2.2 Changes to GlobalDeclarationInstantiation][spec1] +/// * [B.3.2.3 Changes to EvalDeclarationInstantiation][spec2] +/// +/// [spec0]: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation +/// [spec1]: https://tc39.es/ecma262/#sec-web-compat-globaldeclarationinstantiation +/// [spec2]: https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation +#[must_use] +pub fn annex_b_function_declarations_names<'a, N>(node: &'a N) -> Vec +where + &'a N: Into>, +{ + let mut declarations = Vec::new(); + AnnexBFunctionDeclarationNamesVisitor(&mut declarations).visit(node.into()); + declarations +} + +/// The [`Visitor`] used for [`annex_b_function_declarations_names`]. +#[derive(Debug)] +struct AnnexBFunctionDeclarationNamesVisitor<'a>(&'a mut Vec); + +impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> { + type BreakTy = Infallible; + + fn visit_statement_list_item( + &mut self, + node: &'ast StatementListItem, + ) -> ControlFlow { + match node { + StatementListItem::Statement(node) => self.visit(node), + StatementListItem::Declaration(_) => ControlFlow::Continue(()), + } + } + + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { + match node { + Statement::Block(node) => self.visit(node), + Statement::If(node) => self.visit(node), + Statement::DoWhileLoop(node) => self.visit(node), + Statement::WhileLoop(node) => self.visit(node), + Statement::ForLoop(node) => self.visit(node), + Statement::ForInLoop(node) => self.visit(node), + Statement::ForOfLoop(node) => self.visit(node), + Statement::Switch(node) => self.visit(node), + Statement::Labelled(node) => self.visit(node), + Statement::Try(node) => self.visit(node), + Statement::With(node) => self.visit(node), + _ => ControlFlow::Continue(()), + } + } + + fn visit_block(&mut self, node: &'ast crate::statement::Block) -> ControlFlow { + self.visit(node.statement_list()); + for statement in node.statement_list().statements() { + if let StatementListItem::Declaration(Declaration::Function(function)) = statement { + let name = function + .name() + .expect("function declaration must have name"); + self.0.push(name); + } + } + + let lexically_declared_names = lexically_declared_names_legacy(node.statement_list()); + + self.0 + .retain(|name| !lexically_declared_names.contains(&(*name, false))); + + ControlFlow::Continue(()) + } + + fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow { + for case in node.cases() { + self.visit(case); + for statement in case.body().statements() { + if let StatementListItem::Declaration(Declaration::Function(function)) = statement { + let name = function + .name() + .expect("function declaration must have name"); + self.0.push(name); + } + } + } + if let Some(default) = node.default() { + self.visit(default); + for statement in default.statements() { + if let StatementListItem::Declaration(Declaration::Function(function)) = statement { + let name = function + .name() + .expect("function declaration must have name"); + self.0.push(name); + } + } + } + + let lexically_declared_names = lexically_declared_names_legacy(node); + + self.0 + .retain(|name| !lexically_declared_names.contains(&(*name, false))); + + ControlFlow::Continue(()) + } + + fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow { + self.visit(node.block()); + if let Some(catch) = node.catch() { + self.visit(catch.block()); + + if let Some(Binding::Pattern(pattern)) = catch.parameter() { + let bound_names = bound_names(pattern); + + self.0.retain(|name| !bound_names.contains(name)); + } + } + if let Some(finally) = node.finally() { + self.visit(finally.block()); + } + ControlFlow::Continue(()) + } + + fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { + if let Some(node) = node.else_node() { + self.visit(node); + } + self.visit(node.body()) + } + + fn visit_do_while_loop( + &mut self, + node: &'ast crate::statement::DoWhileLoop, + ) -> ControlFlow { + self.visit(node.body()) + } + + fn visit_while_loop( + &mut self, + node: &'ast crate::statement::WhileLoop, + ) -> ControlFlow { + self.visit(node.body()) + } + + fn visit_for_loop( + &mut self, + node: &'ast crate::statement::ForLoop, + ) -> ControlFlow { + self.visit(node.body()); + + if let Some(ForLoopInitializer::Lexical(node)) = node.init() { + let bound_names = bound_names(node); + self.0.retain(|name| !bound_names.contains(name)); + } + + ControlFlow::Continue(()) + } + + fn visit_for_in_loop( + &mut self, + node: &'ast crate::statement::ForInLoop, + ) -> ControlFlow { + self.visit(node.body()); + + if let IterableLoopInitializer::Let(node) = node.initializer() { + let bound_names = bound_names(node); + self.0.retain(|name| !bound_names.contains(name)); + } + if let IterableLoopInitializer::Const(node) = node.initializer() { + let bound_names = bound_names(node); + self.0.retain(|name| !bound_names.contains(name)); + } + + ControlFlow::Continue(()) + } + + fn visit_for_of_loop( + &mut self, + node: &'ast crate::statement::ForOfLoop, + ) -> ControlFlow { + self.visit(node.body()); + + if let IterableLoopInitializer::Let(node) = node.initializer() { + let bound_names = bound_names(node); + self.0.retain(|name| !bound_names.contains(name)); + } + if let IterableLoopInitializer::Const(node) = node.initializer() { + let bound_names = bound_names(node); + self.0.retain(|name| !bound_names.contains(name)); + } + + ControlFlow::Continue(()) + } + + fn visit_labelled( + &mut self, + node: &'ast crate::statement::Labelled, + ) -> ControlFlow { + if let LabelledItem::Statement(node) = node.item() { + self.visit(node); + } + ControlFlow::Continue(()) + } + + fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow { + self.visit(node.statement()) + } +} diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 3bb21423b14..89b838b7c3c 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -234,7 +234,7 @@ impl Eval { let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); compiler.eval_declaration_instantiation(&body, strict)?; - compiler.compile_statement_list(&body, true); + compiler.compile_statement_list(&body, true, false); let env_info = compiler.pop_compile_environment(); compiler.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 7ecb727bedf..d079d5e7eda 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -123,7 +123,7 @@ impl Json { context.realm().environment().compile_env(), context, ); - compiler.compile_statement_list(&statement_list, true); + compiler.compile_statement_list(&statement_list, true, false); Gc::new(compiler.finish()) }; let unfiltered = context.execute(code_block)?; diff --git a/boa_engine/src/bytecompiler/class.rs b/boa_engine/src/bytecompiler/class.rs index 2374e1640d9..ecee3e966b3 100644 --- a/boa_engine/src/bytecompiler/class.rs +++ b/boa_engine/src/bytecompiler/class.rs @@ -47,7 +47,7 @@ impl ByteCompiler<'_, '_> { false, ); - compiler.compile_statement_list(expr.body(), false); + compiler.compile_statement_list(expr.body(), false, false); let env_info = compiler.pop_compile_environment(); @@ -374,7 +374,7 @@ impl ByteCompiler<'_, '_> { false, ); - compiler.compile_statement_list(statement_list, false); + compiler.compile_statement_list(statement_list, false, false); let env_info = compiler.pop_compile_environment(); compiler.pop_compile_environment(); compiler.num_bindings = env_info.num_bindings; diff --git a/boa_engine/src/bytecompiler/declarations.rs b/boa_engine/src/bytecompiler/declarations.rs index e2570cfd520..7fdb5a45669 100644 --- a/boa_engine/src/bytecompiler/declarations.rs +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -15,6 +15,9 @@ use boa_ast::{ }; use boa_interner::Sym; +#[cfg(feature = "annex-b")] +use boa_ast::operations::annex_b_function_declarations_names; + impl ByteCompiler<'_, '_> { /// `GlobalDeclarationInstantiation ( script, env )` /// @@ -151,8 +154,55 @@ impl ByteCompiler<'_, '_> { // 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. // However, if the global object is a Proxy exotic object it may exhibit behaviours // that cause abnormal terminations in some of the following steps. + // 12. NOTE: Annex B.3.2.2 adds additional steps at this point. - // TODO: Support B.3.2.2. + // 12. Perform the following steps: + // a. Let strict be IsStrict of script. + // b. If strict is false, then + #[cfg(feature = "annex-b")] + if !script.strict() { + let lex_names = top_level_lexically_declared_names(script); + + // i. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. + // ii. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, + // or DefaultClause Contained within script, do + for f in annex_b_function_declarations_names(script) { + // 1. Let F be StringValue of the BindingIdentifier of f. + // 2. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier + // would not produce any Early Errors for script, then + if !lex_names.contains(&f) { + // a. If env.HasLexicalDeclaration(F) is false, then + if !self.current_environment.borrow().has_lex_binding(f) { + // i. Let fnDefinable be ? env.CanDeclareGlobalVar(F). + let fn_definable = self.context.can_declare_global_function(f)?; + + // ii. If fnDefinable is true, then + if fn_definable { + // i. NOTE: A var binding for F is only instantiated here if it is neither + // a VarDeclaredName nor the name of another FunctionDeclaration. + // ii. If declaredFunctionOrVarNames does not contain F, then + if !declared_function_names.contains(&f) + && !declared_var_names.contains(&f) + { + // i. Perform ? env.CreateGlobalVarBinding(F, false). + self.context.create_global_var_binding(f, false)?; + + // ii. Append F to declaredFunctionOrVarNames. + declared_function_names.push(f); + } + // iii. When the FunctionDeclaration f is evaluated, perform the following + // steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: + // i. Let genv be the running execution context's VariableEnvironment. + // ii. Let benv be the running execution context's LexicalEnvironment. + // iii. Let fobj be ! benv.GetBindingValue(F, false). + // iv. Perform ? genv.SetMutableBinding(F, fobj, false). + // v. Return unused. + self.annex_b_function_names.push(f); + } + } + } + } + } // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. // 14. Let privateEnv be null. @@ -253,6 +303,8 @@ impl ByteCompiler<'_, '_> { where &'a N: Into>, { + let mut env = self.current_environment.borrow_mut(); + // 1. Let declarations be the LexicallyScopedDeclarations of code. let declarations = lexically_scoped_declarations(block); @@ -266,20 +318,29 @@ impl ByteCompiler<'_, '_> { // a. For each element dn of the BoundNames of d, do for dn in bound_names::<'_, VariableList>(d) { // 1. Perform ! env.CreateImmutableBinding(dn, true). - self.create_immutable_binding(dn, true); + env.create_immutable_binding(dn, true); } } // ii. Else, else { // a. For each element dn of the BoundNames of d, do for dn in bound_names::<'_, Declaration>(d) { + #[cfg(not(feature = "annex-b"))] // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. - // TODO: Support B.3.2.6. - self.create_mutable_binding(dn, false); + env.create_mutable_binding(dn, false); + + #[cfg(feature = "annex-b")] + // 1. If ! env.HasBinding(dn) is false, then + if !env.has_binding(dn) { + // a. Perform ! env.CreateMutableBinding(dn, false). + env.create_mutable_binding(dn, false); + } } } } + drop(env); + // Note: Not sure if the spec is wrong here or if our implementation just differs too much, // but we need 3.a to be finished for all declarations before 3.b can be done. @@ -343,7 +404,8 @@ impl ByteCompiler<'_, '_> { // c. Assert: The following loop will terminate. // d. Repeat, while thisEnv is not varEnv, // i. If thisEnv is not an Object Environment Record, then - // 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts. + // 1. NOTE: The environment of with statements cannot contain any lexical + // declaration so it doesn't need to be checked for var/let hoisting conflicts. // 2. For each element name of varNames, do // a. If ! thisEnv.HasBinding(name) is true, then // i. Throw a SyntaxError exception. @@ -420,7 +482,93 @@ impl ByteCompiler<'_, '_> { functions_to_initialize.reverse(); // 11. NOTE: Annex B.3.2.3 adds additional steps at this point. - // TODO: Support B.3.2.3 + // 11. If strict is false, then + #[cfg(feature = "annex-b")] + if !strict { + let lexically_declared_names = top_level_lexically_declared_names(body); + + // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. + // b. For each FunctionDeclaration f that is directly contained in the StatementList + // of a Block, CaseClause, or DefaultClause Contained within body, do + for f in annex_b_function_declarations_names(body) { + // i. Let F be StringValue of the BindingIdentifier of f. + // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F + // as a BindingIdentifier would not produce any Early Errors for body, then + if !lexically_declared_names.contains(&f) { + // 1. Let bindingExists be false. + // 2. Let thisEnv be lexEnv. + // 3. Assert: The following loop will terminate. + // 4. Repeat, while thisEnv is not varEnv, + // a. If thisEnv is not an Object Environment Record, then + // i. If ! thisEnv.HasBinding(F) is true, then + // i. Let bindingExists be true. + // b. Set thisEnv to thisEnv.[[OuterEnv]]. + let binding_exists = self.has_binding_until_var(f); + + // 5. If bindingExists is false and varEnv is a Global Environment Record, then + let fn_definable = if !binding_exists && var_environment_is_global { + // a. If varEnv.HasLexicalDeclaration(F) is false, then + // b. Else, + if self.current_environment.borrow().has_lex_binding(f) { + // i. Let fnDefinable be false. + false + } else { + // i. Let fnDefinable be ? varEnv.CanDeclareGlobalVar(F). + self.context.can_declare_global_var(f)? + } + } + // 6. Else, + else { + // a. Let fnDefinable be true. + true + }; + + // 7. If bindingExists is false and fnDefinable is true, then + if !binding_exists && fn_definable { + let mut function_names = Vec::new(); + + // a. If declaredFunctionOrVarNames does not contain F, then + if !declared_function_names.contains(&f) + //&& !var_names.contains(&f) + && !function_names.contains(&f) + { + // i. If varEnv is a Global Environment Record, then + if var_environment_is_global { + // i. Perform ? varEnv.CreateGlobalVarBinding(F, true). + self.context.create_global_var_binding(f, true)?; + } + // ii. Else, + else { + // i. Let bindingExists be ! varEnv.HasBinding(F). + // ii. If bindingExists is false, then + if !self.has_binding(f) { + // i. Perform ! varEnv.CreateMutableBinding(F, true). + self.create_mutable_binding(f, true); + + // ii. Perform ! varEnv.InitializeBinding(F, undefined). + let binding = self.initialize_mutable_binding(f, true); + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit(Opcode::DefInitVar, &[index]); + } + } + + // iii. Append F to declaredFunctionOrVarNames. + function_names.push(f); + } + + // b. When the FunctionDeclaration f is evaluated, perform the following steps + // in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: + // i. Let genv be the running execution context's VariableEnvironment. + // ii. Let benv be the running execution context's LexicalEnvironment. + // iii. Let fobj be ! benv.GetBindingValue(F, false). + // iv. Perform ? genv.SetMutableBinding(F, fobj, false). + // v. Return unused. + self.annex_b_function_names.push(f); + } + } + } + } // 12. Let declaredVarNames be a new empty List. let mut declared_var_names = Vec::new(); @@ -752,13 +900,16 @@ impl ByteCompiler<'_, '_> { // a. If strict is true or simpleParameterList is false, then // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). // b. Else, - // i. NOTE: A mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters. + // i. NOTE: A mapped argument object is only provided for non-strict functions + // that don't have a rest parameter, any parameter + // default value initializers, or any destructured parameters. // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). // c. If strict is true, then if strict { // i. Perform ! env.CreateImmutableBinding("arguments", false). - // ii. NOTE: In strict mode code early errors prevent attempting to assign to this binding, so its mutability is not observable. + // ii. NOTE: In strict mode code early errors prevent attempting to assign + // to this binding, so its mutability is not observable. self.create_immutable_binding(arguments, false); self.arguments_binding = Some(self.initialize_immutable_binding(arguments)); } @@ -777,7 +928,7 @@ impl ByteCompiler<'_, '_> { } // 23. Else, // a. Let parameterBindings be parameterNames. - let parameter_bindings = parameter_names; + let parameter_bindings = parameter_names.clone(); // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList). // 25. If hasDuplicates is true, then @@ -821,8 +972,11 @@ impl ByteCompiler<'_, '_> { // 27. If hasParameterExpressions is false, then // 28. Else, - if has_parameter_expressions { - // a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body. + #[allow(unused_variables, unused_mut)] + let mut instantiated_var_names = if has_parameter_expressions { + // a. NOTE: A separate Environment Record is needed to ensure that closures created by + // expressions in the formal parameter list do not have + // visibility of declarations in the function body. // b. Let varEnv be NewDeclarativeEnvironment(env). // c. Set the VariableEnvironment of calleeContext to varEnv. self.push_compile_environment(true); @@ -865,6 +1019,8 @@ impl ByteCompiler<'_, '_> { // the same value as the corresponding initialized parameter. } } + + instantiated_var_names } else { // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars. // b. Let instantiatedVarNames be a copy of the List parameterBindings. @@ -889,21 +1045,67 @@ impl ByteCompiler<'_, '_> { } // d. Let varEnv be env. + + instantiated_var_names }; // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. - // TODO: Support B.3.2.1 - // 29. If strict is false, then - // a. Let lexEnv be NewDeclarativeEnvironment(varEnv). - // b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places all declarations into a new Environment Record. + #[cfg(feature = "annex-b")] + if !strict { + // a. For each FunctionDeclaration f that is directly contained in the StatementList + // of a Block, CaseClause, or DefaultClause, do + for f in annex_b_function_declarations_names(code) { + // i. Let F be StringValue of the BindingIdentifier of f. + // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F + // as a BindingIdentifier would not produce any Early Errors + // for func and parameterNames does not contain F, then + if !lexical_names.contains(&f) && !parameter_names.contains(&f) { + // 1. NOTE: A var binding for F is only instantiated here if it is neither a + // VarDeclaredName, the name of a formal parameter, or another FunctionDeclaration. + + // 2. If initializedBindings does not contain F and F is not "arguments", then + if !instantiated_var_names.contains(&f) && f != arguments { + // a. Perform ! varEnv.CreateMutableBinding(F, false). + self.create_mutable_binding(f, true); + + // b. Perform ! varEnv.InitializeBinding(F, undefined). + let binding = self.initialize_mutable_binding(f, true); + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit(Opcode::DefInitVar, &[index]); + + // c. Append F to instantiatedVarNames. + instantiated_var_names.push(f); + } + + // 3. When the FunctionDeclaration f is evaluated, perform the following steps + // in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: + // a. Let fenv be the running execution context's VariableEnvironment. + // b. Let benv be the running execution context's LexicalEnvironment. + // c. Let fobj be ! benv.GetBindingValue(F, false). + // d. Perform ! fenv.SetMutableBinding(F, fobj, false). + // e. Return unused. + self.annex_b_function_names.push(f); + } + } + } + + // 30. If strict is false, then + // 30.a. Let lexEnv be NewDeclarativeEnvironment(varEnv). + // 30.b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical + // declarations so that a direct eval can determine whether any var scoped declarations + // introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. + // This is not needed for strict functions because a strict direct eval always + // places all declarations into a new Environment Record. // 31. Else, // a. Let lexEnv be varEnv. // 32. Set the LexicalEnvironment of calleeContext to lexEnv. // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 34. For each element d of lexDeclarations, do - // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized. + // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, + // formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do // i. If IsConstantDeclaration of d is true, then // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). diff --git a/boa_engine/src/bytecompiler/env.rs b/boa_engine/src/bytecompiler/env.rs index 8db518ee634..9c325b6f45b 100644 --- a/boa_engine/src/bytecompiler/env.rs +++ b/boa_engine/src/bytecompiler/env.rs @@ -66,6 +66,15 @@ impl ByteCompiler<'_, '_> { .has_binding_eval(name, strict) } + #[cfg(feature = "annex-b")] + /// Check if a binding name exists in a environment. + /// Stop when a function scope is reached. + pub(crate) fn has_binding_until_var(&self, name: Identifier) -> bool { + self.current_environment + .borrow() + .has_binding_until_var(name) + } + /// Create a mutable binding at bytecode compile time. /// This function returns a syntax error, if the binding is a redeclaration. /// @@ -119,4 +128,12 @@ impl ByteCompiler<'_, '_> { .borrow() .set_mutable_binding_recursive(name) } + + #[cfg(feature = "annex-b")] + /// Return the binding locator for a set operation on an existing var binding. + pub(crate) fn set_mutable_binding_var(&self, name: Identifier) -> BindingLocator { + self.current_environment + .borrow() + .set_mutable_binding_var_recursive(name) + } } diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index 4a5370c4d7a..1a503554eaf 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -125,7 +125,7 @@ impl FunctionCompiler { self.generator, ); - compiler.compile_statement_list(body, false); + compiler.compile_statement_list(body, false, false); if let Some(env_labels) = env_labels { let env_info = compiler.pop_compile_environment(); diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 5b488a19a3e..8dea9078ea0 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -290,6 +290,9 @@ pub struct ByteCompiler<'ctx, 'host> { // TODO: remove when we separate scripts from the context context: &'ctx mut Context<'host>, + + #[cfg(feature = "annex-b")] + annex_b_function_names: Vec, } impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { @@ -337,6 +340,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { json_parse, current_environment, context, + + #[cfg(feature = "annex-b")] + annex_b_function_names: Vec::new(), } } @@ -403,11 +409,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { let index = self.get_or_insert_binding(binding); self.emit(Opcode::DefVar, &[index]); } - BindingOpcode::Let => { - let binding = self.initialize_mutable_binding(name, false); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefLet, &[index]); - } BindingOpcode::InitVar => { let binding = if self.has_binding(name) { self.set_mutable_binding(name) @@ -420,12 +421,12 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { BindingOpcode::InitLet => { let binding = self.initialize_mutable_binding(name, false); let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitLet, &[index]); + self.emit(Opcode::PutLexicalValue, &[index]); } BindingOpcode::InitConst => { let binding = self.initialize_immutable_binding(name); let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitConst, &[index]); + self.emit(Opcode::PutLexicalValue, &[index]); } BindingOpcode::SetName => { let binding = self.set_mutable_binding(name); @@ -742,7 +743,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } /// Compile a [`StatementList`]. - pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool) { + pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool, block: bool) { if use_expr { let expr_index = list .statements() @@ -758,11 +759,11 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { .count(); for (i, item) in list.statements().iter().enumerate() { - self.compile_stmt_list_item(item, i + 1 == expr_index); + self.compile_stmt_list_item(item, i + 1 == expr_index, block); } } else { for item in list.statements() { - self.compile_stmt_list_item(item, false); + self.compile_stmt_list_item(item, false, block); } } } @@ -980,10 +981,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { let ident = ident; if let Some(expr) = variable.init() { self.compile_expr(expr, true); - self.emit_binding(BindingOpcode::InitLet, *ident); } else { - self.emit_binding(BindingOpcode::Let, *ident); + self.emit_opcode(Opcode::PushUndefined); } + self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { if let Some(init) = variable.init() { @@ -1023,18 +1024,33 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } /// Compile a [`StatementListItem`]. - fn compile_stmt_list_item(&mut self, item: &StatementListItem, use_expr: bool) { + fn compile_stmt_list_item(&mut self, item: &StatementListItem, use_expr: bool, block: bool) { match item { StatementListItem::Statement(stmt) => { self.compile_stmt(stmt, use_expr); } - StatementListItem::Declaration(decl) => self.compile_decl(decl), + StatementListItem::Declaration(decl) => self.compile_decl(decl, block), } } /// Compile a [`Declaration`]. - pub fn compile_decl(&mut self, decl: &Declaration) { + #[allow(unused_variables)] + pub fn compile_decl(&mut self, decl: &Declaration, block: bool) { match decl { + #[cfg(feature = "annex-b")] + Declaration::Function(function) if block => { + let name = function + .name() + .expect("function declaration must have name"); + if self.annex_b_function_names.contains(&name) { + let binding = self.get_binding_value(name); + let index = self.get_or_insert_binding(binding); + self.emit(Opcode::GetName, &[index]); + let binding = self.set_mutable_binding_var(name); + let index = self.get_or_insert_binding(binding); + self.emit(Opcode::SetName, &[index]); + } + } Declaration::Class(class) => self.class(class, false), Declaration::Lexical(lexical) => self.compile_lexical_decl(lexical), _ => {} diff --git a/boa_engine/src/bytecompiler/module.rs b/boa_engine/src/bytecompiler/module.rs index 01ee557ba98..6e2de81a97b 100644 --- a/boa_engine/src/bytecompiler/module.rs +++ b/boa_engine/src/bytecompiler/module.rs @@ -18,7 +18,7 @@ impl ByteCompiler<'_, '_> { pub fn compile_module_item(&mut self, item: &ModuleItem) { match item { ModuleItem::StatementListItem(stmt) => { - self.compile_stmt_list_item(stmt, false); + self.compile_stmt_list_item(stmt, false, false); } _ => { // TODO: Remove after implementing modules. diff --git a/boa_engine/src/bytecompiler/statement/block.rs b/boa_engine/src/bytecompiler/statement/block.rs index f72a11e6319..a78fe171d36 100644 --- a/boa_engine/src/bytecompiler/statement/block.rs +++ b/boa_engine/src/bytecompiler/statement/block.rs @@ -8,7 +8,7 @@ impl ByteCompiler<'_, '_> { let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); self.block_declaration_instantiation(block); - self.compile_statement_list(block.statement_list(), use_expr); + self.compile_statement_list(block.statement_list(), use_expr, true); let env_info = self.pop_compile_environment(); self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); diff --git a/boa_engine/src/bytecompiler/statement/switch.rs b/boa_engine/src/bytecompiler/statement/switch.rs index 08c986ec5a8..626da044d05 100644 --- a/boa_engine/src/bytecompiler/statement/switch.rs +++ b/boa_engine/src/bytecompiler/statement/switch.rs @@ -43,7 +43,7 @@ impl ByteCompiler<'_, '_> { label }; self.patch_jump(label); - self.compile_statement_list(case.body(), false); + self.compile_statement_list(case.body(), false, true); } if !default_label_set { diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index ce6a3901b7f..caecfd72f7e 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -262,7 +262,7 @@ impl<'host> Context<'host> { self, ); compiler.global_declaration_instantiation(statement_list)?; - compiler.compile_statement_list(statement_list, true); + compiler.compile_statement_list(statement_list, true, false); Ok(Gc::new(compiler.finish())) } diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index e85ff628218..c5b7eb1f256 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -56,6 +56,12 @@ impl CompileTimeEnvironment { .map_or(false, |binding| binding.lex) } + #[cfg(feature = "annex-b")] + /// Check if the environment has a binding with the given name. + pub(crate) fn has_binding(&self, name: Identifier) -> bool { + self.bindings.contains_key(&name) + } + /// Checks if `name` is a lexical binding. pub(crate) fn is_lex_binding(&self, name: Identifier) -> bool { if let Some(binding) = self.bindings.get(&name) { @@ -123,6 +129,23 @@ impl CompileTimeEnvironment { } } + #[cfg(feature = "annex-b")] + /// Check if a binding name exists in a environment. + /// Stop when a function scope is reached. + pub(crate) fn has_binding_until_var(&self, name: Identifier) -> bool { + if self.function_scope { + return false; + } + if self.bindings.contains_key(&name) { + return true; + } + if let Some(outer) = &self.outer { + outer.borrow().has_binding_until_var(name) + } else { + false + } + } + /// Create a mutable binding. /// /// If the binding is a function scope binding and this is a declarative environment, try the outer environment. @@ -236,6 +259,29 @@ impl CompileTimeEnvironment { } } + #[cfg(feature = "annex-b")] + /// Return the binding locator for a set operation on an existing var binding. + pub(crate) fn set_mutable_binding_var_recursive(&self, name: Identifier) -> BindingLocator { + if !self.is_function() { + return self.outer.as_ref().map_or_else( + || BindingLocator::global(name), + |outer| outer.borrow().set_mutable_binding_var_recursive(name), + ); + } + + match self.bindings.get(&name) { + Some(binding) if binding.mutable => { + BindingLocator::declarative(name, self.environment_index, binding.index) + } + Some(binding) if binding.strict => BindingLocator::mutate_immutable(name), + Some(_) => BindingLocator::silent(name), + None => self.outer.as_ref().map_or_else( + || BindingLocator::global(name), + |outer| outer.borrow().set_mutable_binding_var_recursive(name), + ), + } + } + /// Gets the outer environment of this environment. pub(crate) fn outer(&self) -> Option>> { self.outer.clone() diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index b4a567bb087..3557636a612 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -613,13 +613,13 @@ impl DeclarativeEnvironmentStack { } } - /// Set the value of a declarative binding. + /// Set the value of a lexical binding. /// /// # Panics /// /// Panics if the environment or binding index are out of range. #[track_caller] - pub(crate) fn put_declarative_value( + pub(crate) fn put_lexical_value( &mut self, environment_index: usize, binding_index: usize, diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 144f93712cd..844f2928436 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -330,12 +330,9 @@ impl CodeBlock { self.functions[operand as usize].length ) } - Opcode::DefInitArg - | Opcode::DefVar + Opcode::DefVar | Opcode::DefInitVar - | Opcode::DefLet - | Opcode::DefInitLet - | Opcode::DefInitConst + | Opcode::PutLexicalValue | Opcode::GetName | Opcode::GetLocator | Opcode::GetNameAndLocator @@ -977,7 +974,7 @@ impl JsObject { context .vm .environments - .put_declarative_value(index, 0, class_object.into()); + .put_lexical_value(index, 0, class_object.into()); last_env -= 1; } @@ -989,7 +986,7 @@ impl JsObject { context .vm .environments - .put_declarative_value(index, 0, self.clone().into()); + .put_lexical_value(index, 0, self.clone().into()); last_env -= 1; } @@ -1023,7 +1020,7 @@ impl JsObject { context, ) }; - context.vm.environments.put_declarative_value( + context.vm.environments.put_lexical_value( binding.environment_index(), binding.binding_index(), arguments_obj.into(), @@ -1245,7 +1242,7 @@ impl JsObject { context .vm .environments - .put_declarative_value(index, 0, self.clone().into()); + .put_lexical_value(index, 0, self.clone().into()); last_env -= 1; } @@ -1278,7 +1275,7 @@ impl JsObject { context, ) }; - context.vm.environments.put_declarative_value( + context.vm.environments.put_lexical_value( binding.environment_index(), binding.binding_index(), arguments_obj.into(), diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index 951d203cc24..37933d8e506 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -405,12 +405,9 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::DefInitArg - | Opcode::DefVar + Opcode::DefVar | Opcode::DefInitVar - | Opcode::DefLet - | Opcode::DefInitLet - | Opcode::DefInitConst + | Opcode::PutLexicalValue | Opcode::GetName | Opcode::GetLocator | Opcode::GetNameAndLocator diff --git a/boa_engine/src/vm/opcode/define/mod.rs b/boa_engine/src/vm/opcode/define/mod.rs index 4a9bec904e6..cce591aff52 100644 --- a/boa_engine/src/vm/opcode/define/mod.rs +++ b/boa_engine/src/vm/opcode/define/mod.rs @@ -66,57 +66,26 @@ impl Operation for DefInitVar { } } -/// `DefLet` implements the Opcode Operation for `Opcode::DefLet` +/// `PutLexicalValue` implements the Opcode Operation for `Opcode::PutLexicalValue` /// /// Operation: -/// - Declare `let` type variable. +/// - Initialize a lexical binding. #[derive(Debug, Clone, Copy)] -pub(crate) struct DefLet; +pub(crate) struct PutLexicalValue; -impl Operation for DefLet { - const NAME: &'static str = "DefLet"; - const INSTRUCTION: &'static str = "INST - DefLet"; +impl Operation for PutLexicalValue { + const NAME: &'static str = "PutLexicalValue"; + const INSTRUCTION: &'static str = "INST - PutLexicalValue"; fn execute(context: &mut Context<'_>) -> JsResult { let index = context.vm.read::(); + let value = context.vm.pop(); let binding_locator = context.vm.frame().code_block.bindings[index as usize]; - context.vm.environments.put_declarative_value( + context.vm.environments.put_lexical_value( binding_locator.environment_index(), binding_locator.binding_index(), - JsValue::Undefined, + value, ); Ok(CompletionType::Normal) } } - -macro_rules! implement_declaratives { - ($name:ident, $doc_string:literal) => { - #[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")] - #[doc= "\n"] - #[doc="Operation:\n"] - #[doc= concat!(" - ", $doc_string)] - #[derive(Debug, Clone, Copy)] - pub(crate) struct $name; - - impl Operation for $name { - const NAME: &'static str = stringify!($name); - const INSTRUCTION: &'static str = stringify!("INST - " + $name); - - fn execute(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - let value = context.vm.pop(); - let binding_locator = context.vm.frame().code_block.bindings[index as usize]; - context.vm.environments.put_declarative_value( - binding_locator.environment_index(), - binding_locator.binding_index(), - value, - ); - Ok(CompletionType::Normal) - } - } - }; -} - -implement_declaratives!(DefInitLet, "Declare and initialize `let` type variable"); -implement_declaratives!(DefInitConst, "Declare and initialize `const` type variable"); -implement_declaratives!(DefInitArg, "Declare and initialize function arguments"); diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index e4611ecc35c..8336139ab48 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -638,13 +638,6 @@ generate_impl! { /// Stack: value **=>** (ToNumeric(value)), (value - 1) DecPost, - /// Declare and initialize a function argument. - /// - /// Operands: name_index: `u32` - /// - /// Stack: value **=>** - DefInitArg, - /// Declare `var` type variable. /// /// Operands: name_index: `u32` @@ -659,26 +652,12 @@ generate_impl! { /// Stack: value **=>** DefInitVar, - /// Declare `let` type variable. - /// - /// Operands: name_index: `u32` - /// - /// Stack: **=>** - DefLet, - - /// Declare and initialize `let` type variable. - /// - /// Operands: name_index: `u32` - /// - /// Stack: value **=>** - DefInitLet, - - /// Declare and initialize `const` type variable. + /// Initialize a lexical binding. /// /// Operands: name_index: `u32` /// /// Stack: value **=>** - DefInitConst, + PutLexicalValue, /// Find a binding on the environment chain and push its value. /// @@ -1633,7 +1612,6 @@ generate_impl! { #[derive(Clone, Copy, Debug)] pub(crate) enum BindingOpcode { Var, - Let, InitVar, InitLet, InitConst,