diff --git a/Cargo.lock b/Cargo.lock index f54c45bfc9..4727d3126a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,7 @@ dependencies = [ "bitflags 2.6.0", "boa_interner", "boa_macros", + "boa_string", "indexmap", "num-bigint", "rustc-hash 2.0.0", diff --git a/cli/src/main.rs b/cli/src/main.rs index c9dbfbba92..d43335df52 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -190,8 +190,9 @@ where let mut parser = boa_parser::Parser::new(Source::from_bytes(src)); let dump = if args.module { + let scope = context.realm().scope().clone(); let module = parser - .parse_module(context.interner_mut()) + .parse_module(&scope, context.interner_mut()) .map_err(|e| format!("Uncaught SyntaxError: {e}"))?; match arg { @@ -202,8 +203,9 @@ where DumpFormat::Debug => format!("{module:#?}"), } } else { + let scope = context.realm().scope().clone(); let mut script = parser - .parse_script(context.interner_mut()) + .parse_script(&scope, context.interner_mut()) .map_err(|e| format!("Uncaught SyntaxError: {e}"))?; if args.optimize { diff --git a/core/ast/Cargo.toml b/core/ast/Cargo.toml index 3ec560fc3b..53c4beb670 100644 --- a/core/ast/Cargo.toml +++ b/core/ast/Cargo.toml @@ -11,12 +11,14 @@ repository.workspace = true rust-version.workspace = true [features] +annex-b = [] serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"] arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"] [dependencies] boa_interner.workspace = true boa_macros.workspace = true +boa_string.workspace = true rustc-hash = { workspace = true, features = ["std"] } bitflags.workspace = true num-bigint.workspace = true diff --git a/core/ast/src/expression/literal/object.rs b/core/ast/src/expression/literal/object.rs index 86be5f4d3e..88355918b9 100644 --- a/core/ast/src/expression/literal/object.rs +++ b/core/ast/src/expression/literal/object.rs @@ -8,8 +8,10 @@ use crate::{ }, function::{FormalParameterList, FunctionBody}, join_nodes, + operations::{contains, ContainsSymbol}, pattern::{ObjectPattern, ObjectPatternElement}, property::{MethodDefinitionKind, PropertyName}, + scope::FunctionScopes, try_break, visitor::{VisitWith, Visitor, VisitorMut}, }; @@ -401,27 +403,35 @@ impl VisitWith for PropertyDefinition { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ObjectMethodDefinition { - name: PropertyName, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) name: PropertyName, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, kind: MethodDefinitionKind, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl ObjectMethodDefinition { /// Creates a new object method definition. #[inline] #[must_use] - pub const fn new( + pub fn new( name: PropertyName, parameters: FormalParameterList, body: FunctionBody, kind: MethodDefinitionKind, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, kind, + scopes: FunctionScopes::default(), } } @@ -452,6 +462,13 @@ impl ObjectMethodDefinition { pub const fn kind(&self) -> MethodDefinitionKind { self.kind } + + /// Gets the scopes of the object method definition. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for ObjectMethodDefinition { @@ -467,7 +484,7 @@ impl ToIndentedString for ObjectMethodDefinition { }; let name = self.name.to_interned_string(interner); let parameters = join_nodes(interner, self.parameters.as_ref()); - let body = block_to_string(self.body.statements(), interner, indent_n + 1); + let body = block_to_string(&self.body.statements, interner, indent_n + 1); format!("{indentation}{prefix}{name}({parameters}) {body},\n") } } @@ -479,7 +496,7 @@ impl VisitWith for ObjectMethodDefinition { { try_break!(visitor.visit_property_name(&self.name)); try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -488,6 +505,6 @@ impl VisitWith for ObjectMethodDefinition { { try_break!(visitor.visit_property_name_mut(&mut self.name)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/function/arrow_function.rs b/core/ast/src/function/arrow_function.rs index f11ffba5fa..26bf82437f 100644 --- a/core/ast/src/function/arrow_function.rs +++ b/core/ast/src/function/arrow_function.rs @@ -1,3 +1,5 @@ +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::FunctionScopes; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -23,34 +25,42 @@ use super::{FormalParameterList, FunctionBody}; #[derive(Clone, Debug, PartialEq)] pub struct ArrowFunction { pub(crate) name: Option, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl ArrowFunction { /// Creates a new `ArrowFunctionDecl` AST Expression. #[inline] #[must_use] - pub const fn new( + pub fn new( name: Option, - params: FormalParameterList, + parameters: FormalParameterList, body: FunctionBody, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, - parameters: params, + parameters, body, + contains_direct_eval, + scopes: FunctionScopes::default(), } } - /// Gets the name of the function declaration. + /// Gets the name of the arrow function. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } - /// Sets the name of the function declaration. + /// Sets the name of the arrow function. #[inline] pub fn set_name(&mut self, name: Option) { self.name = name; @@ -69,6 +79,13 @@ impl ArrowFunction { pub const fn body(&self) -> &FunctionBody { &self.body } + + /// Returns the scopes of the arrow function. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for ArrowFunction { @@ -102,7 +119,7 @@ impl VisitWith for ArrowFunction { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -113,6 +130,6 @@ impl VisitWith for ArrowFunction { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/function/async_arrow_function.rs b/core/ast/src/function/async_arrow_function.rs index 6e4d19ad98..a6a360dc67 100644 --- a/core/ast/src/function/async_arrow_function.rs +++ b/core/ast/src/function/async_arrow_function.rs @@ -1,6 +1,8 @@ use std::ops::ControlFlow; use super::{FormalParameterList, FunctionBody}; +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::FunctionScopes; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -23,52 +25,67 @@ use boa_interner::{Interner, ToIndentedString}; #[derive(Clone, Debug, PartialEq)] pub struct AsyncArrowFunction { pub(crate) name: Option, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl AsyncArrowFunction { /// Creates a new `AsyncArrowFunction` AST Expression. #[inline] #[must_use] - pub const fn new( + pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, + scopes: FunctionScopes::default(), } } - /// Gets the name of the function declaration. + /// Gets the name of the async arrow function. #[inline] #[must_use] pub const fn name(&self) -> Option { self.name } - /// Sets the name of the function declaration. + /// Sets the name of the async arrow function. #[inline] pub fn set_name(&mut self, name: Option) { self.name = name; } - /// Gets the list of parameters of the arrow function. + /// Gets the list of parameters of the async arrow function. #[inline] #[must_use] pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } - /// Gets the body of the arrow function. + /// Gets the body of the async arrow function. #[inline] #[must_use] pub const fn body(&self) -> &FunctionBody { &self.body } + + /// Returns the scopes of the async arrow function. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for AsyncArrowFunction { @@ -102,7 +119,7 @@ impl VisitWith for AsyncArrowFunction { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -113,6 +130,6 @@ impl VisitWith for AsyncArrowFunction { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/function/async_function.rs b/core/ast/src/function/async_function.rs index a90711654b..f80be21962 100644 --- a/core/ast/src/function/async_function.rs +++ b/core/ast/src/function/async_function.rs @@ -4,7 +4,10 @@ use super::{FormalParameterList, FunctionBody}; use crate::{ block_to_string, expression::{Expression, Identifier}, - join_nodes, try_break, + join_nodes, + operations::{contains, ContainsSymbol}, + scope::{FunctionScopes, Scope}, + try_break, visitor::{VisitWith, Visitor, VisitorMut}, Declaration, }; @@ -24,23 +27,27 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct AsyncFunctionDeclaration { name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl AsyncFunctionDeclaration { /// Creates a new async function declaration. #[inline] #[must_use] - pub const fn new( - name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, - ) -> Self { + pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -64,6 +71,13 @@ impl AsyncFunctionDeclaration { pub const fn body(&self) -> &FunctionBody { &self.body } + + /// Gets the scopes of the async function declaration. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for AsyncFunctionDeclaration { @@ -72,7 +86,7 @@ impl ToIndentedString for AsyncFunctionDeclaration { "async function {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) ) } } @@ -84,7 +98,7 @@ impl VisitWith for AsyncFunctionDeclaration { { try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -93,7 +107,7 @@ impl VisitWith for AsyncFunctionDeclaration { { try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } @@ -117,26 +131,38 @@ impl From for Declaration { #[derive(Clone, Debug, PartialEq)] pub struct AsyncFunctionExpression { pub(crate) name: Option, - parameters: FormalParameterList, - body: FunctionBody, - has_binding_identifier: bool, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) has_binding_identifier: bool, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) name_scope: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl AsyncFunctionExpression { /// Creates a new async function expression. #[inline] #[must_use] - pub const fn new( + pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, has_binding_identifier: bool, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, + name_scope: None, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -167,6 +193,20 @@ impl AsyncFunctionExpression { pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } + + /// Gets the name scope of the async function expression. + #[inline] + #[must_use] + pub const fn name_scope(&self) -> Option<&Scope> { + self.name_scope.as_ref() + } + + /// Gets the scopes of the async function expression. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for AsyncFunctionExpression { @@ -210,7 +250,7 @@ impl VisitWith for AsyncFunctionExpression { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -221,6 +261,6 @@ impl VisitWith for AsyncFunctionExpression { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/function/async_generator.rs b/core/ast/src/function/async_generator.rs index 72614a0bef..b7a046e5fc 100644 --- a/core/ast/src/function/async_generator.rs +++ b/core/ast/src/function/async_generator.rs @@ -1,4 +1,6 @@ //! Async Generator Expression +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::{FunctionScopes, Scope}; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -24,23 +26,27 @@ use super::{FormalParameterList, FunctionBody}; #[derive(Clone, Debug, PartialEq)] pub struct AsyncGeneratorDeclaration { name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl AsyncGeneratorDeclaration { /// Creates a new async generator declaration. #[inline] #[must_use] - pub const fn new( - name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, - ) -> Self { + pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -64,6 +70,13 @@ impl AsyncGeneratorDeclaration { pub const fn body(&self) -> &FunctionBody { &self.body } + + /// Gets the scopes of the async generator declaration. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for AsyncGeneratorDeclaration { @@ -72,7 +85,7 @@ impl ToIndentedString for AsyncGeneratorDeclaration { "async function* {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) ) } } @@ -84,7 +97,7 @@ impl VisitWith for AsyncGeneratorDeclaration { { try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -93,7 +106,7 @@ impl VisitWith for AsyncGeneratorDeclaration { { try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } @@ -117,26 +130,38 @@ impl From for Declaration { #[derive(Clone, Debug, PartialEq)] pub struct AsyncGeneratorExpression { pub(crate) name: Option, - parameters: FormalParameterList, - body: FunctionBody, - has_binding_identifier: bool, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) has_binding_identifier: bool, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) name_scope: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl AsyncGeneratorExpression { /// Creates a new async generator expression. #[inline] #[must_use] - pub const fn new( + pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, has_binding_identifier: bool, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, + name_scope: None, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -167,6 +192,20 @@ impl AsyncGeneratorExpression { pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } + + /// Gets the name scope of the async generator expression. + #[inline] + #[must_use] + pub const fn name_scope(&self) -> Option<&Scope> { + self.name_scope.as_ref() + } + + /// Gets the scopes of the async generator expression. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for AsyncGeneratorExpression { @@ -180,7 +219,7 @@ impl ToIndentedString for AsyncGeneratorExpression { buf.push_str(&format!( "({}) {}", join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) )); buf @@ -203,7 +242,7 @@ impl VisitWith for AsyncGeneratorExpression { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -214,6 +253,6 @@ impl VisitWith for AsyncGeneratorExpression { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/function/class.rs b/core/ast/src/function/class.rs index 677d8ac86b..162b1b6933 100644 --- a/core/ast/src/function/class.rs +++ b/core/ast/src/function/class.rs @@ -3,7 +3,9 @@ use crate::{ block_to_string, expression::{Expression, Identifier}, join_nodes, + operations::{contains, ContainsSymbol}, property::{MethodDefinitionKind, PropertyName}, + scope::{FunctionScopes, Scope}, try_break, visitor::{VisitWith, Visitor, VisitorMut}, Declaration, @@ -25,9 +27,12 @@ use std::hash::Hash; #[derive(Clone, Debug, PartialEq)] pub struct ClassDeclaration { name: Identifier, - super_ref: Option, + pub(crate) super_ref: Option, pub(crate) constructor: Option, pub(crate) elements: Box<[ClassElement]>, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) name_scope: Scope, } impl ClassDeclaration { @@ -45,6 +50,7 @@ impl ClassDeclaration { super_ref, constructor, elements, + name_scope: Scope::default(), } } @@ -75,6 +81,13 @@ impl ClassDeclaration { pub const fn elements(&self) -> &[ClassElement] { &self.elements } + + /// Gets the scope containing the class name binding. + #[inline] + #[must_use] + pub const fn name_scope(&self) -> &Scope { + &self.name_scope + } } impl ToIndentedString for ClassDeclaration { @@ -96,7 +109,7 @@ impl ToIndentedString for ClassDeclaration { buf.push_str(&format!( "{indentation}constructor({}) {}\n", join_nodes(interner, expr.parameters().as_ref()), - block_to_string(expr.body().statements(), interner, indent_n + 1) + block_to_string(&expr.body.statements, interner, indent_n + 1) )); } for element in &self.elements { @@ -162,10 +175,12 @@ impl From for Declaration { #[derive(Clone, Debug, PartialEq)] pub struct ClassExpression { pub(crate) name: Option, - super_ref: Option, + pub(crate) super_ref: Option, pub(crate) constructor: Option, pub(crate) elements: Box<[ClassElement]>, - has_binding_identifier: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) name_scope: Option, } impl ClassExpression { @@ -179,12 +194,17 @@ impl ClassExpression { elements: Box<[ClassElement]>, has_binding_identifier: bool, ) -> Self { + let name_scope = if has_binding_identifier { + Some(Scope::default()) + } else { + None + }; Self { name, super_ref, constructor, elements, - has_binding_identifier, + name_scope, } } @@ -215,12 +235,19 @@ impl ClassExpression { pub const fn elements(&self) -> &[ClassElement] { &self.elements } + + /// Gets the scope containing the class name binding if it exists. + #[inline] + #[must_use] + pub const fn name_scope(&self) -> Option<&Scope> { + self.name_scope.as_ref() + } } impl ToIndentedString for ClassExpression { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let mut buf = "class".to_string(); - if self.has_binding_identifier { + if self.name_scope.is_some() { if let Some(name) = self.name { buf.push_str(&format!(" {}", interner.resolve_expect(name.sym()))); } @@ -241,7 +268,7 @@ impl ToIndentedString for ClassExpression { buf.push_str(&format!( "{indentation}constructor({}) {}\n", join_nodes(interner, expr.parameters().as_ref()), - block_to_string(expr.body().statements(), interner, indent_n + 1) + block_to_string(&expr.body.statements, interner, indent_n + 1) )); } for element in &self.elements { @@ -303,7 +330,41 @@ impl VisitWith for ClassExpression { /// Just an alias for [`Script`](crate::Script), since it has the same exact semantics. /// /// [spec]: https://tc39.es/ecma262/#prod-ClassStaticBlockBody -type StaticBlockBody = crate::Script; +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq)] +pub struct StaticBlockBody { + pub(crate) body: FunctionBody, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, +} + +impl StaticBlockBody { + /// Creates a new static block body. + #[inline] + #[must_use] + pub fn new(body: FunctionBody) -> Self { + Self { + body, + scopes: FunctionScopes::default(), + } + } + + /// Gets the body static block. + #[inline] + #[must_use] + pub const fn statements(&self) -> &FunctionBody { + &self.body + } + + /// Gets the scopes of the static block body. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } +} /// An element that can be within a class. /// @@ -319,13 +380,13 @@ pub enum ClassElement { MethodDefinition(ClassMethodDefinition), /// A field definition. - FieldDefinition(PropertyName, Option), + FieldDefinition(ClassFieldDefinition), /// A static field definition, accessible from the class constructor object - StaticFieldDefinition(PropertyName, Option), + StaticFieldDefinition(ClassFieldDefinition), /// A private field definition, only accessible inside the class declaration. - PrivateFieldDefinition(PrivateName, Option), + PrivateFieldDefinition(PrivateFieldDefinition), /// A private static field definition, only accessible from static methods and fields inside the /// class declaration. @@ -335,39 +396,145 @@ pub enum ClassElement { StaticBlock(StaticBlockBody), } +/// A non-private class element field definition. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FieldDefinition +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq)] +pub struct ClassFieldDefinition { + pub(crate) name: PropertyName, + pub(crate) field: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Scope, +} + +impl ClassFieldDefinition { + /// Creates a new class field definition. + #[inline] + #[must_use] + pub fn new(name: PropertyName, field: Option) -> Self { + Self { + name, + field, + scope: Scope::default(), + } + } + + /// Returns the name of the class field definition. + #[inline] + #[must_use] + pub const fn name(&self) -> &PropertyName { + &self.name + } + + /// Returns the field of the class field definition. + #[inline] + #[must_use] + pub const fn field(&self) -> Option<&Expression> { + self.field.as_ref() + } + + /// Returns the scope of the class field definition. + #[inline] + #[must_use] + pub const fn scope(&self) -> &Scope { + &self.scope + } +} + +/// A private class element field definition. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FieldDefinition +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq)] +pub struct PrivateFieldDefinition { + pub(crate) name: PrivateName, + pub(crate) field: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Scope, +} + +impl PrivateFieldDefinition { + /// Creates a new private field definition. + #[inline] + #[must_use] + pub fn new(name: PrivateName, field: Option) -> Self { + Self { + name, + field, + scope: Scope::default(), + } + } + + /// Returns the name of the private field definition. + #[inline] + #[must_use] + pub const fn name(&self) -> &PrivateName { + &self.name + } + + /// Returns the field of the private field definition. + #[inline] + #[must_use] + pub const fn field(&self) -> Option<&Expression> { + self.field.as_ref() + } + + /// Returns the scope of the private field definition. + #[inline] + #[must_use] + pub const fn scope(&self) -> &Scope { + &self.scope + } +} + impl ToIndentedString for ClassElement { fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String { let indentation = " ".repeat(indent_n + 1); match self { Self::MethodDefinition(m) => m.to_indented_string(interner, indent_n), - Self::FieldDefinition(name, field) => match field { + Self::FieldDefinition(field) => match &field.field { Some(expr) => { format!( "{indentation}{} = {};\n", - name.to_interned_string(interner), + field.name.to_interned_string(interner), expr.to_no_indent_string(interner, indent_n + 1) ) } None => { - format!("{indentation}{};\n", name.to_interned_string(interner),) + format!( + "{indentation}{};\n", + field.name.to_interned_string(interner), + ) } }, - Self::StaticFieldDefinition(name, field) => match field { + Self::StaticFieldDefinition(field) => match &field.field { Some(expr) => { format!( "{indentation}static {} = {};\n", - name.to_interned_string(interner), + field.name.to_interned_string(interner), expr.to_no_indent_string(interner, indent_n + 1) ) } None => { format!( "{indentation}static {};\n", - name.to_interned_string(interner), + field.name.to_interned_string(interner), ) } }, - Self::PrivateFieldDefinition(name, field) => match field { + Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) => match field + { Some(expr) => { format!( "{indentation}#{} = {};\n", @@ -397,10 +564,10 @@ impl ToIndentedString for ClassElement { ) } }, - Self::StaticBlock(body) => { + Self::StaticBlock(block) => { format!( "{indentation}static {}\n", - block_to_string(body.statements(), interner, indent_n + 1) + block_to_string(&block.body.statements, interner, indent_n + 1) ) } } @@ -423,26 +590,26 @@ impl VisitWith for ClassElement { } } try_break!(visitor.visit_formal_parameter_list(&m.parameters)); - visitor.visit_script(&m.body) + visitor.visit_function_body(&m.body) } - Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => { - try_break!(visitor.visit_property_name(pn)); - if let Some(expr) = maybe_expr { + Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => { + try_break!(visitor.visit_property_name(&field.name)); + if let Some(expr) = &field.field { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } - Self::PrivateFieldDefinition(name, maybe_expr) - | Self::PrivateStaticFieldDefinition(name, maybe_expr) => { + Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) + | Self::PrivateStaticFieldDefinition(name, field) => { try_break!(visitor.visit_private_name(name)); - if let Some(expr) = maybe_expr { + if let Some(expr) = field { visitor.visit_expression(expr) } else { ControlFlow::Continue(()) } } - Self::StaticBlock(sl) => visitor.visit_script(sl), + Self::StaticBlock(block) => visitor.visit_function_body(&block.body), } } @@ -461,26 +628,26 @@ impl VisitWith for ClassElement { } } try_break!(visitor.visit_formal_parameter_list_mut(&mut m.parameters)); - visitor.visit_script_mut(&mut m.body) + visitor.visit_function_body_mut(&mut m.body) } - Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => { - try_break!(visitor.visit_property_name_mut(pn)); - if let Some(expr) = maybe_expr { + Self::FieldDefinition(field) | Self::StaticFieldDefinition(field) => { + try_break!(visitor.visit_property_name_mut(&mut field.name)); + if let Some(expr) = &mut field.field { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } - Self::PrivateFieldDefinition(name, maybe_expr) - | Self::PrivateStaticFieldDefinition(name, maybe_expr) => { + Self::PrivateFieldDefinition(PrivateFieldDefinition { name, field, .. }) + | Self::PrivateStaticFieldDefinition(name, field) => { try_break!(visitor.visit_private_name_mut(name)); - if let Some(expr) = maybe_expr { + if let Some(expr) = field { visitor.visit_expression_mut(expr) } else { ControlFlow::Continue(()) } } - Self::StaticBlock(sl) => visitor.visit_script_mut(sl), + Self::StaticBlock(block) => visitor.visit_function_body_mut(&mut block.body), } } } @@ -499,29 +666,37 @@ impl VisitWith for ClassElement { #[derive(Clone, Debug, PartialEq)] pub struct ClassMethodDefinition { name: ClassElementName, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, kind: MethodDefinitionKind, is_static: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl ClassMethodDefinition { /// Creates a new class method definition. #[inline] #[must_use] - pub const fn new( + pub fn new( name: ClassElementName, parameters: FormalParameterList, body: FunctionBody, kind: MethodDefinitionKind, is_static: bool, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, kind, is_static, + scopes: FunctionScopes::default(), } } @@ -566,6 +741,13 @@ impl ClassMethodDefinition { pub const fn is_private(&self) -> bool { self.name.is_private() } + + /// Gets the scopes of the class method definition. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for ClassMethodDefinition { @@ -587,7 +769,7 @@ impl ToIndentedString for ClassMethodDefinition { }; let name = self.name.to_interned_string(interner); let parameters = join_nodes(interner, self.parameters.as_ref()); - let body = block_to_string(self.body.statements(), interner, indent_n + 1); + let body = block_to_string(&self.body.statements, interner, indent_n + 1); format!("{indentation}{prefix}{name}({parameters}) {body}\n") } } diff --git a/core/ast/src/function/generator.rs b/core/ast/src/function/generator.rs index 3c340b87a0..7911b922a6 100644 --- a/core/ast/src/function/generator.rs +++ b/core/ast/src/function/generator.rs @@ -2,7 +2,10 @@ use super::{FormalParameterList, FunctionBody}; use crate::{ block_to_string, expression::{Expression, Identifier}, - join_nodes, try_break, + join_nodes, + operations::{contains, ContainsSymbol}, + scope::{FunctionScopes, Scope}, + try_break, visitor::{VisitWith, Visitor, VisitorMut}, Declaration, }; @@ -22,23 +25,27 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct GeneratorDeclaration { name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl GeneratorDeclaration { /// Creates a new generator declaration. #[inline] #[must_use] - pub const fn new( - name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, - ) -> Self { + pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -62,6 +69,13 @@ impl GeneratorDeclaration { pub const fn body(&self) -> &FunctionBody { &self.body } + + /// Returns the scopes of the generator declaration. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for GeneratorDeclaration { @@ -70,7 +84,7 @@ impl ToIndentedString for GeneratorDeclaration { "function* {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) ) } } @@ -82,7 +96,7 @@ impl VisitWith for GeneratorDeclaration { { try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -91,7 +105,7 @@ impl VisitWith for GeneratorDeclaration { { try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } @@ -115,26 +129,38 @@ impl From for Declaration { #[derive(Clone, Debug, PartialEq)] pub struct GeneratorExpression { pub(crate) name: Option, - parameters: FormalParameterList, - body: FunctionBody, - has_binding_identifier: bool, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) has_binding_identifier: bool, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) name_scope: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl GeneratorExpression { /// Creates a new generator expression. #[inline] #[must_use] - pub const fn new( + pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, has_binding_identifier: bool, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, + name_scope: None, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -165,6 +191,20 @@ impl GeneratorExpression { pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } + + /// Gets the name scope of the generator expression. + #[inline] + #[must_use] + pub const fn name_scope(&self) -> Option<&Scope> { + self.name_scope.as_ref() + } + + /// Gets the scopes of the generator expression. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for GeneratorExpression { @@ -178,7 +218,7 @@ impl ToIndentedString for GeneratorExpression { buf.push_str(&format!( "({}) {}", join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) )); buf @@ -201,7 +241,7 @@ impl VisitWith for GeneratorExpression { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -212,6 +252,6 @@ impl VisitWith for GeneratorExpression { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/function/mod.rs b/core/ast/src/function/mod.rs index d245572f97..2cf0eb26da 100644 --- a/core/ast/src/function/mod.rs +++ b/core/ast/src/function/mod.rs @@ -32,27 +32,107 @@ mod generator; mod ordinary_function; mod parameters; +use std::ops::ControlFlow; + pub use arrow_function::ArrowFunction; pub use async_arrow_function::AsyncArrowFunction; pub use async_function::{AsyncFunctionDeclaration, AsyncFunctionExpression}; pub use async_generator::{AsyncGeneratorDeclaration, AsyncGeneratorExpression}; +use boa_interner::{Interner, ToIndentedString}; pub use class::{ - ClassDeclaration, ClassElement, ClassElementName, ClassExpression, ClassMethodDefinition, - PrivateName, + ClassDeclaration, ClassElement, ClassElementName, ClassExpression, ClassFieldDefinition, + ClassMethodDefinition, PrivateFieldDefinition, PrivateName, StaticBlockBody, }; pub use generator::{GeneratorDeclaration, GeneratorExpression}; pub use ordinary_function::{FunctionDeclaration, FunctionExpression}; pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags}; -use crate::Script; +use crate::{ + try_break, + visitor::{VisitWith, Visitor, VisitorMut}, + StatementList, StatementListItem, +}; /// A Function body. /// -/// Since [`Script`] and `FunctionBody` has the same semantics, this is currently +/// Since `Script` and `FunctionBody` have the same semantics, this is currently /// only an alias of the former. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#prod-FunctionBody -pub type FunctionBody = Script; +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct FunctionBody { + pub(crate) statements: StatementList, +} + +impl FunctionBody { + /// Creates a new `FunctionBody` AST node. + #[must_use] + pub fn new(statements: S, strict: bool) -> Self + where + S: Into>, + { + Self { + statements: StatementList::new(statements.into(), strict), + } + } + + /// Gets the list of statements. + #[inline] + #[must_use] + pub const fn statements(&self) -> &[StatementListItem] { + self.statements.statements() + } + + /// Gets the statement list. + #[inline] + #[must_use] + pub const fn statement_list(&self) -> &StatementList { + &self.statements + } + + /// Get the strict mode. + #[inline] + #[must_use] + pub const fn strict(&self) -> bool { + self.statements.strict() + } +} + +impl From for FunctionBody { + fn from(statements: StatementList) -> Self { + Self { statements } + } +} + +impl ToIndentedString for FunctionBody { + fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { + self.statements.to_indented_string(interner, indentation) + } +} + +impl VisitWith for FunctionBody { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + for statement in &*self.statements { + try_break!(visitor.visit_statement_list_item(statement)); + } + ControlFlow::Continue(()) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + for statement in &mut *self.statements.statements { + try_break!(visitor.visit_statement_list_item_mut(statement)); + } + ControlFlow::Continue(()) + } +} diff --git a/core/ast/src/function/ordinary_function.rs b/core/ast/src/function/ordinary_function.rs index 9ecaa36f43..84f8c4c6e3 100644 --- a/core/ast/src/function/ordinary_function.rs +++ b/core/ast/src/function/ordinary_function.rs @@ -2,7 +2,11 @@ use super::{FormalParameterList, FunctionBody}; use crate::{ block_to_string, expression::{Expression, Identifier}, - join_nodes, try_break, + join_nodes, + operations::{contains, ContainsSymbol}, + scope::{FunctionScopes, Scope}, + scope_analyzer::{analyze_binding_escapes, collect_bindings}, + try_break, visitor::{VisitWith, Visitor, VisitorMut}, Declaration, }; @@ -22,23 +26,27 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct FunctionDeclaration { name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl FunctionDeclaration { /// Creates a new function declaration. #[inline] #[must_use] - pub const fn new( - name: Identifier, - parameters: FormalParameterList, - body: FunctionBody, - ) -> Self { + pub fn new(name: Identifier, parameters: FormalParameterList, body: FunctionBody) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -62,6 +70,13 @@ impl FunctionDeclaration { pub const fn body(&self) -> &FunctionBody { &self.body } + + /// Gets the scopes of the function declaration. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } } impl ToIndentedString for FunctionDeclaration { @@ -70,7 +85,7 @@ impl ToIndentedString for FunctionDeclaration { "function {}({}) {}", interner.resolve_expect(self.name.sym()), join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) ) } } @@ -82,7 +97,7 @@ impl VisitWith for FunctionDeclaration { { try_break!(visitor.visit_identifier(&self.name)); try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -91,7 +106,7 @@ impl VisitWith for FunctionDeclaration { { try_break!(visitor.visit_identifier_mut(&mut self.name)); try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } @@ -115,26 +130,38 @@ impl From for Declaration { #[derive(Clone, Debug, PartialEq)] pub struct FunctionExpression { pub(crate) name: Option, - parameters: FormalParameterList, - body: FunctionBody, - has_binding_identifier: bool, + pub(crate) parameters: FormalParameterList, + pub(crate) body: FunctionBody, + pub(crate) has_binding_identifier: bool, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) name_scope: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scopes: FunctionScopes, } impl FunctionExpression { /// Creates a new function expression. #[inline] #[must_use] - pub const fn new( + pub fn new( name: Option, parameters: FormalParameterList, body: FunctionBody, has_binding_identifier: bool, ) -> Self { + let contains_direct_eval = contains(¶meters, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { name, parameters, body, has_binding_identifier, + name_scope: None, + contains_direct_eval, + scopes: FunctionScopes::default(), } } @@ -165,6 +192,28 @@ impl FunctionExpression { pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } + + /// Gets the name scope of the function expression. + #[inline] + #[must_use] + pub const fn name_scope(&self) -> Option<&Scope> { + self.name_scope.as_ref() + } + + /// Gets the scopes of the function expression. + #[inline] + #[must_use] + pub const fn scopes(&self) -> &FunctionScopes { + &self.scopes + } + + /// Analyze the scope of the function expression. + pub fn analyze_scope(&mut self, strict: bool, scope: &Scope, interner: &Interner) -> bool { + if !collect_bindings(self, strict, false, scope, interner) { + return false; + } + analyze_binding_escapes(self, false, scope.clone(), interner) + } } impl ToIndentedString for FunctionExpression { @@ -178,7 +227,7 @@ impl ToIndentedString for FunctionExpression { buf.push_str(&format!( "({}) {}", join_nodes(interner, self.parameters.as_ref()), - block_to_string(self.body.statements(), interner, indentation) + block_to_string(&self.body.statements, interner, indentation) )); buf @@ -201,7 +250,7 @@ impl VisitWith for FunctionExpression { try_break!(visitor.visit_identifier(ident)); } try_break!(visitor.visit_formal_parameter_list(&self.parameters)); - visitor.visit_script(&self.body) + visitor.visit_function_body(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow @@ -212,6 +261,6 @@ impl VisitWith for FunctionExpression { try_break!(visitor.visit_identifier_mut(ident)); } try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters)); - visitor.visit_script_mut(&mut self.body) + visitor.visit_function_body_mut(&mut self.body) } } diff --git a/core/ast/src/lib.rs b/core/ast/src/lib.rs index e2b19c93df..edaafdc570 100644 --- a/core/ast/src/lib.rs +++ b/core/ast/src/lib.rs @@ -37,10 +37,14 @@ pub mod keyword; pub mod operations; pub mod pattern; pub mod property; +pub mod scope; +pub mod scope_analyzer; pub mod statement; pub mod visitor; -use boa_interner::{Interner, ToIndentedString, ToInternedString}; +use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString}; +use boa_string::{JsStr, JsString}; +use expression::Identifier; pub use self::{ declaration::Declaration, @@ -105,3 +109,28 @@ impl ToStringEscaped for [u16] { .collect() } } + +pub(crate) trait ToJsString { + fn to_js_string(&self, interner: &Interner) -> JsString; +} + +impl ToJsString for Sym { + #[allow(clippy::cast_possible_truncation)] + fn to_js_string(&self, interner: &Interner) -> JsString { + // TODO: Identify latin1 encodeable strings during parsing to avoid this check. + let string = interner.resolve_expect(*self).utf16(); + for c in string { + if u8::try_from(*c).is_err() { + return JsString::from(string); + } + } + let string = string.iter().map(|c| *c as u8).collect::>(); + JsString::from(JsStr::latin1(&string)) + } +} + +impl ToJsString for Identifier { + fn to_js_string(&self, interner: &Interner) -> JsString { + self.sym().to_js_string(interner) + } +} diff --git a/core/ast/src/operations.rs b/core/ast/src/operations.rs index f74c6ac8ba..a4f683b85f 100644 --- a/core/ast/src/operations.rs +++ b/core/ast/src/operations.rs @@ -16,17 +16,17 @@ use crate::{ access::{PrivatePropertyAccess, SuperPropertyAccess}, literal::PropertyDefinition, operator::BinaryInPrivate, - Await, Identifier, OptionalOperationKind, SuperCall, Yield, + Await, Call, Identifier, OptionalOperationKind, SuperCall, Yield, }, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, - ClassElementName, ClassExpression, FormalParameterList, FunctionDeclaration, - FunctionExpression, GeneratorDeclaration, GeneratorExpression, + ClassElementName, ClassExpression, FormalParameterList, FunctionBody, FunctionDeclaration, + FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateFieldDefinition, }, statement::{ iteration::{ForLoopInitializer, IterableLoopInitializer}, - LabelledItem, + LabelledItem, With, }, try_break, visitor::{NodeRef, VisitWith, Visitor}, @@ -61,6 +61,8 @@ pub enum ContainsSymbol { MethodDefinition, /// The `BindingIdentifier` "eval" or "arguments". EvalOrArguments, + /// A direct call to `eval`. + DirectEval, } /// Returns `true` if the node contains the given symbol. @@ -80,6 +82,26 @@ where impl<'ast> Visitor<'ast> for ContainsVisitor { type BreakTy = (); + fn visit_with(&mut self, node: &'ast With) -> ControlFlow { + try_break!(node.expression().visit_with(self)); + node.statement().visit_with(self) + } + + fn visit_call(&mut self, node: &'ast Call) -> ControlFlow { + if self.0 == ContainsSymbol::DirectEval { + if let Expression::Identifier(ident) = node.function().flatten() { + if ident.sym() == Sym::EVAL { + return ControlFlow::Break(()); + } + } + } + try_break!(node.function().visit_with(self)); + for arg in node.args() { + try_break!(arg.visit_with(self)); + } + ControlFlow::Continue(()) + } + fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow { if self.0 == ContainsSymbol::EvalOrArguments && (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS) @@ -179,14 +201,18 @@ where fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { match node { ClassElement::MethodDefinition(m) => { + if self.0 == ContainsSymbol::DirectEval { + return ControlFlow::Continue(()); + } + if let ClassElementName::PropertyName(name) = m.name() { name.visit_with(self) } else { ControlFlow::Continue(()) } } - ClassElement::FieldDefinition(name, _) - | ClassElement::StaticFieldDefinition(name, _) => name.visit_with(self), + ClassElement::FieldDefinition(field) + | ClassElement::StaticFieldDefinition(field) => field.name.visit_with(self), _ => ControlFlow::Continue(()), } } @@ -196,6 +222,10 @@ where node: &'ast PropertyDefinition, ) -> ControlFlow { if let PropertyDefinition::MethodDefinition(m) = node { + if self.0 == ContainsSymbol::DirectEval { + return ControlFlow::Continue(()); + } + if self.0 == ContainsSymbol::MethodDefinition { return ControlFlow::Break(()); } @@ -215,6 +245,7 @@ where ContainsSymbol::SuperCall, ContainsSymbol::Super, ContainsSymbol::This, + ContainsSymbol::DirectEval, ] .contains(&self.0) { @@ -234,6 +265,7 @@ where ContainsSymbol::SuperCall, ContainsSymbol::Super, ContainsSymbol::This, + ContainsSymbol::DirectEval, ] .contains(&self.0) { @@ -401,7 +433,7 @@ where /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper #[must_use] #[inline] -pub fn has_direct_super_new(params: &FormalParameterList, body: &Script) -> bool { +pub fn has_direct_super_new(params: &FormalParameterList, body: &FunctionBody) -> bool { contains(params, ContainsSymbol::SuperCall) || contains(body, ContainsSymbol::SuperCall) } @@ -600,6 +632,11 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> ControlFlow::Continue(()) } + fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { + top_level_lexicals(node.statement_list(), self.0); + ControlFlow::Continue(()) + } + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { // ModuleItem : ImportDeclaration @@ -655,72 +692,72 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> &mut self, node: &'ast FunctionExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_function_declaration( &mut self, node: &'ast FunctionDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_function_expression( &mut self, node: &'ast AsyncFunctionExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_function_declaration( &mut self, node: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_generator_expression( &mut self, node: &'ast GeneratorExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_generator_declaration( &mut self, node: &'ast GeneratorDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_generator_expression( &mut self, node: &'ast AsyncGeneratorExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_generator_declaration( &mut self, node: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_arrow_function( &mut self, node: &'ast AsyncArrowFunction, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { - if let ClassElement::StaticBlock(body) = node { - self.visit_script(body); + if let ClassElement::StaticBlock(block) = node { + self.visit_function_body(&block.body); } ControlFlow::Continue(()) } @@ -787,6 +824,11 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { ControlFlow::Continue(()) } + fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { + top_level_vars(node.statement_list(), self.0); + ControlFlow::Continue(()) + } + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { // ModuleItem : ImportDeclaration @@ -898,7 +940,7 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { self.visit(node.body()) } - fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow { + fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit(node.statement()) } @@ -933,61 +975,61 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { &mut self, node: &'ast FunctionExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_function_declaration( &mut self, node: &'ast FunctionDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_function_expression( &mut self, node: &'ast AsyncFunctionExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_function_declaration( &mut self, node: &'ast AsyncFunctionDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_generator_expression( &mut self, node: &'ast GeneratorExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_generator_declaration( &mut self, node: &'ast GeneratorDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_generator_expression( &mut self, node: &'ast AsyncGeneratorExpression, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_async_generator_declaration( &mut self, node: &'ast AsyncGeneratorDeclaration, ) -> ControlFlow { - self.visit_script(node.body()) + self.visit_function_body(node.body()) } fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { - if let ClassElement::StaticBlock(body) = node { - self.visit_script(body); + if let ClassElement::StaticBlock(block) = node { + self.visit_function_body(&block.body); } node.visit_with(self) } @@ -1138,7 +1180,7 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { names.push(name.description()); } } - ClassElement::PrivateFieldDefinition(name, _) + ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. }) | ClassElement::PrivateStaticFieldDefinition(name, _) => { names.push(name.description()); } @@ -1161,21 +1203,21 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { try_break!(visitor.visit(m.parameters())); try_break!(visitor.visit(m.body())); } - ClassElement::FieldDefinition(name, expression) - | ClassElement::StaticFieldDefinition(name, expression) => { - try_break!(visitor.visit(name)); - if let Some(expression) = expression { + ClassElement::FieldDefinition(field) + | ClassElement::StaticFieldDefinition(field) => { + try_break!(visitor.visit(&field.name)); + if let Some(expression) = &field.field { try_break!(visitor.visit(expression)); } } - ClassElement::PrivateFieldDefinition(_, expression) - | ClassElement::PrivateStaticFieldDefinition(_, expression) => { - if let Some(expression) = expression { + ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { field, .. }) + | ClassElement::PrivateStaticFieldDefinition(_, field) => { + if let Some(expression) = field { try_break!(visitor.visit(expression)); } } - ClassElement::StaticBlock(statement_list) => { - try_break!(visitor.visit(statement_list)); + ClassElement::StaticBlock(block) => { + try_break!(visitor.visit(&block.body)); } } } @@ -1199,7 +1241,7 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { names.push(name.description()); } } - ClassElement::PrivateFieldDefinition(name, _) + ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { name, .. }) | ClassElement::PrivateStaticFieldDefinition(name, _) => { names.push(name.description()); } @@ -1222,21 +1264,21 @@ impl<'ast> Visitor<'ast> for AllPrivateIdentifiersValidVisitor { try_break!(visitor.visit(m.parameters())); try_break!(visitor.visit(m.body())); } - ClassElement::FieldDefinition(name, expression) - | ClassElement::StaticFieldDefinition(name, expression) => { - try_break!(visitor.visit(name)); - if let Some(expression) = expression { + ClassElement::FieldDefinition(field) + | ClassElement::StaticFieldDefinition(field) => { + try_break!(visitor.visit(&field.name)); + if let Some(expression) = &field.field { try_break!(visitor.visit(expression)); } } - ClassElement::PrivateFieldDefinition(_, expression) - | ClassElement::PrivateStaticFieldDefinition(_, expression) => { - if let Some(expression) = expression { + ClassElement::PrivateFieldDefinition(PrivateFieldDefinition { field, .. }) + | ClassElement::PrivateStaticFieldDefinition(_, field) => { + if let Some(expression) = field { try_break!(visitor.visit(expression)); } } - ClassElement::StaticBlock(statement_list) => { - try_break!(visitor.visit(statement_list)); + ClassElement::StaticBlock(block) => { + try_break!(visitor.visit(&block.body)); } } } @@ -1755,6 +1797,12 @@ impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_, 'ast> { TopLevelLexicallyScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) } + fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { + // 1. Return TopLevelVarScopedDeclarations of StatementList. + TopLevelLexicallyScopedDeclarationsVisitor(self.0) + .visit_statement_list(node.statement_list()) + } + fn visit_export_declaration( &mut self, node: &'ast ExportDeclaration, @@ -1974,6 +2022,11 @@ impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statements()) } + fn visit_function_body(&mut self, node: &'ast FunctionBody) -> ControlFlow { + // 1. Return TopLevelVarScopedDeclarations of StatementList. + TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statement_list()) + } + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { match node { Statement::Block(s) => self.visit(s), @@ -2074,7 +2127,7 @@ impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { ControlFlow::Continue(()) } - fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow { + fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit(node.statement()); ControlFlow::Continue(()) } @@ -2342,7 +2395,7 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> { self.visit(node.body()); if let Some(ForLoopInitializer::Lexical(node)) = node.init() { - let bound_names = bound_names(node); + let bound_names = bound_names(&node.declaration); self.0.retain(|name| !bound_names.contains(name)); } @@ -2395,7 +2448,7 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> { ControlFlow::Continue(()) } - fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow { + fn visit_with(&mut self, node: &'ast With) -> ControlFlow { self.visit(node.statement()) } } diff --git a/core/ast/src/scope.rs b/core/ast/src/scope.rs new file mode 100644 index 0000000000..621189ee79 --- /dev/null +++ b/core/ast/src/scope.rs @@ -0,0 +1,529 @@ +//! This module implements the binding scope for various AST nodes. +//! +//! Scopes are used to track the bindings of identifiers in the AST. + +use boa_string::JsString; +use rustc_hash::FxHashMap; +use std::{cell::RefCell, fmt::Debug, rc::Rc}; + +#[derive(Clone, Debug, PartialEq)] +#[allow(clippy::struct_excessive_bools)] +struct Binding { + index: u32, + mutable: bool, + lex: bool, + strict: bool, + escapes: bool, +} + +/// A scope maps bound identifiers to their binding positions. +/// +/// It can be either a global scope or a function scope or a declarative scope. +#[derive(Clone, PartialEq)] +pub struct Scope { + inner: Rc, +} + +impl Debug for Scope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Scope") + .field("outer", &self.inner.outer) + .field("index", &self.inner.index) + .field("bindings", &self.inner.bindings) + .field("function", &self.inner.function) + .finish() + } +} + +impl Default for Scope { + fn default() -> Self { + Self::new_global() + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Scope { + fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self::new_global()) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) struct Inner { + outer: Option, + index: u32, + bindings: RefCell>, + function: bool, +} + +impl Scope { + /// Creates a new global scope. + #[must_use] + pub fn new_global() -> Self { + Self { + inner: Rc::new(Inner { + outer: None, + index: 0, + bindings: RefCell::default(), + function: true, + }), + } + } + + /// Creates a new scope. + #[must_use] + pub fn new(parent: Self, function: bool) -> Self { + let index = parent.inner.index + 1; + Self { + inner: Rc::new(Inner { + outer: Some(parent), + index, + bindings: RefCell::default(), + function, + }), + } + } + + /// Marks all bindings in this scope as escaping. + pub fn escape_all_bindings(&self) { + for binding in self.inner.bindings.borrow_mut().values_mut() { + binding.escapes = true; + } + } + + /// Check if the scope has a lexical binding with the given name. + #[must_use] + pub fn has_lex_binding(&self, name: &JsString) -> bool { + self.inner + .bindings + .borrow() + .get(name) + .map_or(false, |binding| binding.lex) + } + + /// Check if the scope has a binding with the given name. + #[must_use] + pub fn has_binding(&self, name: &JsString) -> bool { + self.inner.bindings.borrow().contains_key(name) + } + + /// Get the binding locator for a binding with the given name. + /// Fall back to the global scope if the binding is not found. + #[must_use] + pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference { + if let Some(binding) = self.inner.bindings.borrow().get(&name) { + IdentifierReference::new( + BindingLocator::declarative(name, self.inner.index, binding.index), + binding.lex, + binding.escapes, + ) + } else if let Some(outer) = &self.inner.outer { + outer.get_identifier_reference(name) + } else { + IdentifierReference::new(BindingLocator::global(name), false, true) + } + } + + /// Returns the number of bindings in this scope. + #[must_use] + #[allow(clippy::cast_possible_truncation)] + pub fn num_bindings(&self) -> u32 { + self.inner.bindings.borrow().len() as u32 + } + + /// Returns the index of this scope. + #[must_use] + pub fn scope_index(&self) -> u32 { + self.inner.index + } + + /// Check if the scope is a function scope. + #[must_use] + pub fn is_function(&self) -> bool { + self.inner.function + } + + /// Check if the scope is a global scope. + #[must_use] + pub fn is_global(&self) -> bool { + self.inner.outer.is_none() + } + + /// Get the locator for a binding name. + #[must_use] + pub fn get_binding(&self, name: &JsString) -> Option { + self.inner.bindings.borrow().get(name).map(|binding| { + BindingLocator::declarative(name.clone(), self.inner.index, binding.index) + }) + } + + /// Get the locator for a binding name. + #[must_use] + pub fn get_binding_reference(&self, name: &JsString) -> Option { + self.inner.bindings.borrow().get(name).map(|binding| { + IdentifierReference::new( + BindingLocator::declarative(name.clone(), self.inner.index, binding.index), + binding.lex, + binding.escapes, + ) + }) + } + + /// Simulate a binding access. + /// + /// - If the binding access crosses a function border, the binding is marked as escaping. + /// - If the binding access is in an eval or with scope, the binding is marked as escaping. + pub fn access_binding(&self, name: &JsString, eval_or_with: bool) { + let mut crossed_function_border = false; + let mut current = self; + loop { + if let Some(binding) = current.inner.bindings.borrow_mut().get_mut(name) { + if crossed_function_border || eval_or_with { + binding.escapes = true; + } + return; + } + if let Some(outer) = ¤t.inner.outer { + if current.inner.function { + crossed_function_border = true; + } + current = outer; + } else { + return; + } + } + } + + /// Creates a mutable binding. + #[must_use] + #[allow(clippy::cast_possible_truncation)] + pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator { + let binding_index = self.inner.bindings.borrow().len() as u32; + self.inner.bindings.borrow_mut().insert( + name.clone(), + Binding { + index: binding_index, + mutable: true, + lex: !function_scope, + strict: false, + escapes: self.is_global(), + }, + ); + BindingLocator::declarative(name, self.inner.index, binding_index) + } + + /// Crate an immutable binding. + #[allow(clippy::cast_possible_truncation)] + pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) { + let binding_index = self.inner.bindings.borrow().len() as u32; + self.inner.bindings.borrow_mut().insert( + name, + Binding { + index: binding_index, + mutable: false, + lex: true, + strict, + escapes: self.is_global(), + }, + ); + } + + /// Return the binding locator for a mutable binding. + /// + /// # Errors + /// Returns an error if the binding is not mutable or does not exist. + pub fn set_mutable_binding( + &self, + name: JsString, + ) -> Result { + Ok(match self.inner.bindings.borrow().get(&name) { + Some(binding) if binding.mutable => IdentifierReference::new( + BindingLocator::declarative(name, self.inner.index, binding.index), + binding.lex, + binding.escapes, + ), + Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable), + Some(_) => return Err(BindingLocatorError::Silent), + None => self.inner.outer.as_ref().map_or_else( + || { + Ok(IdentifierReference::new( + BindingLocator::global(name.clone()), + false, + true, + )) + }, + |outer| outer.set_mutable_binding(name.clone()), + )?, + }) + } + + #[cfg(feature = "annex-b")] + /// Return the binding locator for a set operation on an existing var binding. + /// + /// # Errors + /// Returns an error if the binding is not mutable or does not exist. + pub fn set_mutable_binding_var( + &self, + name: JsString, + ) -> Result { + if !self.is_function() { + return self.inner.outer.as_ref().map_or_else( + || { + Ok(IdentifierReference::new( + BindingLocator::global(name.clone()), + false, + true, + )) + }, + |outer| outer.set_mutable_binding_var(name.clone()), + ); + } + + Ok(match self.inner.bindings.borrow().get(&name) { + Some(binding) if binding.mutable => IdentifierReference::new( + BindingLocator::declarative(name, self.inner.index, binding.index), + binding.lex, + binding.escapes, + ), + Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable), + Some(_) => return Err(BindingLocatorError::Silent), + None => self.inner.outer.as_ref().map_or_else( + || { + Ok(IdentifierReference::new( + BindingLocator::global(name.clone()), + false, + true, + )) + }, + |outer| outer.set_mutable_binding_var(name.clone()), + )?, + }) + } + + /// Gets the outer scope of this scope. + #[must_use] + pub fn outer(&self) -> Option { + self.inner.outer.clone() + } +} + +/// A reference to an identifier in a scope. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct IdentifierReference { + locator: BindingLocator, + lexical: bool, + escapes: bool, +} + +impl IdentifierReference { + /// Create a new identifier reference. + pub(crate) fn new(locator: BindingLocator, lexical: bool, escapes: bool) -> Self { + Self { + locator, + lexical, + escapes, + } + } + + /// Get the binding locator for this identifier reference. + #[must_use] + pub fn locator(&self) -> BindingLocator { + self.locator.clone() + } + + /// Returns if the binding can be function local. + #[must_use] + pub fn local(&self) -> bool { + self.locator.scope > 0 && !self.escapes + } + + /// Check if this identifier reference is lexical. + #[must_use] + pub fn is_lexical(&self) -> bool { + self.lexical + } +} + +/// A binding locator contains all information about a binding that is needed to resolve it at runtime. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct BindingLocator { + /// Name of the binding. + name: JsString, + + /// Scope of the binding. + /// - 0: Global object + /// - 1: Global declarative scope + /// - n: Stack scope at index n - 2 + scope: u32, + + /// Index of the binding in the scope. + binding_index: u32, +} + +impl BindingLocator { + /// Creates a new declarative binding locator that has knows indices. + pub(crate) const fn declarative(name: JsString, scope_index: u32, binding_index: u32) -> Self { + Self { + name, + scope: scope_index + 1, + binding_index, + } + } + + /// Creates a binding locator that indicates that the binding is on the global object. + pub(super) const fn global(name: JsString) -> Self { + Self { + name, + scope: 0, + binding_index: 0, + } + } + + /// Returns the name of the binding. + #[must_use] + pub const fn name(&self) -> &JsString { + &self.name + } + + /// Returns if the binding is located on the global object. + #[must_use] + pub const fn is_global(&self) -> bool { + self.scope == 0 + } + + /// Returns the scope of the binding. + #[must_use] + pub fn scope(&self) -> BindingLocatorScope { + match self.scope { + 0 => BindingLocatorScope::GlobalObject, + 1 => BindingLocatorScope::GlobalDeclarative, + n => BindingLocatorScope::Stack(n - 2), + } + } + + /// Sets the scope of the binding. + pub fn set_scope(&mut self, scope: BindingLocatorScope) { + self.scope = match scope { + BindingLocatorScope::GlobalObject => 0, + BindingLocatorScope::GlobalDeclarative => 1, + BindingLocatorScope::Stack(index) => index + 2, + }; + } + + /// Returns the binding index of the binding. + #[must_use] + pub const fn binding_index(&self) -> u32 { + self.binding_index + } + + /// Sets the binding index of the binding. + pub fn set_binding_index(&mut self, index: u32) { + self.binding_index = index; + } +} + +/// Action that is returned when a fallible binding operation. +#[derive(Copy, Clone, Debug)] +pub enum BindingLocatorError { + /// Trying to mutate immutable binding, + MutateImmutable, + + /// Indicates that any action is silently ignored. + Silent, +} + +/// The scope in which a binding is located. +#[derive(Clone, Copy, Debug)] +pub enum BindingLocatorScope { + /// The binding is located on the global object. + GlobalObject, + + /// The binding is located in the global declarative scope. + GlobalDeclarative, + + /// The binding is located in the scope stack at the given index. + Stack(u32), +} + +/// A collection of function scopes. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct FunctionScopes { + pub(crate) function_scope: Scope, + pub(crate) parameters_eval_scope: Option, + pub(crate) parameters_scope: Option, + pub(crate) lexical_scope: Option, +} + +impl FunctionScopes { + /// Returns the function scope for this function. + #[must_use] + pub fn function_scope(&self) -> &Scope { + &self.function_scope + } + + /// Returns the parameters eval scope for this function. + #[must_use] + pub fn parameters_eval_scope(&self) -> Option<&Scope> { + self.parameters_eval_scope.as_ref() + } + + /// Returns the parameters scope for this function. + #[must_use] + pub fn parameters_scope(&self) -> Option<&Scope> { + self.parameters_scope.as_ref() + } + + /// Returns the lexical scope for this function. + #[must_use] + pub fn lexical_scope(&self) -> Option<&Scope> { + self.lexical_scope.as_ref() + } + + /// Returns the effective paramter scope for this function. + pub(crate) fn parameter_scope(&self) -> Scope { + if let Some(parameters_eval_scope) = &self.parameters_eval_scope { + return parameters_eval_scope.clone(); + } + self.function_scope.clone() + } + + /// Returns the effective body scope for this function. + pub(crate) fn body_scope(&self) -> Scope { + if let Some(lexical_scope) = &self.lexical_scope { + return lexical_scope.clone(); + } + if let Some(parameters_scope) = &self.parameters_scope { + return parameters_scope.clone(); + } + if let Some(parameters_eval_scope) = &self.parameters_eval_scope { + return parameters_eval_scope.clone(); + } + self.function_scope.clone() + } + + /// Marks all bindings in all scopes as escaping. + pub(crate) fn escape_all_bindings(&self) { + self.function_scope.escape_all_bindings(); + if let Some(parameters_eval_scope) = &self.parameters_eval_scope { + parameters_eval_scope.escape_all_bindings(); + } + if let Some(parameters_scope) = &self.parameters_scope { + parameters_scope.escape_all_bindings(); + } + if let Some(lexical_scope) = &self.lexical_scope { + lexical_scope.escape_all_bindings(); + } + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for FunctionScopes { + fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self { + function_scope: Scope::new_global(), + parameters_eval_scope: None, + parameters_scope: None, + lexical_scope: None, + }) + } +} diff --git a/core/ast/src/scope_analyzer.rs b/core/ast/src/scope_analyzer.rs new file mode 100644 index 0000000000..e40534e67a --- /dev/null +++ b/core/ast/src/scope_analyzer.rs @@ -0,0 +1,1992 @@ +//! This module implements the scope analysis for the AST. +//! +//! The scope analysis is done in two steps: +//! 1. Collecting bindings: This step collects all the bindings in the AST and fills the scopes with them. +//! 2. Analyzing binding escapes: This step analyzes if the bindings escape their function scopes. + +#[cfg(feature = "annex-b")] +use crate::operations::annex_b_function_declarations_names; +use crate::{ + declaration::{Binding, ExportDeclaration, LexicalDeclaration, VariableList}, + expression::{literal::ObjectMethodDefinition, Identifier}, + function::{ + ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, + AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, + ClassExpression, FormalParameterList, FunctionBody, FunctionDeclaration, + FunctionExpression, GeneratorDeclaration, GeneratorExpression, + }, + operations::{ + bound_names, lexically_declared_names, lexically_scoped_declarations, var_declared_names, + var_scoped_declarations, LexicallyScopedDeclaration, VarScopedDeclaration, + }, + property::PropertyName, + scope::{FunctionScopes, IdentifierReference, Scope}, + statement::{ + iteration::{ForLoopInitializer, IterableLoopInitializer}, + Block, Catch, ForInLoop, ForLoop, ForOfLoop, Switch, With, + }, + try_break, + visitor::{NodeRef, NodeRefMut, VisitorMut}, + Declaration, Module, Script, StatementListItem, ToJsString, +}; +use boa_interner::{Interner, Sym}; +use rustc_hash::FxHashMap; +use std::ops::ControlFlow; + +/// Collect bindings and fill the scopes with them. +#[must_use] +pub(crate) fn collect_bindings<'a, N>( + node: &'a mut N, + strict: bool, + eval: bool, + scope: &Scope, + interner: &Interner, +) -> bool +where + &'a mut N: Into>, +{ + let mut visitor = BindingCollectorVisitor { + strict, + eval, + scope: scope.clone(), + interner, + }; + !visitor.visit(node).is_break() +} + +/// Analyze if bindings escape their function scopes. +#[must_use] +pub(crate) fn analyze_binding_escapes<'a, N>( + node: &'a mut N, + in_eval: bool, + scope: Scope, + interner: &Interner, +) -> bool +where + &'a mut N: Into>, +{ + let mut visitor = BindingEscapeAnalyzer { + scope, + direct_eval: in_eval, + with: false, + interner, + }; + !visitor.visit(node.into()).is_break() +} + +struct BindingEscapeAnalyzer<'interner> { + scope: Scope, + direct_eval: bool, + with: bool, + interner: &'interner Interner, +} + +impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> { + type BreakTy = &'static str; + + fn visit_identifier_mut(&mut self, node: &'ast mut Identifier) -> ControlFlow { + let name = node.to_js_string(self.interner); + self.scope + .access_binding(&name, self.direct_eval || self.with); + ControlFlow::Continue(()) + } + + fn visit_block_mut(&mut self, node: &'ast mut Block) -> ControlFlow { + let direct_eval_old = self.direct_eval; + self.direct_eval = node.contains_direct_eval || self.direct_eval; + if let Some(scope) = &mut node.scope { + if self.direct_eval { + scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, scope); + } + + try_break!(self.visit_statement_list_mut(&mut node.statements)); + if let Some(scope) = &mut node.scope { + std::mem::swap(&mut self.scope, scope); + } + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } + + fn visit_switch_mut(&mut self, node: &'ast mut Switch) -> ControlFlow { + try_break!(self.visit_expression_mut(&mut node.val)); + let direct_eval_old = self.direct_eval; + self.direct_eval = node.contains_direct_eval || self.direct_eval; + if let Some(scope) = &mut node.scope { + if self.direct_eval { + scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, scope); + } + for case in &mut node.cases { + try_break!(self.visit_case_mut(case)); + } + if let Some(scope) = &mut node.scope { + std::mem::swap(&mut self.scope, scope); + } + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } + + fn visit_with_mut(&mut self, node: &'ast mut With) -> ControlFlow { + let with = self.with; + self.with = true; + if self.direct_eval { + node.scope.escape_all_bindings(); + } + try_break!(self.visit_expression_mut(&mut node.expression)); + std::mem::swap(&mut self.scope, &mut node.scope); + try_break!(self.visit_statement_mut(&mut node.statement)); + std::mem::swap(&mut self.scope, &mut node.scope); + self.with = with; + ControlFlow::Continue(()) + } + + fn visit_catch_mut(&mut self, node: &'ast mut Catch) -> ControlFlow { + let direct_eval_old = self.direct_eval; + self.direct_eval = node.contains_direct_eval || self.direct_eval; + if self.direct_eval { + node.scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, &mut node.scope); + if let Some(binding) = &mut node.parameter { + try_break!(self.visit_binding_mut(binding)); + } + try_break!(self.visit_block_mut(&mut node.block)); + std::mem::swap(&mut self.scope, &mut node.scope); + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } + + fn visit_for_loop_mut(&mut self, node: &'ast mut ForLoop) -> ControlFlow { + let direct_eval_old = self.direct_eval; + self.direct_eval = node.inner.contains_direct_eval || self.direct_eval; + if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init { + if self.direct_eval { + decl.scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, &mut decl.scope); + } + if let Some(init) = &mut node.inner.init { + try_break!(self.visit_for_loop_initializer_mut(init)); + } + if let Some(condition) = &mut node.inner.condition { + try_break!(self.visit_expression_mut(condition)); + } + if let Some(final_expr) = &mut node.inner.final_expr { + try_break!(self.visit_expression_mut(final_expr)); + } + try_break!(self.visit_statement_mut(&mut node.inner.body)); + if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init { + std::mem::swap(&mut self.scope, &mut decl.scope); + } + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } + + fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { + let direct_eval_old = self.direct_eval; + if let Some(scope) = &mut node.target_scope { + self.direct_eval = node.target_contains_direct_eval || self.direct_eval; + if self.direct_eval { + scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, scope); + } + try_break!(self.visit_expression_mut(&mut node.target)); + if let Some(scope) = &mut node.target_scope { + self.direct_eval = direct_eval_old; + std::mem::swap(&mut self.scope, scope); + } + if let Some(scope) = &mut node.scope { + self.direct_eval = node.contains_direct_eval || self.direct_eval; + if self.direct_eval { + scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, scope); + } + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.initializer)); + try_break!(self.visit_statement_mut(&mut node.body)); + if let Some(scope) = &mut node.scope { + std::mem::swap(&mut self.scope, scope); + } + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } + + fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { + let direct_eval_old = self.direct_eval; + if let Some(scope) = &mut node.iterable_scope { + self.direct_eval = node.iterable_contains_direct_eval || self.direct_eval; + if self.direct_eval { + scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, scope); + } + try_break!(self.visit_expression_mut(&mut node.iterable)); + if let Some(scope) = &mut node.iterable_scope { + self.direct_eval = direct_eval_old; + std::mem::swap(&mut self.scope, scope); + } + if let Some(scope) = &mut node.scope { + self.direct_eval = node.contains_direct_eval || self.direct_eval; + if self.direct_eval { + scope.escape_all_bindings(); + } + std::mem::swap(&mut self.scope, scope); + } + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.init)); + try_break!(self.visit_statement_mut(&mut node.body)); + if let Some(scope) = &mut node.scope { + std::mem::swap(&mut self.scope, scope); + } + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } + + fn visit_function_declaration_mut( + &mut self, + node: &'ast mut FunctionDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_generator_declaration_mut( + &mut self, + node: &'ast mut GeneratorDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_async_function_declaration_mut( + &mut self, + node: &'ast mut AsyncFunctionDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_async_generator_declaration_mut( + &mut self, + node: &'ast mut AsyncGeneratorDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_function_expression_mut( + &mut self, + node: &'ast mut FunctionExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_generator_expression_mut( + &mut self, + node: &'ast mut GeneratorExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_async_function_expression_mut( + &mut self, + node: &'ast mut AsyncFunctionExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_async_generator_expression_mut( + &mut self, + node: &'ast mut AsyncGeneratorExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_arrow_function_mut( + &mut self, + node: &'ast mut ArrowFunction, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_async_arrow_function_mut( + &mut self, + node: &'ast mut AsyncArrowFunction, + ) -> ControlFlow { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_class_declaration_mut( + &mut self, + node: &'ast mut ClassDeclaration, + ) -> ControlFlow { + node.name_scope.escape_all_bindings(); + std::mem::swap(&mut self.scope, &mut node.name_scope); + if let Some(super_ref) = &mut node.super_ref { + try_break!(self.visit_expression_mut(super_ref)); + } + if let Some(constructor) = &mut node.constructor { + try_break!(self.visit_function_expression_mut(constructor)); + } + for element in &mut *node.elements { + try_break!(self.visit_class_element_mut(element)); + } + std::mem::swap(&mut self.scope, &mut node.name_scope); + ControlFlow::Continue(()) + } + + fn visit_class_expression_mut( + &mut self, + node: &'ast mut ClassExpression, + ) -> ControlFlow { + if let Some(name_scope) = &mut node.name_scope { + if self.direct_eval { + name_scope.escape_all_bindings(); + } + name_scope.escape_all_bindings(); + std::mem::swap(&mut self.scope, name_scope); + } + if let Some(super_ref) = &mut node.super_ref { + try_break!(self.visit_expression_mut(super_ref)); + } + if let Some(constructor) = &mut node.constructor { + try_break!(self.visit_function_expression_mut(constructor)); + } + for element in &mut *node.elements { + try_break!(self.visit_class_element_mut(element)); + } + if let Some(name_scope) = &mut node.name_scope { + std::mem::swap(&mut self.scope, name_scope); + } + ControlFlow::Continue(()) + } + + fn visit_class_element_mut( + &mut self, + node: &'ast mut ClassElement, + ) -> ControlFlow { + if let ClassElement::MethodDefinition(node) = node { + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } else { + ControlFlow::Continue(()) + } + } + + fn visit_object_method_definition_mut( + &mut self, + node: &'ast mut ObjectMethodDefinition, + ) -> ControlFlow { + try_break!(self.visit_property_name_mut(&mut node.name)); + self.visit_function_like( + &mut node.parameters, + &mut node.body, + &node.scopes, + node.contains_direct_eval, + ) + } + + fn visit_export_declaration_mut( + &mut self, + node: &'ast mut ExportDeclaration, + ) -> ControlFlow { + match node { + ExportDeclaration::ReExport { specifier, kind } => { + try_break!(self.visit_module_specifier_mut(specifier)); + self.visit_re_export_kind_mut(kind) + } + ExportDeclaration::List(list) => { + for item in &mut **list { + try_break!(self.visit_export_specifier_mut(item)); + } + ControlFlow::Continue(()) + } + ExportDeclaration::VarStatement(var) => self.visit_var_declaration_mut(var), + ExportDeclaration::Declaration(decl) => self.visit_declaration_mut(decl), + ExportDeclaration::DefaultFunctionDeclaration(f) => { + self.visit_function_declaration_mut(f) + } + ExportDeclaration::DefaultGeneratorDeclaration(g) => { + self.visit_generator_declaration_mut(g) + } + ExportDeclaration::DefaultAsyncFunctionDeclaration(af) => { + self.visit_async_function_declaration_mut(af) + } + ExportDeclaration::DefaultAsyncGeneratorDeclaration(ag) => { + self.visit_async_generator_declaration_mut(ag) + } + ExportDeclaration::DefaultClassDeclaration(c) => self.visit_class_declaration_mut(c), + ExportDeclaration::DefaultAssignmentExpression(expr) => { + let name = Sym::DEFAULT_EXPORT.to_js_string(self.interner); + drop(self.scope.create_mutable_binding(name.clone(), false)); + self.scope.access_binding(&name, true); + self.visit_expression_mut(expr) + } + } + } + + fn visit_module_mut(&mut self, node: &'ast mut Module) -> ControlFlow { + let mut scope = node.scope.clone(); + scope.escape_all_bindings(); + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_module_item_list_mut(&mut node.items)); + std::mem::swap(&mut self.scope, &mut scope); + ControlFlow::Continue(()) + } +} + +impl BindingEscapeAnalyzer<'_> { + fn visit_function_like( + &mut self, + parameters: &mut FormalParameterList, + body: &mut FunctionBody, + scopes: &FunctionScopes, + contains_direct_eval: bool, + ) -> ControlFlow<&'static str> { + let direct_eval_old = self.direct_eval; + self.direct_eval = contains_direct_eval || self.direct_eval; + if self.direct_eval { + scopes.escape_all_bindings(); + } + let mut scope = scopes.parameter_scope(); + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_formal_parameter_list_mut(parameters)); + std::mem::swap(&mut self.scope, &mut scope); + scope = scopes.body_scope(); + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_function_body_mut(body)); + std::mem::swap(&mut self.scope, &mut scope); + self.direct_eval = direct_eval_old; + ControlFlow::Continue(()) + } +} + +struct BindingCollectorVisitor<'interner> { + strict: bool, + eval: bool, + scope: Scope, + interner: &'interner Interner, +} + +impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { + type BreakTy = &'static str; + + fn visit_function_declaration_mut( + &mut self, + node: &'ast mut FunctionDeclaration, + ) -> ControlFlow { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + + fn visit_generator_declaration_mut( + &mut self, + node: &'ast mut GeneratorDeclaration, + ) -> ControlFlow { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + + fn visit_async_function_declaration_mut( + &mut self, + node: &'ast mut AsyncFunctionDeclaration, + ) -> ControlFlow { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + + fn visit_async_generator_declaration_mut( + &mut self, + node: &'ast mut AsyncGeneratorDeclaration, + ) -> ControlFlow { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + + fn visit_function_expression_mut( + &mut self, + node: &'ast mut FunctionExpression, + ) -> ControlFlow { + let name = if node.has_binding_identifier { + node.name() + } else { + None + }; + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + name, + &mut node.name_scope, + strict, + false, + ) + } + + fn visit_generator_expression_mut( + &mut self, + node: &'ast mut GeneratorExpression, + ) -> ControlFlow { + let name = if node.has_binding_identifier { + node.name() + } else { + None + }; + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + name, + &mut node.name_scope, + strict, + false, + ) + } + + fn visit_async_function_expression_mut( + &mut self, + node: &'ast mut AsyncFunctionExpression, + ) -> ControlFlow { + let name = if node.has_binding_identifier { + node.name() + } else { + None + }; + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + name, + &mut node.name_scope, + strict, + false, + ) + } + + fn visit_async_generator_expression_mut( + &mut self, + node: &'ast mut AsyncGeneratorExpression, + ) -> ControlFlow { + let name = if node.has_binding_identifier { + node.name() + } else { + None + }; + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + name, + &mut node.name_scope, + strict, + false, + ) + } + + fn visit_arrow_function_mut( + &mut self, + node: &'ast mut ArrowFunction, + ) -> ControlFlow { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + true, + ) + } + + fn visit_async_arrow_function_mut( + &mut self, + node: &'ast mut AsyncArrowFunction, + ) -> ControlFlow { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + true, + ) + } + + fn visit_class_declaration_mut( + &mut self, + node: &'ast mut ClassDeclaration, + ) -> ControlFlow { + let mut name_scope = Scope::new(self.scope.clone(), false); + let name = node.name().to_js_string(self.interner); + name_scope.create_immutable_binding(name, true); + std::mem::swap(&mut self.scope, &mut name_scope); + if let Some(super_ref) = &mut node.super_ref { + try_break!(self.visit_expression_mut(super_ref)); + } + if let Some(constructor) = &mut node.constructor { + try_break!(self.visit_function_expression_mut(constructor)); + } + for element in &mut *node.elements { + try_break!(self.visit_class_element_mut(element)); + } + std::mem::swap(&mut self.scope, &mut name_scope); + node.name_scope = name_scope; + ControlFlow::Continue(()) + } + + fn visit_class_expression_mut( + &mut self, + node: &'ast mut ClassExpression, + ) -> ControlFlow { + let mut name_scope = None; + if let Some(name) = node.name { + if node.name_scope.is_some() { + let mut scope = Scope::new(self.scope.clone(), false); + let name = name.to_js_string(self.interner); + scope.create_immutable_binding(name, true); + node.name_scope = Some(scope.clone()); + std::mem::swap(&mut self.scope, &mut scope); + name_scope = Some(scope); + } + } + if let Some(super_ref) = &mut node.super_ref { + try_break!(self.visit_expression_mut(super_ref)); + } + if let Some(constructor) = &mut node.constructor { + try_break!(self.visit_function_expression_mut(constructor)); + } + for element in &mut *node.elements { + try_break!(self.visit_class_element_mut(element)); + } + if let Some(mut scope) = name_scope { + std::mem::swap(&mut self.scope, &mut scope); + } + ControlFlow::Continue(()) + } + + fn visit_class_element_mut( + &mut self, + node: &'ast mut ClassElement, + ) -> ControlFlow { + match node { + ClassElement::MethodDefinition(node) => { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { + try_break!(self.visit_property_name_mut(&mut field.name)); + let mut scope = Scope::new(self.scope.clone(), true); + std::mem::swap(&mut self.scope, &mut scope); + if let Some(e) = &mut field.field { + try_break!(self.visit_expression_mut(e)); + } + std::mem::swap(&mut self.scope, &mut scope); + field.scope = scope; + ControlFlow::Continue(()) + } + ClassElement::PrivateFieldDefinition(field) => { + let mut scope = Scope::new(self.scope.clone(), true); + std::mem::swap(&mut self.scope, &mut scope); + if let Some(e) = &mut field.field { + try_break!(self.visit_expression_mut(e)); + } + std::mem::swap(&mut self.scope, &mut scope); + field.scope = scope; + ControlFlow::Continue(()) + } + ClassElement::PrivateStaticFieldDefinition(_, e) => { + if let Some(e) = e { + try_break!(self.visit_expression_mut(e)); + } + ControlFlow::Continue(()) + } + ClassElement::StaticBlock(node) => { + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut FormalParameterList::default(), + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + } + } + + fn visit_object_method_definition_mut( + &mut self, + node: &'ast mut ObjectMethodDefinition, + ) -> ControlFlow { + match &mut node.name { + PropertyName::Literal(_) => {} + PropertyName::Computed(name) => { + try_break!(self.visit_expression_mut(name)); + } + } + let strict = node.body.strict(); + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + None, + &mut None, + strict, + false, + ) + } + + fn visit_block_mut(&mut self, node: &'ast mut Block) -> ControlFlow { + let mut scope = block_declaration_instantiation(node, self.scope.clone(), self.interner); + if let Some(scope) = &mut scope { + std::mem::swap(&mut self.scope, scope); + } + try_break!(self.visit_statement_list_mut(&mut node.statements)); + if let Some(scope) = &mut scope { + std::mem::swap(&mut self.scope, scope); + } + node.scope = scope; + ControlFlow::Continue(()) + } + + fn visit_switch_mut(&mut self, node: &'ast mut Switch) -> ControlFlow { + try_break!(self.visit_expression_mut(&mut node.val)); + let mut scope = block_declaration_instantiation(node, self.scope.clone(), self.interner); + if let Some(scope) = &mut scope { + std::mem::swap(&mut self.scope, scope); + } + for case in &mut *node.cases { + try_break!(self.visit_case_mut(case)); + } + if let Some(scope) = &mut scope { + std::mem::swap(&mut self.scope, scope); + } + node.scope = scope; + ControlFlow::Continue(()) + } + + fn visit_with_mut(&mut self, node: &'ast mut With) -> ControlFlow { + try_break!(self.visit_expression_mut(&mut node.expression)); + let mut scope = Scope::new(self.scope.clone(), false); + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_statement_mut(&mut node.statement)); + std::mem::swap(&mut self.scope, &mut scope); + node.scope = scope; + ControlFlow::Continue(()) + } + + fn visit_catch_mut(&mut self, node: &'ast mut Catch) -> ControlFlow { + let mut scope = Scope::new(self.scope.clone(), false); + if let Some(binding) = node.parameter() { + match binding { + Binding::Identifier(ident) => { + let ident = ident.to_js_string(self.interner); + drop(scope.create_mutable_binding(ident.clone(), false)); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + let ident = ident.to_js_string(self.interner); + drop(scope.create_mutable_binding(ident, false)); + } + } + } + } + std::mem::swap(&mut self.scope, &mut scope); + if let Some(binding) = &mut node.parameter { + try_break!(self.visit_binding_mut(binding)); + } + try_break!(self.visit_block_mut(&mut node.block)); + std::mem::swap(&mut self.scope, &mut scope); + node.scope = scope; + ControlFlow::Continue(()) + } + + fn visit_for_loop_mut(&mut self, node: &'ast mut ForLoop) -> ControlFlow { + let scope = match &mut node.inner.init { + Some(ForLoopInitializer::Lexical(decl)) => { + let mut scope = Scope::new(self.scope.clone(), false); + let names = bound_names(&decl.declaration); + if decl.declaration.is_const() { + for name in &names { + let name = name.to_js_string(self.interner); + scope.create_immutable_binding(name, true); + } + } else { + for name in &names { + let name = name.to_js_string(self.interner); + drop(scope.create_mutable_binding(name, false)); + } + } + decl.scope = scope.clone(); + std::mem::swap(&mut self.scope, &mut scope); + Some(scope) + } + _ => None, + }; + if let Some(fli) = &mut node.inner.init { + try_break!(self.visit_for_loop_initializer_mut(fli)); + } + if let Some(expr) = &mut node.inner.condition { + try_break!(self.visit_expression_mut(expr)); + } + if let Some(expr) = &mut node.inner.final_expr { + try_break!(self.visit_expression_mut(expr)); + } + self.visit_statement_mut(&mut node.inner.body); + if let Some(mut scope) = scope { + std::mem::swap(&mut self.scope, &mut scope); + } + ControlFlow::Continue(()) + } + + fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { + let initializer_bound_names = match node.initializer() { + IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => bound_names(declaration), + _ => Vec::new(), + }; + if initializer_bound_names.is_empty() { + try_break!(self.visit_expression_mut(&mut node.target)); + } else { + let mut scope = Scope::new(self.scope.clone(), false); + for name in &initializer_bound_names { + let name = name.to_js_string(self.interner); + drop(scope.create_mutable_binding(name, false)); + } + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_expression_mut(&mut node.target)); + std::mem::swap(&mut self.scope, &mut scope); + node.target_scope = Some(scope); + } + let scope = match node.initializer() { + IterableLoopInitializer::Let(declaration) => { + let scope = Scope::new(self.scope.clone(), false); + match declaration { + Binding::Identifier(ident) => { + let ident = ident.to_js_string(self.interner); + drop(scope.create_mutable_binding(ident.clone(), false)); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + let ident = ident.to_js_string(self.interner); + drop(scope.create_mutable_binding(ident, false)); + } + } + } + Some(scope) + } + IterableLoopInitializer::Const(declaration) => { + let scope = Scope::new(self.scope.clone(), false); + match declaration { + Binding::Identifier(ident) => { + let ident = ident.to_js_string(self.interner); + scope.create_immutable_binding(ident.clone(), true); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + let ident = ident.to_js_string(self.interner); + scope.create_immutable_binding(ident, true); + } + } + } + Some(scope) + } + _ => None, + }; + if let Some(mut scope) = scope { + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.initializer)); + try_break!(self.visit_statement_mut(&mut node.body)); + std::mem::swap(&mut self.scope, &mut scope); + node.scope = Some(scope); + } else { + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.initializer)); + try_break!(self.visit_statement_mut(&mut node.body)); + } + ControlFlow::Continue(()) + } + + fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { + let initializer_bound_names = match node.initializer() { + IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => bound_names(declaration), + _ => Vec::new(), + }; + if initializer_bound_names.is_empty() { + try_break!(self.visit_expression_mut(&mut node.iterable)); + } else { + let mut scope = Scope::new(self.scope.clone(), false); + for name in &initializer_bound_names { + let name = name.to_js_string(self.interner); + drop(scope.create_mutable_binding(name, false)); + } + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_expression_mut(&mut node.iterable)); + std::mem::swap(&mut self.scope, &mut scope); + node.iterable_scope = Some(scope); + } + let scope = match node.initializer() { + IterableLoopInitializer::Let(declaration) => { + let scope = Scope::new(self.scope.clone(), false); + match declaration { + Binding::Identifier(ident) => { + let ident = ident.to_js_string(self.interner); + drop(scope.create_mutable_binding(ident.clone(), false)); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + let ident = ident.to_js_string(self.interner); + drop(scope.create_mutable_binding(ident, false)); + } + } + } + Some(scope) + } + IterableLoopInitializer::Const(declaration) => { + let scope = Scope::new(self.scope.clone(), false); + match declaration { + Binding::Identifier(ident) => { + let ident = ident.to_js_string(self.interner); + scope.create_immutable_binding(ident.clone(), true); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + let ident = ident.to_js_string(self.interner); + scope.create_immutable_binding(ident, true); + } + } + } + Some(scope) + } + _ => None, + }; + if let Some(mut scope) = scope { + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.init)); + try_break!(self.visit_statement_mut(&mut node.body)); + std::mem::swap(&mut self.scope, &mut scope); + node.scope = Some(scope); + } else { + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.init)); + try_break!(self.visit_statement_mut(&mut node.body)); + } + ControlFlow::Continue(()) + } + + fn visit_module_mut(&mut self, node: &'ast mut Module) -> ControlFlow { + let mut scope = Scope::new(self.scope.clone(), true); + module_instantiation(node, &scope, self.interner); + std::mem::swap(&mut self.scope, &mut scope); + try_break!(self.visit_module_item_list_mut(&mut node.items)); + std::mem::swap(&mut self.scope, &mut scope); + node.scope = scope; + ControlFlow::Continue(()) + } + + fn visit_script_mut(&mut self, node: &'ast mut Script) -> ControlFlow { + if self.eval { + try_break!(self.visit_statement_list_mut(node.statements_mut())); + } else { + match global_declaration_instantiation(node, &self.scope, self.interner) { + Ok(()) => { + try_break!(self.visit_statement_list_mut(node.statements_mut())); + } + Err(e) => return ControlFlow::Break(e), + } + } + ControlFlow::Continue(()) + } +} + +impl BindingCollectorVisitor<'_> { + #[allow(clippy::too_many_arguments)] + fn visit_function_like( + &mut self, + body: &mut FunctionBody, + parameters: &mut FormalParameterList, + scopes: &mut FunctionScopes, + name: Option, + name_scope: &mut Option, + strict: bool, + arrow: bool, + ) -> ControlFlow<&'static str> { + let strict = self.strict || strict; + + let function_scope = if let Some(name) = name { + let scope = Scope::new(self.scope.clone(), false); + let name = name.to_js_string(self.interner); + scope.create_immutable_binding(name, strict); + *name_scope = Some(scope.clone()); + Scope::new(scope, true) + } else { + Scope::new(self.scope.clone(), true) + }; + + let function_scopes = function_declaration_instantiation( + body, + parameters, + arrow, + strict, + function_scope.clone(), + self.interner, + ); + + let mut params_scope = function_scopes.parameter_scope(); + let mut body_scope = function_scopes.body_scope(); + + std::mem::swap(&mut self.scope, &mut params_scope); + try_break!(self.visit_formal_parameter_list_mut(parameters)); + std::mem::swap(&mut self.scope, &mut params_scope); + + std::mem::swap(&mut self.scope, &mut body_scope); + try_break!(self.visit_function_body_mut(body)); + std::mem::swap(&mut self.scope, &mut body_scope); + + *scopes = function_scopes; + + ControlFlow::Continue(()) + } +} + +/// `GlobalDeclarationInstantiation ( script, env )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation +/// +/// # Errors +/// +/// - If a duplicate lexical declaration is found. +fn global_declaration_instantiation( + script: &Script, + env: &Scope, + interner: &Interner, +) -> Result<(), &'static str> { + // 1. Let lexNames be the LexicallyDeclaredNames of script. + let lex_names = lexically_declared_names(script); + + // 2. Let varNames be the VarDeclaredNames of script. + let var_names = var_declared_names(script); + + // 3. For each element name of lexNames, do + for name in lex_names { + let name = name.to_js_string(interner); + + // Note: Our implementation differs from the spec here. + // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. + // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if env.has_binding(&name) { + return Err("duplicate lexical declaration"); + } + } + + // 4. For each element name of varNames, do + for name in var_names { + let name = name.to_js_string(interner); + + // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if env.has_lex_binding(&name) { + return Err("duplicate lexical declaration"); + } + } + + // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. + // 14. Let privateEnv be null. + // 15. For each element d of lexDeclarations, do + for statement in &**script.statements() { + // a. NOTE: 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 ? env.CreateImmutableBinding(dn, true). + // ii. Else, + // 1. Perform ? env.CreateMutableBinding(dn, false). + if let StatementListItem::Declaration(declaration) = statement { + match declaration { + Declaration::ClassDeclaration(class) => { + for name in bound_names(class) { + let name = name.to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + let name = name.to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + } + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + let name = name.to_js_string(interner); + env.create_immutable_binding(name, true); + } + } + _ => {} + } + } + } + + Ok(()) +} + +/// `BlockDeclarationInstantiation ( code, env )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation +fn block_declaration_instantiation<'a, N>( + block: &'a N, + scope: Scope, + interner: &Interner, +) -> Option +where + &'a N: Into>, +{ + let scope = Scope::new(scope, false); + + // 1. Let declarations be the LexicallyScopedDeclarations of code. + let declarations = lexically_scoped_declarations(block); + + // 2. Let privateEnv be the running execution context's PrivateEnvironment. + // Note: Private environments are currently handled differently. + + // 3. For each element d of declarations, do + for d in &declarations { + // i. If IsConstantDeclaration of d is true, then + if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(d)) = d { + // a. For each element dn of the BoundNames of d, do + for dn in bound_names::<'_, VariableList>(d) { + // 1. Perform ! env.CreateImmutableBinding(dn, true). + let dn = dn.to_js_string(interner); + scope.create_immutable_binding(dn, true); + } + } + // ii. Else, + else { + // a. For each element dn of the BoundNames of d, do + for dn in d.bound_names() { + let dn = dn.to_js_string(interner); + + #[cfg(not(feature = "annex-b"))] + // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. + drop(scope.create_mutable_binding(dn, false)); + + #[cfg(feature = "annex-b")] + // 1. If ! env.HasBinding(dn) is false, then + if !scope.has_binding(&dn) { + // a. Perform ! env.CreateMutableBinding(dn, false). + drop(scope.create_mutable_binding(dn, false)); + } + } + } + } + + if scope.num_bindings() > 0 { + Some(scope) + } else { + None + } +} + +/// `FunctionDeclarationInstantiation ( func, argumentsList )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation +fn function_declaration_instantiation( + body: &FunctionBody, + formals: &FormalParameterList, + arrow: bool, + strict: bool, + function_scope: Scope, + interner: &Interner, +) -> FunctionScopes { + let mut scopes = FunctionScopes { + function_scope, + parameters_eval_scope: None, + parameters_scope: None, + lexical_scope: None, + }; + + // 1. Let calleeContext be the running execution context. + // 2. Let code be func.[[ECMAScriptCode]]. + // 3. Let strict be func.[[Strict]]. + // 4. Let formals be func.[[FormalParameters]]. + + // 5. Let parameterNames be the BoundNames of formals. + let mut parameter_names = bound_names(formals); + + // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. + // 7. Let simpleParameterList be IsSimpleParameterList of formals. + + // 8. Let hasParameterExpressions be ContainsExpression of formals. + let has_parameter_expressions = formals.has_expressions(); + + // 9. Let varNames be the VarDeclaredNames of code. + let var_names = var_declared_names(body); + + // 10. Let varDeclarations be the VarScopedDeclarations of code. + let var_declarations = var_scoped_declarations(body); + + // 11. Let lexicalNames be the LexicallyDeclaredNames of code. + let lexical_names = lexically_declared_names(body); + + // 12. Let functionNames be a new empty List. + let mut function_names = Vec::new(); + + // 13. Let functionsToInitialize be a new empty List. + // let mut functions_to_initialize = Vec::new(); + + // 14. For each element d of varDeclarations, in reverse List order, do + for declaration in var_declarations.iter().rev() { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + // a.ii. Let fn be the sole element of the BoundNames of d. + let name = match declaration { + VarScopedDeclaration::FunctionDeclaration(f) => f.name(), + VarScopedDeclaration::GeneratorDeclaration(f) => f.name(), + VarScopedDeclaration::AsyncFunctionDeclaration(f) => f.name(), + VarScopedDeclaration::AsyncGeneratorDeclaration(f) => f.name(), + VarScopedDeclaration::VariableDeclaration(_) => continue, + }; + + // a.iii. If functionNames does not contain fn, then + if !function_names.contains(&name) { + // 1. Insert fn as the first element of functionNames. + function_names.push(name); + } + } + + function_names.reverse(); + + // 15. Let argumentsObjectNeeded be true. + let mut arguments_object_needed = true; + + let arguments = Sym::ARGUMENTS.into(); + + // 16. If func.[[ThisMode]] is lexical, then + // 17. Else if parameterNames contains "arguments", then + if arrow || parameter_names.contains(&arguments) { + // 16.a. NOTE: Arrow functions never have an arguments object. + // 16.b. Set argumentsObjectNeeded to false. + // 17.a. Set argumentsObjectNeeded to false. + arguments_object_needed = false; + } + // 18. Else if hasParameterExpressions is false, then + else if !has_parameter_expressions { + //a. If functionNames contains "arguments" or lexicalNames contains "arguments", then + if function_names.contains(&arguments) || lexical_names.contains(&arguments) { + // i. Set argumentsObjectNeeded to false. + arguments_object_needed = false; + } + } + + // 19. If strict is true or hasParameterExpressions is false, then + let env = if strict || !has_parameter_expressions { + // a. NOTE: Only a single Environment Record is needed for the parameters, + // since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. + // b. Let env be the LexicalEnvironment of calleeContext. + scopes.function_scope.clone() + } + // 20. Else, + else { + // a. NOTE: A separate Environment Record is needed to ensure that bindings created by + // direct eval calls in the formal parameter list are outside the environment where parameters are declared. + // b. Let calleeEnv be the LexicalEnvironment of calleeContext. + // c. Let env be NewDeclarativeEnvironment(calleeEnv). + // d. Assert: The VariableEnvironment of calleeContext is calleeEnv. + // e. Set the LexicalEnvironment of calleeContext to env. + let scope = Scope::new(scopes.function_scope.clone(), false); + scopes.parameters_eval_scope = Some(scope.clone()); + scope + }; + + // 22. If argumentsObjectNeeded is true, then + // + // NOTE(HalidOdat): Has been moved up, so "arguments" gets registed as + // the first binding in the environment with index 0. + if arguments_object_needed { + let arguments = arguments.to_js_string(interner); + + // 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. + env.create_immutable_binding(arguments.clone(), false); + } + // d. Else, + else { + // i. Perform ! env.CreateMutableBinding("arguments", false). + drop(env.create_mutable_binding(arguments.clone(), false)); + } + } + + // 21. For each String paramName of parameterNames, do + for param_name in ¶meter_names { + let param_name = param_name.to_js_string(interner); + + // a. Let alreadyDeclared be ! env.HasBinding(paramName). + let already_declared = env.has_binding(¶m_name); + + // b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict + // functions that do not have parameter default values or rest parameters. + + // c. If alreadyDeclared is false, then + if !already_declared { + // i. Perform ! env.CreateMutableBinding(paramName, false). + drop(env.create_mutable_binding(param_name.clone(), false)); + + // Note: In this case the function contains a mapped arguments object. + // Because we do not track (yet) if the mapped arguments object escapes the function, + // we have to assume that the binding might escape trough the arguments object. + if arguments_object_needed && !strict && formals.is_simple() { + env.access_binding(¶m_name, true); + } + + // Note: These steps are not necessary in our implementation. + // ii. If hasDuplicates is true, then + // 1. Perform ! env.InitializeBinding(paramName, undefined). + } + } + + // 22. If argumentsObjectNeeded is true, then + if arguments_object_needed { + // MOVED: a-e. + // + // NOTE(HalidOdat): Has been moved up, see comment above. + + // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». + parameter_names.push(arguments); + } + + // 23. Else, + // a. Let parameterBindings be parameterNames. + let parameter_bindings = parameter_names.clone(); + + // 27. If hasParameterExpressions is false, then + // 28. Else, + #[allow(unused_variables, unused_mut)] + let (mut instantiated_var_names, mut var_env) = 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). + let var_env = Scope::new(env.clone(), false); + scopes.parameters_scope = Some(var_env.clone()); + + // c. Set the VariableEnvironment of calleeContext to varEnv. + + // d. Let instantiatedVarNames be a new empty List. + let mut instantiated_var_names = Vec::new(); + + // e. For each element n of varNames, do + for n in var_names { + // i. If instantiatedVarNames does not contain n, then + if !instantiated_var_names.contains(&n) { + // 1. Append n to instantiatedVarNames. + instantiated_var_names.push(n); + + let n_string = n.to_js_string(interner); + + // 2. Perform ! varEnv.CreateMutableBinding(n, false). + drop(var_env.create_mutable_binding(n_string, false)); + } + } + + (instantiated_var_names, var_env) + } 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. + let mut instantiated_var_names = parameter_bindings; + + // c. For each element n of varNames, do + for n in var_names { + // i. If instantiatedVarNames does not contain n, then + if !instantiated_var_names.contains(&n) { + // 1. Append n to instantiatedVarNames. + instantiated_var_names.push(n); + + let n = n.to_js_string(interner); + + // 2. Perform ! env.CreateMutableBinding(n, false). + // 3. Perform ! env.InitializeBinding(n, undefined). + drop(env.create_mutable_binding(n, true)); + } + } + + // d. Let varEnv be env. + (instantiated_var_names, env) + }; + + // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. + // 29. If strict is false, then + #[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(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 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 { + let f_string = f.to_js_string(interner); + + // a. Perform ! varEnv.CreateMutableBinding(F, false). + // b. Perform ! varEnv.InitializeBinding(F, undefined). + drop(var_env.create_mutable_binding(f_string, false)); + + // c. Append F to instantiatedVarNames. + instantiated_var_names.push(f); + } + } + } + } + + // 30. If strict is false, then + // 31. Else, + let lex_env = if strict { + // a. Let lexEnv be varEnv. + var_env + } else { + // 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. + let lex_env = Scope::new(var_env, false); + scopes.lexical_scope = Some(lex_env.clone()); + lex_env + }; + + // 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. + // 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). + // ii. Else, + // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). + for statement in body.statements() { + if let StatementListItem::Declaration(declaration) = statement { + match declaration { + Declaration::ClassDeclaration(class) => { + for name in bound_names(class) { + let name = name.to_js_string(interner); + drop(lex_env.create_mutable_binding(name, false)); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + let name = name.to_js_string(interner); + drop(lex_env.create_mutable_binding(name, false)); + } + } + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + let name = name.to_js_string(interner); + lex_env.create_immutable_binding(name, true); + } + } + _ => {} + } + } + } + + // 35. Let privateEnv be the PrivateEnvironment of calleeContext. + // 36. For each Parse Node f of functionsToInitialize, do + + if let Some(lexical_scope) = &scopes.lexical_scope { + if lexical_scope.num_bindings() == 0 { + scopes.lexical_scope = None; + } + } + + // 37. Return unused. + scopes +} + +/// Abstract operation [`InitializeEnvironment ( )`][spec]. +/// +/// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment +fn module_instantiation(module: &Module, env: &Scope, interner: &Interner) { + for entry in module.items().import_entries() { + let local_name = entry.local_name().to_js_string(interner); + env.create_immutable_binding(local_name, true); + } + let var_declarations = var_scoped_declarations(module); + let mut declared_var_names = Vec::new(); + for var in var_declarations { + for name in var.bound_names() { + let name = name.to_js_string(interner); + if !declared_var_names.contains(&name) { + drop(env.create_mutable_binding(name.clone(), false)); + declared_var_names.push(name); + } + } + } + + let lex_declarations = lexically_scoped_declarations(module); + for declaration in lex_declarations { + match declaration { + LexicallyScopedDeclaration::FunctionDeclaration(f) => { + let name = bound_names(f)[0].to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + LexicallyScopedDeclaration::GeneratorDeclaration(g) => { + let name = bound_names(g)[0].to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + LexicallyScopedDeclaration::AsyncFunctionDeclaration(af) => { + let name = bound_names(af)[0].to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + LexicallyScopedDeclaration::AsyncGeneratorDeclaration(ag) => { + let name = bound_names(ag)[0].to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + LexicallyScopedDeclaration::ClassDeclaration(class) => { + for name in bound_names(class) { + let name = name.to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + continue; + } + LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(c)) => { + for name in bound_names(c) { + let name = name.to_js_string(interner); + env.create_immutable_binding(name, true); + } + continue; + } + LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Let(l)) => { + for name in bound_names(l) { + let name = name.to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + continue; + } + LexicallyScopedDeclaration::AssignmentExpression(expr) => { + for name in bound_names(expr) { + let name = name.to_js_string(interner); + drop(env.create_mutable_binding(name, false)); + } + continue; + } + }; + } +} + +/// This struct isused to store bindings created during the declaration of an eval ast node. +#[derive(Debug, Default)] +pub struct EvalDeclarationBindings { + /// New annexb function names created during the declaration of an eval ast node. + pub new_annex_b_function_names: Vec, + + /// New function names created during the declaration of an eval ast node. + pub new_function_names: FxHashMap, + + /// New variable names created during the declaration of an eval ast node. + pub new_var_names: Vec, +} + +/// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation +/// +/// # Errors +/// +/// * Returns a syntax error if a duplicate lexical declaration is found. +/// * Returns a syntax error if a variable declaration in an eval function already exists as a lexical variable. +#[allow(clippy::missing_panics_doc)] +pub(crate) fn eval_declaration_instantiation_scope( + body: &Script, + strict: bool, + var_env: &Scope, + lex_env: &Scope, + #[allow(unused_variables)] annex_b_function_names: &[Identifier], + interner: &Interner, +) -> Result { + let mut result = EvalDeclarationBindings::default(); + + // 2. Let varDeclarations be the VarScopedDeclarations of body. + let var_declarations = var_scoped_declarations(body); + + // 3. If strict is false, then + if !strict { + // 1. Let varNames be the VarDeclaredNames of body. + let var_names = var_declared_names(body); + + // a. If varEnv is a Global Environment Record, then + if var_env.is_global() { + // i. For each element name of varNames, do + for name in &var_names { + let name = name.to_js_string(interner); + + // 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration. + if var_env.has_lex_binding(&name) { + return Err(format!( + "duplicate lexical declaration {}", + name.to_std_string_escaped() + )); + } + } + } + + // b. Let thisEnv be lexEnv. + let mut this_env = lex_env.clone(); + + // c. Assert: The following loop will terminate. + // d. Repeat, while thisEnv is not varEnv, + while this_env.scope_index() != var_env.scope_index() { + // 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. + // 2. For each element name of varNames, do + for name in &var_names { + let name = interner.resolve_expect(name.sym()).utf16().into(); + + // a. If ! thisEnv.HasBinding(name) is true, then + if this_env.has_binding(&name) { + // i. Throw a SyntaxError exception. + // ii. NOTE: Annex B.3.4 defines alternate semantics for the above step. + return Err(format!("variable declaration {} in eval function already exists as a lexical variable", name.to_std_string_escaped())); + } + // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration. + } + + // ii. Set thisEnv to thisEnv.[[OuterEnv]]. + if let Some(outer) = this_env.outer() { + this_env = outer; + } else { + break; + } + } + } + + // NOTE: These steps depend on the current environment state are done before bytecode compilation, + // in `eval_declaration_instantiation_context`. + // + // SKIP: 4. Let privateIdentifiers be a new empty List. + // SKIP: 5. Let pointer be privateEnv. + // SKIP: 6. Repeat, while pointer is not null, + // a. For each Private Name binding of pointer.[[Names]], do + // i. If privateIdentifiers does not contain binding.[[Description]], + // append binding.[[Description]] to privateIdentifiers. + // b. Set pointer to pointer.[[OuterPrivateEnvironment]]. + // SKIP: 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception. + + // 8. Let functionsToInitialize be a new empty List. + let mut functions_to_initialize = Vec::new(); + + // 9. Let declaredFunctionNames be a new empty List. + let mut declared_function_names = Vec::new(); + + // 10. For each element d of varDeclarations, in reverse List order, do + for declaration in var_declarations.iter().rev() { + // a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + // a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + // a.iii. Let fn be the sole element of the BoundNames of d. + let name = match &declaration { + VarScopedDeclaration::FunctionDeclaration(f) => f.name(), + VarScopedDeclaration::GeneratorDeclaration(f) => f.name(), + VarScopedDeclaration::AsyncFunctionDeclaration(f) => f.name(), + VarScopedDeclaration::AsyncGeneratorDeclaration(f) => f.name(), + VarScopedDeclaration::VariableDeclaration(_) => continue, + }; + // a.iv. If declaredFunctionNames does not contain fn, then + if !declared_function_names.contains(&name) { + // 1. If varEnv is a Global Environment Record, then + // 2. Append fn to declaredFunctionNames. + declared_function_names.push(name); + + // 3. Insert d as the first element of functionsToInitialize. + functions_to_initialize.push(declaration.clone()); + } + } + + functions_to_initialize.reverse(); + + // 11. NOTE: Annex B.3.2.3 adds additional steps at this point. + // 11. If strict is false, then + #[cfg(feature = "annex-b")] + if !strict { + // NOTE: This diviates from the specification, we split the first part of defining the annex-b names + // in `eval_declaration_instantiation_context`, because it depends on the context. + if !var_env.is_global() { + for name in annex_b_function_names { + let f = name.to_js_string(interner); + // i. Let bindingExists be ! varEnv.HasBinding(F). + // ii. If bindingExists is false, then + if !var_env.has_binding(&f) { + // i. Perform ! varEnv.CreateMutableBinding(F, true). + // ii. Perform ! varEnv.InitializeBinding(F, undefined). + let binding = var_env.create_mutable_binding(f, true); + result + .new_annex_b_function_names + .push(IdentifierReference::new( + binding, + !var_env.is_function(), + true, + )); + } + } + } + } + + // 12. Let declaredVarNames be a new empty List. + let mut declared_var_names = Vec::new(); + + // 13. For each element d of varDeclarations, do + for declaration in var_declarations { + // a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else { + continue; + }; + + // a.i. For each String vn of the BoundNames of d, do + for name in bound_names(&declaration) { + // 1. If declaredFunctionNames does not contain vn, then + if !declared_function_names.contains(&name) { + // a. If varEnv is a Global Environment Record, then + // b. If declaredVarNames does not contain vn, then + if !declared_var_names.contains(&name) { + // i. Append vn to declaredVarNames. + declared_var_names.push(name); + } + } + } + } + + // 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a + // Global Environment Record and the global object is a Proxy exotic object. + + // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. + // 16. For each element d of lexDeclarations, do + for statement in &**body.statements() { + // a. NOTE: 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). + // ii. Else, + // 1. Perform ? lexEnv.CreateMutableBinding(dn, false). + if let StatementListItem::Declaration(declaration) = statement { + match declaration { + Declaration::ClassDeclaration(class) => { + for name in bound_names(class) { + let name = name.to_js_string(interner); + drop(lex_env.create_mutable_binding(name, false)); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + let name = name.to_js_string(interner); + drop(lex_env.create_mutable_binding(name, false)); + } + } + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + let name = name.to_js_string(interner); + lex_env.create_immutable_binding(name, true); + } + } + _ => {} + } + } + } + + // 17. For each Parse Node f of functionsToInitialize, do + for function in functions_to_initialize { + // a. Let fn be the sole element of the BoundNames of f. + let name = match &function { + VarScopedDeclaration::FunctionDeclaration(f) => f.name(), + VarScopedDeclaration::GeneratorDeclaration(f) => f.name(), + VarScopedDeclaration::AsyncFunctionDeclaration(f) => f.name(), + VarScopedDeclaration::AsyncGeneratorDeclaration(f) => f.name(), + VarScopedDeclaration::VariableDeclaration(_) => { + continue; + } + }; + + // c. If varEnv is a Global Environment Record, then + // d. Else, + if !var_env.is_global() { + // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. + let n = name.to_js_string(interner); + + // i. Let bindingExists be ! varEnv.HasBinding(fn). + let binding_exists = var_env.has_binding(&n); + + // ii. If bindingExists is false, then + // iii. Else, + if binding_exists { + // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). + let binding = var_env.set_mutable_binding(n).expect("must not fail"); + result.new_function_names.insert( + name, + ( + IdentifierReference::new(binding.locator(), !var_env.is_function(), true), + true, + ), + ); + } else { + // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. + // 2. Perform ! varEnv.CreateMutableBinding(fn, true). + // 3. Perform ! varEnv.InitializeBinding(fn, fo). + let binding = var_env.create_mutable_binding(n, !strict); + result.new_function_names.insert( + name, + ( + IdentifierReference::new(binding, !var_env.is_function(), true), + false, + ), + ); + } + } + } + + // 18. For each String vn of declaredVarNames, do + for name in declared_var_names { + // a. If varEnv is a Global Environment Record, then + // b. Else, + if !var_env.is_global() { + let name = name.to_js_string(interner); + + // i. Let bindingExists be ! varEnv.HasBinding(vn). + let binding_exists = var_env.has_binding(&name); + + // ii. If bindingExists is false, then + if !binding_exists { + // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. + // 2. Perform ! varEnv.CreateMutableBinding(vn, true). + // 3. Perform ! varEnv.InitializeBinding(vn, undefined). + let binding = var_env.create_mutable_binding(name, true); + result.new_var_names.push(IdentifierReference::new( + binding, + !var_env.is_function(), + true, + )); + } + } + } + + // 19. Return unused. + Ok(result) +} diff --git a/core/ast/src/source.rs b/core/ast/src/source.rs index 10ca0d727b..f1220a7621 100644 --- a/core/ast/src/source.rs +++ b/core/ast/src/source.rs @@ -1,8 +1,14 @@ use std::ops::ControlFlow; -use boa_interner::ToIndentedString; +use boa_interner::{Interner, ToIndentedString}; use crate::{ + expression::Identifier, + scope::Scope, + scope_analyzer::{ + analyze_binding_escapes, collect_bindings, eval_declaration_instantiation_scope, + EvalDeclarationBindings, + }, visitor::{VisitWith, Visitor, VisitorMut}, ModuleItemList, StatementList, }; @@ -44,6 +50,47 @@ impl Script { pub const fn strict(&self) -> bool { self.statements.strict() } + + /// Analyze the scope of the script. + pub fn analyze_scope(&mut self, scope: &Scope, interner: &Interner) -> bool { + if !collect_bindings(self, self.strict(), false, scope, interner) { + return false; + } + analyze_binding_escapes(self, false, scope.clone(), interner) + } + + /// Analyze the scope of the script in eval mode. + /// + /// # Errors + /// + /// Returns an error if the scope analysis fails with a syntax error. + pub fn analyze_scope_eval( + &mut self, + strict: bool, + variable_scope: &Scope, + lexical_scope: &Scope, + annex_b_function_names: &[Identifier], + interner: &Interner, + ) -> Result { + let bindings = eval_declaration_instantiation_scope( + self, + strict, + variable_scope, + lexical_scope, + annex_b_function_names, + interner, + )?; + + if !collect_bindings(self, strict, true, lexical_scope, interner) { + return Err(String::from("Failed to analyze scope")); + } + + if !analyze_binding_escapes(self, true, lexical_scope.clone(), interner) { + return Err(String::from("Failed to analyze scope")); + } + + Ok(bindings) + } } impl VisitWith for Script { @@ -63,7 +110,7 @@ impl VisitWith for Script { } impl ToIndentedString for Script { - fn to_indented_string(&self, interner: &boa_interner::Interner, indentation: usize) -> String { + fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { self.statements.to_indented_string(interner, indentation) } } @@ -77,14 +124,20 @@ impl ToIndentedString for Script { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq)] pub struct Module { - items: ModuleItemList, + pub(crate) items: ModuleItemList, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Scope, } impl Module { /// Creates a new `ModuleNode`. #[must_use] - pub const fn new(items: ModuleItemList) -> Self { - Self { items } + pub fn new(items: ModuleItemList) -> Self { + Self { + items, + scope: Scope::default(), + } } /// Gets the list of itemos of this `ModuleNode`. @@ -92,6 +145,21 @@ impl Module { pub const fn items(&self) -> &ModuleItemList { &self.items } + + /// Gets the scope of this `ModuleNode`. + #[inline] + #[must_use] + pub const fn scope(&self) -> &Scope { + &self.scope + } + + /// Analyze the scope of the module. + pub fn analyze_scope(&mut self, scope: &Scope, interner: &Interner) -> bool { + if !collect_bindings(self, true, false, scope, interner) { + return false; + } + analyze_binding_escapes(self, false, scope.clone(), interner) + } } impl VisitWith for Module { diff --git a/core/ast/src/statement/block.rs b/core/ast/src/statement/block.rs index 590b41b2c0..94671059fd 100644 --- a/core/ast/src/statement/block.rs +++ b/core/ast/src/statement/block.rs @@ -1,6 +1,8 @@ //! Block AST node. use crate::{ + operations::{contains, ContainsSymbol}, + scope::Scope, visitor::{VisitWith, Visitor, VisitorMut}, Statement, StatementList, }; @@ -27,7 +29,11 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq, Default)] pub struct Block { #[cfg_attr(feature = "serde", serde(flatten))] - statements: StatementList, + pub(crate) statements: StatementList, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Option, } impl Block { @@ -37,6 +43,13 @@ impl Block { pub const fn statement_list(&self) -> &StatementList { &self.statements } + + /// Gets the scope of the block. + #[inline] + #[must_use] + pub const fn scope(&self) -> Option<&Scope> { + self.scope.as_ref() + } } impl From for Block @@ -44,8 +57,12 @@ where T: Into, { fn from(list: T) -> Self { + let statements = list.into(); + let contains_direct_eval = contains(&statements, ContainsSymbol::DirectEval); Self { - statements: list.into(), + statements, + scope: None, + contains_direct_eval, } } } diff --git a/core/ast/src/statement/iteration/for_in_loop.rs b/core/ast/src/statement/iteration/for_in_loop.rs index d93e4dff56..4cee0198a6 100644 --- a/core/ast/src/statement/iteration/for_in_loop.rs +++ b/core/ast/src/statement/iteration/for_in_loop.rs @@ -1,3 +1,5 @@ +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::Scope; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -18,9 +20,17 @@ use core::ops::ControlFlow; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ForInLoop { - initializer: IterableLoopInitializer, - target: Expression, - body: Box, + pub(crate) initializer: IterableLoopInitializer, + pub(crate) target: Expression, + pub(crate) body: Box, + pub(crate) target_contains_direct_eval: bool, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) target_scope: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Option, } impl ForInLoop { @@ -28,10 +38,17 @@ impl ForInLoop { #[inline] #[must_use] pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self { + let target_contains_direct_eval = contains(&target, ContainsSymbol::DirectEval); + let contains_direct_eval = contains(&initializer, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { initializer, target, body: body.into(), + target_contains_direct_eval, + contains_direct_eval, + target_scope: None, + scope: None, } } @@ -55,6 +72,20 @@ impl ForInLoop { pub const fn body(&self) -> &Statement { &self.body } + + /// Returns the target scope of the for...in loop. + #[inline] + #[must_use] + pub const fn target_scope(&self) -> Option<&Scope> { + self.target_scope.as_ref() + } + + /// Returns the scope of the for...in loop. + #[inline] + #[must_use] + pub const fn scope(&self) -> Option<&Scope> { + self.scope.as_ref() + } } impl ToIndentedString for ForInLoop { diff --git a/core/ast/src/statement/iteration/for_loop.rs b/core/ast/src/statement/iteration/for_loop.rs index 248c4e9c7c..a164da7efe 100644 --- a/core/ast/src/statement/iteration/for_loop.rs +++ b/core/ast/src/statement/iteration/for_loop.rs @@ -1,3 +1,5 @@ +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::Scope; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -23,7 +25,7 @@ use core::ops::ControlFlow; #[derive(Clone, Debug, PartialEq)] pub struct ForLoop { #[cfg_attr(feature = "serde", serde(flatten))] - inner: Box, + pub(crate) inner: Box, } impl ForLoop { @@ -138,27 +140,39 @@ impl VisitWith for ForLoop { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] -struct InnerForLoop { - init: Option, - condition: Option, - final_expr: Option, - body: Statement, +pub(crate) struct InnerForLoop { + pub(crate) init: Option, + pub(crate) condition: Option, + pub(crate) final_expr: Option, + pub(crate) body: Statement, + pub(crate) contains_direct_eval: bool, } impl InnerForLoop { /// Creates a new inner for loop. #[inline] - const fn new( + fn new( init: Option, condition: Option, final_expr: Option, body: Statement, ) -> Self { + let mut contains_direct_eval = contains(&body, ContainsSymbol::DirectEval); + if let Some(init) = &init { + contains_direct_eval |= contains(init, ContainsSymbol::DirectEval); + } + if let Some(condition) = &condition { + contains_direct_eval |= contains(condition, ContainsSymbol::DirectEval); + } + if let Some(final_expr) = &final_expr { + contains_direct_eval |= contains(final_expr, ContainsSymbol::DirectEval); + } Self { init, condition, final_expr, body, + contains_direct_eval, } } @@ -204,14 +218,48 @@ pub enum ForLoopInitializer { /// A var declaration initializer. Var(VarDeclaration), /// A lexical declaration initializer. - Lexical(LexicalDeclaration), + Lexical(ForLoopInitializerLexical), +} + +/// A lexical declaration initializer for a `ForLoop`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq)] +pub struct ForLoopInitializerLexical { + pub(crate) declaration: LexicalDeclaration, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Scope, +} + +impl ForLoopInitializerLexical { + /// Creates a new lexical declaration initializer. + #[inline] + #[must_use] + pub fn new(declaration: LexicalDeclaration, scope: Scope) -> Self { + Self { declaration, scope } + } + + /// Returns the declaration of the lexical initializer. + #[inline] + #[must_use] + pub const fn declaration(&self) -> &LexicalDeclaration { + &self.declaration + } + + /// Returns the scope of the lexical initializer. + #[inline] + #[must_use] + pub const fn scope(&self) -> &Scope { + &self.scope + } } impl ToInternedString for ForLoopInitializer { fn to_interned_string(&self, interner: &Interner) -> String { match self { Self::Var(var) => var.to_interned_string(interner), - Self::Lexical(lex) => lex.to_interned_string(interner), + Self::Lexical(lex) => lex.declaration.to_interned_string(interner), Self::Expression(expr) => expr.to_interned_string(interner), } } @@ -227,7 +275,10 @@ impl From for ForLoopInitializer { impl From for ForLoopInitializer { #[inline] fn from(list: LexicalDeclaration) -> Self { - Self::Lexical(list) + Self::Lexical(ForLoopInitializerLexical { + declaration: list, + scope: Scope::default(), + }) } } @@ -246,7 +297,7 @@ impl VisitWith for ForLoopInitializer { match self { Self::Expression(expr) => visitor.visit_expression(expr), Self::Var(vd) => visitor.visit_var_declaration(vd), - Self::Lexical(ld) => visitor.visit_lexical_declaration(ld), + Self::Lexical(ld) => visitor.visit_lexical_declaration(&ld.declaration), } } @@ -257,7 +308,7 @@ impl VisitWith for ForLoopInitializer { match self { Self::Expression(expr) => visitor.visit_expression_mut(expr), Self::Var(vd) => visitor.visit_var_declaration_mut(vd), - Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), + Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(&mut ld.declaration), } } } diff --git a/core/ast/src/statement/iteration/for_of_loop.rs b/core/ast/src/statement/iteration/for_of_loop.rs index 3f660046cb..a26e5df9cf 100644 --- a/core/ast/src/statement/iteration/for_of_loop.rs +++ b/core/ast/src/statement/iteration/for_of_loop.rs @@ -1,3 +1,5 @@ +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::Scope; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -23,10 +25,18 @@ use core::ops::ControlFlow; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct ForOfLoop { - init: IterableLoopInitializer, - iterable: Expression, - body: Box, + pub(crate) init: IterableLoopInitializer, + pub(crate) iterable: Expression, + pub(crate) body: Box, r#await: bool, + pub(crate) iterable_contains_direct_eval: bool, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) iterable_scope: Option, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Option, } impl ForOfLoop { @@ -39,11 +49,18 @@ impl ForOfLoop { body: Statement, r#await: bool, ) -> Self { + let iterable_contains_direct_eval = contains(&iterable, ContainsSymbol::DirectEval); + let contains_direct_eval = contains(&init, ContainsSymbol::DirectEval) + || contains(&body, ContainsSymbol::DirectEval); Self { init, iterable, body: body.into(), + iterable_contains_direct_eval, + contains_direct_eval, r#await, + iterable_scope: None, + scope: None, } } @@ -74,6 +91,20 @@ impl ForOfLoop { pub const fn r#await(&self) -> bool { self.r#await } + + /// Return the iterable scope of the for...of loop. + #[inline] + #[must_use] + pub const fn iterable_scope(&self) -> Option<&Scope> { + self.iterable_scope.as_ref() + } + + /// Return the scope of the for...of loop. + #[inline] + #[must_use] + pub const fn scope(&self) -> Option<&Scope> { + self.scope.as_ref() + } } impl ToIndentedString for ForOfLoop { diff --git a/core/ast/src/statement/iteration/mod.rs b/core/ast/src/statement/iteration/mod.rs index a1177f4956..e3611e6603 100644 --- a/core/ast/src/statement/iteration/mod.rs +++ b/core/ast/src/statement/iteration/mod.rs @@ -18,7 +18,7 @@ use core::ops::ControlFlow; pub use self::{ do_while_loop::DoWhileLoop, for_in_loop::ForInLoop, - for_loop::{ForLoop, ForLoopInitializer}, + for_loop::{ForLoop, ForLoopInitializer, ForLoopInitializerLexical}, for_of_loop::ForOfLoop, r#break::Break, r#continue::Continue, diff --git a/core/ast/src/statement/switch.rs b/core/ast/src/statement/switch.rs index 87c4154239..42d5c646af 100644 --- a/core/ast/src/statement/switch.rs +++ b/core/ast/src/statement/switch.rs @@ -1,6 +1,8 @@ //! Switch node. use crate::{ expression::Expression, + operations::{contains, ContainsSymbol}, + scope::Scope, statement::Statement, try_break, visitor::{VisitWith, Visitor, VisitorMut}, @@ -114,8 +116,12 @@ impl VisitWith for Case { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Switch { - val: Expression, - cases: Box<[Case]>, + pub(crate) val: Expression, + pub(crate) cases: Box<[Case]>, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Option, } impl Switch { @@ -123,7 +129,16 @@ impl Switch { #[inline] #[must_use] pub fn new(val: Expression, cases: Box<[Case]>) -> Self { - Self { val, cases } + let mut contains_direct_eval = false; + for case in &cases { + contains_direct_eval |= contains(case, ContainsSymbol::DirectEval); + } + Self { + val, + cases, + contains_direct_eval, + scope: None, + } } /// Gets the value to switch. @@ -151,6 +166,13 @@ impl Switch { } None } + + /// Gets the scope of the switch statement. + #[inline] + #[must_use] + pub const fn scope(&self) -> Option<&Scope> { + self.scope.as_ref() + } } impl ToIndentedString for Switch { diff --git a/core/ast/src/statement/try.rs b/core/ast/src/statement/try.rs index 8370b19e9f..8354fe169a 100644 --- a/core/ast/src/statement/try.rs +++ b/core/ast/src/statement/try.rs @@ -1,5 +1,7 @@ //! Error handling statements +use crate::operations::{contains, ContainsSymbol}; +use crate::scope::Scope; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ @@ -142,16 +144,29 @@ impl VisitWith for Try { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Catch { - parameter: Option, - block: Block, + pub(crate) parameter: Option, + pub(crate) block: Block, + pub(crate) contains_direct_eval: bool, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Scope, } impl Catch { /// Creates a new catch block. #[inline] #[must_use] - pub const fn new(parameter: Option, block: Block) -> Self { - Self { parameter, block } + pub fn new(parameter: Option, block: Block) -> Self { + let mut contains_direct_eval = contains(&block, ContainsSymbol::DirectEval); + if let Some(param) = ¶meter { + contains_direct_eval |= contains(param, ContainsSymbol::DirectEval); + } + Self { + parameter, + block, + contains_direct_eval, + scope: Scope::default(), + } } /// Gets the parameter of the catch block. @@ -167,6 +182,13 @@ impl Catch { pub const fn block(&self) -> &Block { &self.block } + + /// Returns the scope of the catch block. + #[inline] + #[must_use] + pub const fn scope(&self) -> &Scope { + &self.scope + } } impl ToIndentedString for Catch { diff --git a/core/ast/src/statement/with.rs b/core/ast/src/statement/with.rs index 4aad4e2659..1e8f921269 100644 --- a/core/ast/src/statement/with.rs +++ b/core/ast/src/statement/with.rs @@ -1,5 +1,6 @@ use crate::{ expression::Expression, + scope::Scope, statement::Statement, try_break, visitor::{VisitWith, Visitor, VisitorMut}, @@ -19,8 +20,11 @@ use core::ops::ControlFlow; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct With { - expression: Expression, - statement: Box, + pub(crate) expression: Expression, + pub(crate) statement: Box, + + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) scope: Scope, } impl With { @@ -30,6 +34,7 @@ impl With { Self { expression, statement: Box::new(statement), + scope: Scope::default(), } } @@ -44,6 +49,12 @@ impl With { pub const fn statement(&self) -> &Statement { &self.statement } + + /// Returns the scope of the `With` statement. + #[must_use] + pub const fn scope(&self) -> &Scope { + &self.scope + } } impl From for Statement { diff --git a/core/ast/src/statement_list.rs b/core/ast/src/statement_list.rs index bae91c1055..e86ec9c9eb 100644 --- a/core/ast/src/statement_list.rs +++ b/core/ast/src/statement_list.rs @@ -98,7 +98,7 @@ impl VisitWith for StatementListItem { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq)] pub struct StatementList { - statements: Box<[StatementListItem]>, + pub(crate) statements: Box<[StatementListItem]>, strict: bool, } diff --git a/core/ast/src/visitor.rs b/core/ast/src/visitor.rs index 351f52fe45..bcc22c3f74 100644 --- a/core/ast/src/visitor.rs +++ b/core/ast/src/visitor.rs @@ -31,7 +31,7 @@ use crate::{ function::{ ArrowFunction, AsyncArrowFunction, AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncGeneratorDeclaration, AsyncGeneratorExpression, ClassDeclaration, ClassElement, - ClassExpression, FormalParameter, FormalParameterList, FunctionDeclaration, + ClassExpression, FormalParameter, FormalParameterList, FunctionBody, FunctionDeclaration, FunctionExpression, GeneratorDeclaration, GeneratorExpression, PrivateName, }, pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern}, @@ -126,6 +126,7 @@ macro_rules! node_ref { node_ref! { Script, Module, + FunctionBody, StatementList, StatementListItem, Statement, @@ -234,6 +235,7 @@ pub trait Visitor<'ast>: Sized { define_visit!(visit_script, Script); define_visit!(visit_module, Module); + define_visit!(visit_function_body, FunctionBody); define_visit!(visit_statement_list, StatementList); define_visit!(visit_statement_list_item, StatementListItem); define_visit!(visit_statement, Statement); @@ -339,6 +341,7 @@ pub trait Visitor<'ast>: Sized { match node { NodeRef::Script(n) => self.visit_script(n), NodeRef::Module(n) => self.visit_module(n), + NodeRef::FunctionBody(n) => self.visit_function_body(n), NodeRef::StatementList(n) => self.visit_statement_list(n), NodeRef::StatementListItem(n) => self.visit_statement_list_item(n), NodeRef::Statement(n) => self.visit_statement(n), @@ -449,6 +452,7 @@ pub trait VisitorMut<'ast>: Sized { define_visit_mut!(visit_script_mut, Script); define_visit_mut!(visit_module_mut, Module); + define_visit_mut!(visit_function_body_mut, FunctionBody); define_visit_mut!(visit_statement_list_mut, StatementList); define_visit_mut!(visit_statement_list_item_mut, StatementListItem); define_visit_mut!(visit_statement_mut, Statement); @@ -563,6 +567,7 @@ pub trait VisitorMut<'ast>: Sized { match node { NodeRefMut::Script(n) => self.visit_script_mut(n), NodeRefMut::Module(n) => self.visit_module_mut(n), + NodeRefMut::FunctionBody(n) => self.visit_function_body_mut(n), NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n), NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n), NodeRefMut::Statement(n) => self.visit_statement_mut(n), diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 547b455a2a..5e7596f6c2 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -56,7 +56,7 @@ flowgraph = [] trace = ["js"] # Enable Boa's additional ECMAScript features for web browsers. -annex-b = ["boa_parser/annex-b"] +annex-b = ["boa_ast/annex-b", "boa_parser/annex-b"] # Enable Boa's Temporal proposal implementation temporal = ["dep:icu_calendar", "dep:temporal_rs"] diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 75b8448580..c86b084fee 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -9,22 +9,23 @@ //! [spec]: https://tc39.es/ecma262/#sec-eval-x //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval -use std::rc::Rc; - use crate::{ builtins::{function::OrdinaryFunction, BuiltInObject}, bytecompiler::{eval_declaration_instantiation_context, ByteCompiler}, context::intrinsics::Intrinsics, - environments::{CompileTimeEnvironment, Environment}, + environments::Environment, error::JsNativeError, js_string, object::JsObject, realm::Realm, string::StaticJsStrings, - vm::{CallFrame, CallFrameFlags, Opcode}, + vm::{CallFrame, CallFrameFlags, Constant, Opcode}, Context, JsArgs, JsResult, JsString, JsValue, }; -use boa_ast::operations::{contains, contains_arguments, ContainsSymbol}; +use boa_ast::{ + operations::{contains, contains_arguments, ContainsSymbol}, + scope::Scope, +}; use boa_gc::Gc; use boa_parser::{Parser, Source}; use boa_profiler::Profiler; @@ -62,7 +63,7 @@ impl Eval { /// [spec]: https://tc39.es/ecma262/#sec-eval-x fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Return ? PerformEval(x, false, false). - Self::perform_eval(args.get_or_undefined(0), false, false, context) + Self::perform_eval(args.get_or_undefined(0), false, None, false, context) } /// `19.2.1.1 PerformEval ( x, strictCaller, direct )` @@ -74,6 +75,7 @@ impl Eval { pub(crate) fn perform_eval( x: &JsValue, direct: bool, + lexical_scope: Option, mut strict: bool, context: &mut Context, ) -> JsResult { @@ -128,7 +130,7 @@ impl Eval { if strict { parser.set_strict(); } - let body = parser.parse_eval(direct, context.interner_mut())?; + let mut body = parser.parse_eval(direct, context.interner_mut())?; // 6. Let inFunction be false. // 7. Let inMethod be false. @@ -229,11 +231,18 @@ impl Eval { } }); - let var_environment = context.vm.environments.outer_function_environment().clone(); - let mut var_env = var_environment.compile_env(); + let (var_environment, mut variable_scope) = + if let Some(e) = context.vm.environments.outer_function_environment() { + (e.0, e.1) + } else { + ( + context.realm().environment().clone(), + context.realm().scope().clone(), + ) + }; - let lex_env = context.vm.environments.current_compile_environment(); - let lex_env = Rc::new(CompileTimeEnvironment::new(lex_env, strict)); + let lexical_scope = lexical_scope.unwrap_or(context.realm().scope().clone()); + let lexical_scope = Scope::new(lexical_scope, strict); let mut annex_b_function_names = Vec::new(); @@ -241,8 +250,12 @@ impl Eval { &mut annex_b_function_names, &body, strict, - if strict { &lex_env } else { &var_env }, - &lex_env, + if strict { + &lexical_scope + } else { + &variable_scope + }, + &lexical_scope, context, )?; @@ -252,8 +265,8 @@ impl Eval { js_string!("
"), body.strict(), false, - var_env.clone(), - lex_env.clone(), + variable_scope.clone(), + lexical_scope.clone(), false, false, context.interner_mut(), @@ -262,23 +275,36 @@ impl Eval { compiler.current_open_environments_count += 1; - let env_index = compiler.constants.len() as u32; + let scope_index = compiler.constants.len() as u32; compiler .constants - .push(crate::vm::Constant::CompileTimeEnvironment(lex_env.clone())); + .push(Constant::Scope(lexical_scope.clone())); - compiler.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + compiler.emit_with_varying_operand(Opcode::PushScope, scope_index); if strict { - var_env = lex_env.clone(); - compiler.variable_environment = lex_env.clone(); + variable_scope = lexical_scope.clone(); + compiler.variable_scope = lexical_scope.clone(); } #[cfg(feature = "annex-b")] { - compiler.annex_b_function_names = annex_b_function_names; + compiler + .annex_b_function_names + .clone_from(&annex_b_function_names); } - compiler.eval_declaration_instantiation(&body, strict, &var_env, &lex_env); + let bindings = body + .analyze_scope_eval( + strict, + &variable_scope, + &lexical_scope, + &annex_b_function_names, + compiler.interner(), + ) + .map_err(|e| JsNativeError::syntax().with_message(e))?; + + compiler.eval_declaration_instantiation(&body, strict, &variable_scope, bindings); + compiler.compile_statement_list(body.statements(), true, false); let code_block = Gc::new(compiler.finish()); diff --git a/core/engine/src/builtins/function/arguments.rs b/core/engine/src/builtins/function/arguments.rs index e863058223..83fd821287 100644 --- a/core/engine/src/builtins/function/arguments.rs +++ b/core/engine/src/builtins/function/arguments.rs @@ -79,6 +79,7 @@ impl UnmappedArguments { /// This struct stores all the data to access mapped function parameters in their environment. #[derive(Debug, Clone, Trace, Finalize)] pub(crate) struct MappedArguments { + #[unsafe_ignore_trace] binding_indices: Vec>, environment: Gc, } diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index a94022b2ad..8c4967e564 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -17,10 +17,7 @@ use crate::{ }, bytecompiler::FunctionCompiler, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - environments::{ - BindingLocatorEnvironment, EnvironmentStack, FunctionSlots, PrivateEnvironment, - ThisBindingStatus, - }, + environments::{EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus}, error::JsNativeError, js_string, native_function::NativeFunctionObject, @@ -45,6 +42,7 @@ use boa_ast::{ all_private_identifiers_valid, bound_names, contains, lexically_declared_names, ContainsSymbol, }, + scope::BindingLocatorScope, }; use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_interner::Sym; @@ -408,6 +406,8 @@ impl BuiltInFunctionObject { new_target.clone() }; + let strict = context.is_strict(); + let default = if r#async && generator { // 5. Else, // a. Assert: kind is async-generator. @@ -635,6 +635,14 @@ impl BuiltInFunctionObject { body }; + let mut function = + boa_ast::function::FunctionExpression::new(None, parameters, body, false); + if !function.analyze_scope(strict, context.realm().scope(), context.interner()) { + return Err(JsNativeError::syntax() + .with_message("failed to analyze function scope") + .into()); + } + let in_with = context.vm.environments.has_object_environment(); let code = FunctionCompiler::new() .name(js_string!("anonymous")) @@ -642,10 +650,11 @@ impl BuiltInFunctionObject { .r#async(r#async) .in_with(in_with) .compile( - ¶meters, - &body, - context.realm().environment().compile_env(), - context.realm().environment().compile_env(), + function.parameters(), + function.body(), + context.realm().scope().clone(), + context.realm().scope().clone(), + function.scopes(), context.interner_mut(), ); @@ -1008,12 +1017,9 @@ pub(crate) fn function_call( let mut last_env = 0; if code.has_binding_identifier() { - let index = context - .vm - .environments - .push_lexical(code.constant_compile_time_environment(last_env)); + let index = context.vm.environments.push_lexical(1); context.vm.environments.put_lexical_value( - BindingLocatorEnvironment::Stack(index), + BindingLocatorScope::Stack(index), 0, function_object.clone().into(), ); @@ -1021,7 +1027,7 @@ pub(crate) fn function_call( } context.vm.environments.push_function( - code.constant_compile_time_environment(last_env), + code.constant_scope(last_env), FunctionSlots::new(this, function_object.clone(), None), ); @@ -1101,12 +1107,9 @@ fn function_construct( let mut last_env = 0; if code.has_binding_identifier() { - let index = context - .vm - .environments - .push_lexical(code.constant_compile_time_environment(last_env)); + let index = context.vm.environments.push_lexical(1); context.vm.environments.put_lexical_value( - BindingLocatorEnvironment::Stack(index), + BindingLocatorScope::Stack(index), 0, this_function_object.clone().into(), ); @@ -1114,7 +1117,7 @@ fn function_construct( } context.vm.environments.push_function( - code.constant_compile_time_environment(last_env), + code.constant_scope(last_env), FunctionSlots::new( this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { ThisBindingStatus::Initialized(o.into()) diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 08dcdd7d54..ce4af91bad 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -15,6 +15,7 @@ use std::{borrow::Cow, iter::once}; +use boa_ast::scope::Scope; use boa_macros::{js_str, utf16}; use itertools::Itertools; @@ -111,15 +112,15 @@ impl Json { // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. let mut parser = Parser::new(Source::from_bytes(&script_string)); parser.set_json_parse(); - let script = parser.parse_script(context.interner_mut())?; + let script = parser.parse_script(&Scope::new_global(), context.interner_mut())?; let code_block = { let in_with = context.vm.environments.has_object_environment(); let mut compiler = ByteCompiler::new( js_string!("
"), script.strict(), true, - context.realm().environment().compile_env(), - context.realm().environment().compile_env(), + context.realm().scope().clone(), + context.realm().scope().clone(), false, false, context.interner_mut(), diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index 81bfd1178a..bfc6900229 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -10,6 +10,7 @@ use boa_ast::{ FunctionExpression, }, property::{MethodDefinitionKind, PropertyName}, + scope::Scope, Expression, }; use boa_gc::Gc; @@ -32,6 +33,7 @@ pub(crate) struct ClassSpec<'a> { constructor: Option<&'a FunctionExpression>, elements: &'a [ClassElement], has_binding_identifier: bool, + name_scope: Option<&'a Scope>, } impl<'a> From<&'a ClassDeclaration> for ClassSpec<'a> { @@ -42,6 +44,7 @@ impl<'a> From<&'a ClassDeclaration> for ClassSpec<'a> { constructor: class.constructor(), elements: class.elements(), has_binding_identifier: true, + name_scope: Some(class.name_scope()), } } } @@ -54,6 +57,7 @@ impl<'a> From<&'a ClassExpression> for ClassSpec<'a> { constructor: class.constructor(), elements: class.elements(), has_binding_identifier: class.name().is_some(), + name_scope: class.name_scope(), } } } @@ -75,13 +79,11 @@ impl ByteCompiler<'_> { .map_or(Sym::EMPTY_STRING, Identifier::sym) .to_js_string(self.interner()); - let old_lex_env = if class.has_binding_identifier { - let old_lex_env = self.lexical_environment.clone(); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - self.lexical_environment - .create_immutable_binding(class_name.clone(), true); - Some(old_lex_env) + let outer_scope = if let Some(name_scope) = class.name_scope { + let outer_scope = self.lexical_scope.clone(); + let scope_index = self.push_scope(name_scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); + Some(outer_scope) } else { None }; @@ -90,8 +92,8 @@ impl ByteCompiler<'_> { class_name.clone(), true, self.json_parse, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), false, false, self.interner, @@ -100,10 +102,9 @@ impl ByteCompiler<'_> { compiler.code_block_flags |= CodeBlockFlags::IS_CLASS_CONSTRUCTOR; - // Function environment - let _ = compiler.push_compile_environment(true); - if let Some(expr) = &class.constructor { + let _ = compiler.push_scope(expr.scopes().function_scope()); + compiler.length = expr.parameters().length(); compiler.params = expr.parameters().clone(); @@ -113,15 +114,20 @@ impl ByteCompiler<'_> { false, true, false, + expr.scopes(), ); - compiler.compile_statement_list(expr.body().statements(), false, false); + compiler.compile_statement_list(expr.body().statement_list(), false, false); compiler.emit_opcode(Opcode::PushUndefined); } else if class.super_ref.is_some() { + // We push an empty, unused function scope since the compiler expects a function scope. + let _ = compiler.push_scope(&Scope::new(compiler.lexical_scope.clone(), true)); compiler.emit_opcode(Opcode::SuperCallDerived); compiler.emit_opcode(Opcode::BindThisValue); } else { + // We push an empty, unused function scope since the compiler expects a function scope. + let _ = compiler.push_scope(&Scope::new(compiler.lexical_scope.clone(), true)); compiler.emit_opcode(Opcode::PushUndefined); } compiler.emit_opcode(Opcode::SetReturnValue); @@ -157,8 +163,12 @@ impl ByteCompiler<'_> { self.emit_u32(index); } } - ClassElement::PrivateFieldDefinition(name, _) - | ClassElement::PrivateStaticFieldDefinition(name, _) => { + ClassElement::PrivateFieldDefinition(field) => { + count += 1; + let index = self.get_or_insert_private_name(*field.name()); + self.emit_u32(index); + } + ClassElement::PrivateStaticFieldDefinition(name, _) => { count += 1; let index = self.get_or_insert_private_name(*name); self.emit_u32(index); @@ -171,7 +181,7 @@ impl ByteCompiler<'_> { let mut static_elements = Vec::new(); let mut static_field_name_count = 0; - if old_lex_env.is_some() { + if outer_scope.is_some() { self.emit_opcode(Opcode::Dup); self.emit_binding(BindingOpcode::InitLexical, class_name.clone()); } @@ -258,9 +268,9 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Swap); } } - ClassElement::FieldDefinition(name, field) => { + ClassElement::FieldDefinition(field) => { self.emit_opcode(Opcode::Dup); - match name { + match field.name() { PropertyName::Literal(name) => { self.emit_push_literal(Literal::String( self.interner().resolve_expect(*name).into_common(false), @@ -274,8 +284,8 @@ impl ByteCompiler<'_> { js_string!(), true, self.json_parse, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), false, false, self.interner, @@ -283,8 +293,8 @@ impl ByteCompiler<'_> { ); // Function environment - let _ = field_compiler.push_compile_environment(true); - let is_anonymous_function = if let Some(node) = field { + let _ = field_compiler.push_scope(field.scope()); + let is_anonymous_function = if let Some(node) = &field.field() { field_compiler.compile_expr(node, true); node.is_anonymous_function_definition() } else { @@ -303,22 +313,22 @@ impl ByteCompiler<'_> { &[Operand::Bool(is_anonymous_function)], ); } - ClassElement::PrivateFieldDefinition(name, field) => { + ClassElement::PrivateFieldDefinition(field) => { self.emit_opcode(Opcode::Dup); - let name_index = self.get_or_insert_private_name(*name); + let name_index = self.get_or_insert_private_name(*field.name()); let mut field_compiler = ByteCompiler::new( class_name.clone(), true, self.json_parse, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), false, false, self.interner, self.in_with, ); - let _ = field_compiler.push_compile_environment(true); - if let Some(node) = field { + let _ = field_compiler.push_scope(field.scope()); + if let Some(node) = field.field() { field_compiler.compile_expr(node, true); } else { field_compiler.emit_opcode(Opcode::PushUndefined); @@ -332,8 +342,8 @@ impl ByteCompiler<'_> { self.emit_with_varying_operand(Opcode::GetFunction, index); self.emit_with_varying_operand(Opcode::PushClassFieldPrivate, name_index); } - ClassElement::StaticFieldDefinition(name, field) => { - let name_index = match name { + ClassElement::StaticFieldDefinition(field) => { + let name_index = match field.name() { PropertyName::Literal(name) => { Some(self.get_or_insert_name((*name).into())) } @@ -351,15 +361,15 @@ impl ByteCompiler<'_> { class_name.clone(), true, self.json_parse, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), false, false, self.interner, self.in_with, ); - let _ = field_compiler.push_compile_environment(true); - let is_anonymous_function = if let Some(node) = field { + let _ = field_compiler.push_scope(field.scope()); + let is_anonymous_function = if let Some(node) = &field.field() { field_compiler.compile_expr(node, true); node.is_anonymous_function_definition() } else { @@ -389,29 +399,34 @@ impl ByteCompiler<'_> { let index = self.get_or_insert_private_name(*name); self.emit_with_varying_operand(Opcode::DefinePrivateField, index); } - ClassElement::StaticBlock(body) => { + ClassElement::StaticBlock(block) => { let mut compiler = ByteCompiler::new( Sym::EMPTY_STRING.to_js_string(self.interner()), true, false, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), false, false, self.interner, self.in_with, ); - let _ = compiler.push_compile_environment(true); + let _ = compiler.push_scope(block.scopes().function_scope()); compiler.function_declaration_instantiation( - body, + block.statements(), &FormalParameterList::default(), false, true, false, + block.scopes(), ); - compiler.compile_statement_list(body.statements(), false, false); + compiler.compile_statement_list( + block.statements().statement_list(), + false, + false, + ); let code = Gc::new(compiler.finish()); static_elements.push(StaticElement::StaticBlock(code)); @@ -459,9 +474,9 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Pop); - if let Some(old_lex_env) = old_lex_env { - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; + if let Some(outer_scope) = outer_scope { + self.pop_scope(); + self.lexical_scope = outer_scope; self.emit_opcode(Opcode::PopEnvironment); } diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index 439e7de5a2..e040c99a5d 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/core/engine/src/bytecompiler/declarations.rs @@ -1,13 +1,10 @@ -use std::rc::Rc; - use crate::{ bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind}, - environments::CompileTimeEnvironment, vm::{BindingOpcode, Opcode}, Context, JsNativeError, JsResult, }; use boa_ast::{ - declaration::{Binding, LexicalDeclaration, VariableList}, + declaration::Binding, expression::Identifier, function::{FormalParameterList, FunctionBody}, operations::{ @@ -15,8 +12,10 @@ use boa_ast::{ lexically_scoped_declarations, var_declared_names, var_scoped_declarations, LexicallyScopedDeclaration, VarScopedDeclaration, }, + scope::{FunctionScopes, Scope}, + scope_analyzer::EvalDeclarationBindings, visitor::NodeRef, - Declaration, Script, StatementListItem, + Script, }; use boa_interner::{JStrRef, Sym}; @@ -40,7 +39,7 @@ use super::{Operand, ToJsString}; pub(crate) fn global_declaration_instantiation_context( _annex_b_function_names: &mut Vec, _script: &Script, - _env: &Rc, + _env: &Scope, _context: &mut Context, ) -> JsResult<()> { Ok(()) @@ -59,7 +58,7 @@ pub(crate) fn global_declaration_instantiation_context( pub(crate) fn global_declaration_instantiation_context( annex_b_function_names: &mut Vec, script: &Script, - env: &Rc, + env: &Scope, context: &mut Context, ) -> JsResult<()> { // SKIP: 1. Let lexNames be the LexicallyDeclaredNames of script. @@ -202,8 +201,8 @@ pub(crate) fn eval_declaration_instantiation_context( #[allow(unused, clippy::ptr_arg)] annex_b_function_names: &mut Vec, body: &Script, #[allow(unused)] strict: bool, - #[allow(unused)] var_env: &Rc, - #[allow(unused)] lex_env: &Rc, + #[allow(unused)] var_env: &Scope, + #[allow(unused)] lex_env: &Scope, context: &mut Context, ) -> JsResult<()> { // SKIP: 3. If strict is false, then @@ -293,7 +292,7 @@ pub(crate) fn eval_declaration_instantiation_context( // 3. Assert: The following loop will terminate. // 4. Repeat, while thisEnv is not varEnv, - while this_env.environment_index() != lex_env.environment_index() { + while this_env.scope_index() != lex_env.scope_index() { let f = f.to_js_string(context.interner()); // a. If thisEnv is not an Object Environment Record, then @@ -381,29 +380,15 @@ impl ByteCompiler<'_> { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation - pub(crate) fn global_declaration_instantiation( - &mut self, - script: &Script, - env: &Rc, - ) { + pub(crate) fn global_declaration_instantiation(&mut self, script: &Script) { // 1. Let lexNames be the LexicallyDeclaredNames of script. let lex_names = lexically_declared_names(script); // 2. Let varNames be the VarDeclaredNames of script. - let var_names = var_declared_names(script); - // 3. For each element name of lexNames, do for name in lex_names { let name = name.to_js_string(self.interner()); - // Note: Our implementation differs from the spec here. - // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. - // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. - if env.has_binding(&name) { - self.emit_syntax_error("duplicate lexical declaration"); - return; - } - // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). let index = self.get_or_insert_string(name); self.emit_with_varying_operand(Opcode::HasRestrictedGlobalProperty, index); @@ -414,17 +399,6 @@ impl ByteCompiler<'_> { self.patch_jump(exit); } - // 4. For each element name of varNames, do - for name in var_names { - let name = name.to_js_string(self.interner()); - - // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. - if env.has_lex_binding(&name) { - self.emit_syntax_error("duplicate lexical declaration"); - return; - } - } - // 5. Let varDeclarations be the VarScopedDeclarations of script. // Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations. let var_declarations = var_scoped_declarations(script); @@ -502,67 +476,42 @@ impl ByteCompiler<'_> { } } - // NOTE: These steps depend on the global object are done before bytecode compilation. - // - // SKIP: 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. - // SKIP: 12. NOTE: Annex B.3.2.2 adds additional steps at this point. - // SKIP: 12. Perform the following steps: - // SKIP: a. Let strict be IsStrict of script. - // SKIP: b. If strict is false, then - - // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. - // 14. Let privateEnv be null. - // 15. For each element d of lexDeclarations, do - for statement in &**script.statements() { - // a. NOTE: 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 ? env.CreateImmutableBinding(dn, true). - // ii. Else, - // 1. Perform ? env.CreateMutableBinding(dn, false). - if let StatementListItem::Declaration(declaration) = statement { - match declaration { - Declaration::ClassDeclaration(class) => { - for name in bound_names(class) { - let name = name.to_js_string(self.interner()); - env.create_mutable_binding(name, false); - } - } - Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { - for name in bound_names(declaration) { - let name = name.to_js_string(self.interner()); - env.create_mutable_binding(name, false); - } - } - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { - for name in bound_names(declaration) { - let name = name.to_js_string(self.interner()); - env.create_immutable_binding(name, true); - } - } - _ => {} - } - } - } - // 16. For each Parse Node f of functionsToInitialize, do for function in functions_to_initialize { // a. Let fn be the sole element of the BoundNames of f. - let (name, generator, r#async, parameters, body) = match &function { - VarScopedDeclaration::FunctionDeclaration(f) => { - (f.name(), false, false, f.parameters(), f.body()) - } - VarScopedDeclaration::GeneratorDeclaration(f) => { - (f.name(), true, false, f.parameters(), f.body()) - } - VarScopedDeclaration::AsyncFunctionDeclaration(f) => { - (f.name(), false, true, f.parameters(), f.body()) - } - VarScopedDeclaration::AsyncGeneratorDeclaration(f) => { - (f.name(), true, true, f.parameters(), f.body()) - } + let (name, generator, r#async, parameters, body, scopes) = match &function { + VarScopedDeclaration::FunctionDeclaration(f) => ( + f.name(), + false, + false, + f.parameters(), + f.body(), + f.scopes().clone(), + ), + VarScopedDeclaration::GeneratorDeclaration(f) => ( + f.name(), + true, + false, + f.parameters(), + f.body(), + f.scopes().clone(), + ), + VarScopedDeclaration::AsyncFunctionDeclaration(f) => ( + f.name(), + false, + true, + f.parameters(), + f.body(), + f.scopes().clone(), + ), + VarScopedDeclaration::AsyncGeneratorDeclaration(f) => ( + f.name(), + true, + true, + f.parameters(), + f.body(), + f.scopes().clone(), + ), VarScopedDeclaration::VariableDeclaration(_) => continue, }; @@ -572,12 +521,12 @@ impl ByteCompiler<'_> { .r#async(r#async) .strict(self.strict()) .in_with(self.in_with) - .binding_identifier(None) .compile( parameters, body, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), + &scopes, self.interner, ); @@ -614,51 +563,13 @@ impl ByteCompiler<'_> { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation - pub(crate) fn block_declaration_instantiation<'a, N>( - &mut self, - block: &'a N, - env: &Rc, - ) where + pub(crate) fn block_declaration_instantiation<'a, N>(&mut self, block: &'a N) + where &'a N: Into>, { // 1. Let declarations be the LexicallyScopedDeclarations of code. let declarations = lexically_scoped_declarations(block); - // 2. Let privateEnv be the running execution context's PrivateEnvironment. - // Note: Private environments are currently handled differently. - - // 3. For each element d of declarations, do - for d in &declarations { - // i. If IsConstantDeclaration of d is true, then - if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(d)) = d - { - // a. For each element dn of the BoundNames of d, do - for dn in bound_names::<'_, VariableList>(d) { - // 1. Perform ! env.CreateImmutableBinding(dn, true). - let dn = dn.to_js_string(self.interner()); - env.create_immutable_binding(dn, true); - } - } - // ii. Else, - else { - // a. For each element dn of the BoundNames of d, do - for dn in d.bound_names() { - let dn = dn.to_js_string(self.interner()); - - #[cfg(not(feature = "annex-b"))] - // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. - 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); - } - } - } - } - // 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. @@ -697,65 +608,14 @@ impl ByteCompiler<'_> { pub(crate) fn eval_declaration_instantiation( &mut self, body: &Script, - strict: bool, - var_env: &Rc, - lex_env: &Rc, + #[allow(unused_variables)] strict: bool, + var_env: &Scope, + bindings: EvalDeclarationBindings, ) { // 2. Let varDeclarations be the VarScopedDeclarations of body. let var_declarations = var_scoped_declarations(body); - // 3. If strict is false, then - if !strict { - // 1. Let varNames be the VarDeclaredNames of body. - let var_names = var_declared_names(body); - - // a. If varEnv is a Global Environment Record, then - if var_env.is_global() { - // i. For each element name of varNames, do - for name in &var_names { - let name = name.to_js_string(self.interner()); - - // 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. - // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration. - if var_env.has_lex_binding(&name) { - self.emit_syntax_error("duplicate lexical declaration"); - return; - } - } - } - - // b. Let thisEnv be lexEnv. - let mut this_env = lex_env.clone(); - - // c. Assert: The following loop will terminate. - // d. Repeat, while thisEnv is not varEnv, - while this_env.environment_index() != var_env.environment_index() { - // 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. - // 2. For each element name of varNames, do - for name in &var_names { - let name = self.interner().resolve_expect(name.sym()).utf16().into(); - - // a. If ! thisEnv.HasBinding(name) is true, then - if this_env.has_binding(&name) { - // i. Throw a SyntaxError exception. - // ii. NOTE: Annex B.3.4 defines alternate semantics for the above step. - let msg = format!("variable declaration {} in eval function already exists as a lexical variable", name.to_std_string_escaped()); - self.emit_syntax_error(&msg); - return; - } - // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration. - } - - // ii. Set thisEnv to thisEnv.[[OuterEnv]]. - if let Some(outer) = this_env.outer() { - this_env = outer; - } else { - break; - } - } - } + // SKIP: 3. If strict is false, then // NOTE: These steps depend on the current environment state are done before bytecode compilation, // in `eval_declaration_instantiation_context`. @@ -820,18 +680,14 @@ impl ByteCompiler<'_> { // NOTE: This diviates from the specification, we split the first part of defining the annex-b names // in `eval_declaration_instantiation_context`, because it depends on the context. if !var_env.is_global() { - for name in self.annex_b_function_names.clone() { - let f = name.to_js_string(self.interner()); + for binding in bindings.new_annex_b_function_names { // i. Let bindingExists be ! varEnv.HasBinding(F). // ii. If bindingExists is false, then - if !var_env.has_binding(&f) { - // i. Perform ! varEnv.CreateMutableBinding(F, true). - // ii. Perform ! varEnv.InitializeBinding(F, undefined). - let binding = var_env.create_mutable_binding(f, true); - let index = self.get_or_insert_binding(binding); - self.emit_opcode(Opcode::PushUndefined); - self.emit_with_varying_operand(Opcode::DefInitVar, index); - } + // i. Perform ! varEnv.CreateMutableBinding(F, true). + // ii. Perform ! varEnv.InitializeBinding(F, undefined). + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit_binding_access(Opcode::DefInitVar, &index); } } } @@ -877,54 +733,43 @@ impl ByteCompiler<'_> { // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. // 16. For each element d of lexDeclarations, do - for statement in &**body.statements() { - // a. NOTE: 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). - // ii. Else, - // 1. Perform ? lexEnv.CreateMutableBinding(dn, false). - if let StatementListItem::Declaration(declaration) = statement { - match declaration { - Declaration::ClassDeclaration(class) => { - for name in bound_names(class) { - let name = name.to_js_string(self.interner()); - lex_env.create_mutable_binding(name, false); - } - } - Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { - for name in bound_names(declaration) { - let name = name.to_js_string(self.interner()); - lex_env.create_mutable_binding(name, false); - } - } - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { - for name in bound_names(declaration) { - let name = name.to_js_string(self.interner()); - lex_env.create_immutable_binding(name, true); - } - } - _ => {} - } - } - } // 17. For each Parse Node f of functionsToInitialize, do for function in functions_to_initialize { // a. Let fn be the sole element of the BoundNames of f. - let (name, generator, r#async, parameters, body) = match &function { - VarScopedDeclaration::FunctionDeclaration(f) => { - (f.name(), false, false, f.parameters(), f.body()) - } - VarScopedDeclaration::GeneratorDeclaration(f) => { - (f.name(), true, false, f.parameters(), f.body()) - } - VarScopedDeclaration::AsyncFunctionDeclaration(f) => { - (f.name(), false, true, f.parameters(), f.body()) - } - VarScopedDeclaration::AsyncGeneratorDeclaration(f) => { - (f.name(), true, true, f.parameters(), f.body()) - } + let (name, generator, r#async, parameters, body, scopes) = match &function { + VarScopedDeclaration::FunctionDeclaration(f) => ( + f.name(), + false, + false, + f.parameters(), + f.body(), + f.scopes().clone(), + ), + VarScopedDeclaration::GeneratorDeclaration(f) => ( + f.name(), + true, + false, + f.parameters(), + f.body(), + f.scopes().clone(), + ), + VarScopedDeclaration::AsyncFunctionDeclaration(f) => ( + f.name(), + false, + true, + f.parameters(), + f.body(), + f.scopes().clone(), + ), + VarScopedDeclaration::AsyncGeneratorDeclaration(f) => ( + f.name(), + true, + true, + f.parameters(), + f.body(), + f.scopes().clone(), + ), VarScopedDeclaration::VariableDeclaration(_) => { continue; } @@ -936,12 +781,13 @@ impl ByteCompiler<'_> { .r#async(r#async) .strict(self.strict()) .in_with(self.in_with) - .binding_identifier(Some(name.sym().to_js_string(self.interner()))) + .name_scope(None) .compile( parameters, body, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), + &scopes, self.interner, ); @@ -966,25 +812,24 @@ impl ByteCompiler<'_> { let index = self.push_function_to_constants(code); self.emit_with_varying_operand(Opcode::GetFunction, index); - let name = name.to_js_string(self.interner()); - // i. Let bindingExists be ! varEnv.HasBinding(fn). - let binding_exists = var_env.has_binding(&name); + let (binding, binding_exists) = bindings + .new_function_names + .get(&name) + .expect("binding must exist"); // ii. If bindingExists is false, then // iii. Else, - if binding_exists { + if *binding_exists { // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). - let binding = var_env.set_mutable_binding(name).expect("must not fail"); - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); + let index = self.get_or_insert_binding(binding.clone()); + self.emit_binding_access(Opcode::SetName, &index); } else { // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. // 2. Perform ! varEnv.CreateMutableBinding(fn, true). // 3. Perform ! varEnv.InitializeBinding(fn, fo). - let binding = var_env.create_mutable_binding(name, !strict); - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::DefInitVar, index); + let index = self.get_or_insert_binding(binding.clone()); + self.emit_binding_access(Opcode::DefInitVar, &index); } } } @@ -1001,24 +846,17 @@ impl ByteCompiler<'_> { &[Operand::Bool(true), Operand::Varying(index)], ); } - // b. Else, - else { - let name = name.to_js_string(self.interner()); - - // i. Let bindingExists be ! varEnv.HasBinding(vn). - let binding_exists = var_env.has_binding(&name); - - // ii. If bindingExists is false, then - if !binding_exists { - // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. - // 2. Perform ! varEnv.CreateMutableBinding(vn, true). - // 3. Perform ! varEnv.InitializeBinding(vn, undefined). - let binding = var_env.create_mutable_binding(name, true); - let index = self.get_or_insert_binding(binding); - self.emit_opcode(Opcode::PushUndefined); - self.emit_with_varying_operand(Opcode::DefInitVar, index); - } - } + } + // 18.b + for binding in bindings.new_var_names { + // i. Let bindingExists be ! varEnv.HasBinding(vn). + // ii. If bindingExists is false, then + // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. + // 2. Perform ! varEnv.CreateMutableBinding(vn, true). + // 3. Perform ! varEnv.InitializeBinding(vn, undefined). + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit_binding_access(Opcode::DefInitVar, &index); } // 19. Return unused. @@ -1037,6 +875,7 @@ impl ByteCompiler<'_> { arrow: bool, strict: bool, generator: bool, + scopes: &FunctionScopes, ) { // 1. Let calleeContext be the running execution context. // 2. Let code be func.[[ECMAScriptCode]]. @@ -1123,25 +962,13 @@ impl ByteCompiler<'_> { } } - // 19. If strict is true or hasParameterExpressions is false, then - if strict || !has_parameter_expressions { - // a. NOTE: Only a single Environment Record is needed for the parameters, - // since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. - // b. Let env be the LexicalEnvironment of calleeContext. + // 19-20 + if let Some(scope) = scopes.parameters_eval_scope() { + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); } - // 20. Else, - else { - // a. NOTE: A separate Environment Record is needed to ensure that bindings created by - // direct eval calls in the formal parameter list are outside the environment where parameters are declared. - // b. Let calleeEnv be the LexicalEnvironment of calleeContext. - // c. Let env be NewDeclarativeEnvironment(calleeEnv). - // d. Assert: The VariableEnvironment of calleeContext is calleeEnv. - // e. Set the LexicalEnvironment of calleeContext to env. - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - }; - let env = self.lexical_environment.clone(); + let scope = self.lexical_scope.clone(); // 22. If argumentsObjectNeeded is true, then // @@ -1165,44 +992,10 @@ impl ByteCompiler<'_> { self.emitted_mapped_arguments_object_opcode = true; } - // 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. - env.create_immutable_binding(arguments.clone(), false); - } - // d. Else, - else { - // i. Perform ! env.CreateMutableBinding("arguments", false). - env.create_mutable_binding(arguments.clone(), false); - } - // e. Perform ! env.InitializeBinding("arguments", ao). self.emit_binding(BindingOpcode::InitLexical, arguments); } - // 21. For each String paramName of parameterNames, do - for param_name in ¶meter_names { - let param_name = param_name.to_js_string(self.interner()); - - // a. Let alreadyDeclared be ! env.HasBinding(paramName). - let already_declared = env.has_binding(¶m_name); - - // b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict - // functions that do not have parameter default values or rest parameters. - - // c. If alreadyDeclared is false, then - if !already_declared { - // i. Perform ! env.CreateMutableBinding(paramName, false). - env.create_mutable_binding(param_name, false); - - // Note: These steps are not necessary in our implementation. - // ii. If hasDuplicates is true, then - // 1. Perform ! env.InitializeBinding(paramName, undefined). - } - } - // 22. If argumentsObjectNeeded is true, then if arguments_object_needed { // MOVED: a-e. @@ -1257,82 +1050,87 @@ impl ByteCompiler<'_> { // 27. If hasParameterExpressions is false, then // 28. Else, #[allow(unused_variables, unused_mut)] - let (mut instantiated_var_names, mut var_env) = 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. - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - - let mut var_env = self.lexical_environment.clone(); - - // d. Let instantiatedVarNames be a new empty List. - let mut instantiated_var_names = Vec::new(); - - // e. For each element n of varNames, do - for n in var_names { - // i. If instantiatedVarNames does not contain n, then - if !instantiated_var_names.contains(&n) { - // 1. Append n to instantiatedVarNames. - instantiated_var_names.push(n); - - let n_string = n.to_js_string(self.interner()); - - // 2. Perform ! varEnv.CreateMutableBinding(n, false). - let binding = var_env.create_mutable_binding(n_string.clone(), false); - - // 3. If parameterBindings does not contain n, or if functionNames contains n, then - if !parameter_bindings.contains(&n) || function_names.contains(&n) { - // a. Let initialValue be undefined. - self.emit_opcode(Opcode::PushUndefined); - } - // 4. Else, - else { - // a. Let initialValue be ! env.GetBindingValue(n, false). - let binding = env.get_binding(&n_string).expect("must have binding"); - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::GetName, index); - } + let (mut instantiated_var_names, mut variable_scope) = + if let Some(scope) = scopes.parameters_scope() { + // 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. + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); + + let mut variable_scope = self.lexical_scope.clone(); + + // d. Let instantiatedVarNames be a new empty List. + let mut instantiated_var_names = Vec::new(); + + // e. For each element n of varNames, do + for n in var_names { + // i. If instantiatedVarNames does not contain n, then + if !instantiated_var_names.contains(&n) { + // 1. Append n to instantiatedVarNames. + instantiated_var_names.push(n); + + let n_string = n.to_js_string(self.interner()); + + // 2. Perform ! varEnv.CreateMutableBinding(n, false). + let binding = variable_scope + .get_binding_reference(&n_string) + .expect("must have binding"); + + // 3. If parameterBindings does not contain n, or if functionNames contains n, then + if !parameter_bindings.contains(&n) || function_names.contains(&n) { + // a. Let initialValue be undefined. + self.emit_opcode(Opcode::PushUndefined); + } + // 4. Else, + else { + // a. Let initialValue be ! env.GetBindingValue(n, false). + let binding = scope + .get_binding_reference(&n_string) + .expect("must have binding"); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::GetName, &index); + } - // 5. Perform ! varEnv.InitializeBinding(n, initialValue). - let index = self.get_or_insert_binding(binding); - self.emit_opcode(Opcode::PushUndefined); - self.emit_with_varying_operand(Opcode::DefInitVar, index); + // 5. Perform ! varEnv.InitializeBinding(n, initialValue). + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit_binding_access(Opcode::DefInitVar, &index); - // 6. NOTE: A var with the same name as a formal parameter initially has - // the same value as the corresponding initialized parameter. + // 6. NOTE: A var with the same name as a formal parameter initially has + // the same value as the corresponding initialized parameter. + } } - } - - (instantiated_var_names, var_env) - } 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. - let mut instantiated_var_names = parameter_bindings; - - // c. For each element n of varNames, do - for n in var_names { - // i. If instantiatedVarNames does not contain n, then - if !instantiated_var_names.contains(&n) { - // 1. Append n to instantiatedVarNames. - instantiated_var_names.push(n); - let n = n.to_js_string(self.interner()); - - // 2. Perform ! env.CreateMutableBinding(n, false). - // 3. Perform ! env.InitializeBinding(n, undefined). - let binding = env.create_mutable_binding(n, true); - let index = self.get_or_insert_binding(binding); - self.emit_opcode(Opcode::PushUndefined); - self.emit_with_varying_operand(Opcode::DefInitVar, index); + (instantiated_var_names, variable_scope) + } 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. + let mut instantiated_var_names = parameter_bindings; + + // c. For each element n of varNames, do + for n in var_names { + // i. If instantiatedVarNames does not contain n, then + if !instantiated_var_names.contains(&n) { + // 1. Append n to instantiatedVarNames. + instantiated_var_names.push(n); + + let n = n.to_js_string(self.interner()); + + // 2. Perform ! env.CreateMutableBinding(n, false). + // 3. Perform ! env.InitializeBinding(n, undefined). + let binding = scope.get_binding_reference(&n).expect("binding must exist"); + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit_binding_access(Opcode::DefInitVar, &index); + } } - } - // d. Let varEnv be env. - (instantiated_var_names, env) - }; + // d. Let varEnv be env. + (instantiated_var_names, scope) + }; // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. // 29. If strict is false, then @@ -1355,10 +1153,12 @@ impl ByteCompiler<'_> { // a. Perform ! varEnv.CreateMutableBinding(F, false). // b. Perform ! varEnv.InitializeBinding(F, undefined). - let binding = var_env.create_mutable_binding(f_string, false); + let binding = variable_scope + .get_binding_reference(&f_string) + .expect("binding must exist"); let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::PushUndefined); - self.emit_with_varying_operand(Opcode::DefInitVar, index); + self.emit_binding_access(Opcode::DefInitVar, &index); // c. Append F to instantiatedVarNames. instantiated_var_names.push(f); @@ -1376,58 +1176,10 @@ impl ByteCompiler<'_> { } } - // 30. If strict is false, then - // 31. Else, - let lex_env = if strict { - // a. Let lexEnv be varEnv. - var_env - } else { - // 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. - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - self.lexical_environment.clone() - }; - - // 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. - // 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). - // ii. Else, - // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). - for statement in &**body.statements() { - if let StatementListItem::Declaration(declaration) = statement { - match declaration { - Declaration::ClassDeclaration(class) => { - for name in bound_names(class) { - let name = name.to_js_string(self.interner()); - lex_env.create_mutable_binding(name, false); - } - } - Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { - for name in bound_names(declaration) { - let name = name.to_js_string(self.interner()); - lex_env.create_mutable_binding(name, false); - } - } - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { - for name in bound_names(declaration) { - let name = name.to_js_string(self.interner()); - lex_env.create_immutable_binding(name, true); - } - } - _ => {} - } - } + // 30-31 + if let Some(scope) = scopes.lexical_scope() { + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); } // 35. Let privateEnv be the PrivateEnvironment of calleeContext. diff --git a/core/engine/src/bytecompiler/env.rs b/core/engine/src/bytecompiler/env.rs index 8a61adee20..59b65184e3 100644 --- a/core/engine/src/bytecompiler/env.rs +++ b/core/engine/src/bytecompiler/env.rs @@ -1,33 +1,28 @@ +use boa_ast::scope::Scope; + use super::ByteCompiler; -use crate::environments::CompileTimeEnvironment; -use std::rc::Rc; impl ByteCompiler<'_> { - /// Push either a new declarative or function environment on the compile time environment stack. + /// Push either a new declarative or function scope on the environment stack. #[must_use] - pub(crate) fn push_compile_environment(&mut self, function_scope: bool) -> u32 { + pub(crate) fn push_scope(&mut self, scope: &Scope) -> u32 { self.current_open_environments_count += 1; - let env = Rc::new(CompileTimeEnvironment::new( - self.lexical_environment.clone(), - function_scope, - )); - let index = self.constants.len() as u32; self.constants - .push(crate::vm::Constant::CompileTimeEnvironment(env.clone())); + .push(crate::vm::Constant::Scope(scope.clone())); - if function_scope { - self.variable_environment = env.clone(); + if scope.is_function() { + self.variable_scope = scope.clone(); } - self.lexical_environment = env; + self.lexical_scope = scope.clone(); index } - /// Pops the top compile time environment and returns its index in the compile time environments array. - pub(crate) fn pop_compile_environment(&mut self) { + /// Pops the top scope. + pub(crate) fn pop_scope(&mut self) { self.current_open_environments_count -= 1; } } diff --git a/core/engine/src/bytecompiler/expression/assign.rs b/core/engine/src/bytecompiler/expression/assign.rs index 12d3644dc7..f6743a9cf9 100644 --- a/core/engine/src/bytecompiler/expression/assign.rs +++ b/core/engine/src/bytecompiler/expression/assign.rs @@ -1,11 +1,13 @@ use crate::{ bytecompiler::{Access, ByteCompiler, Operand, ToJsString}, - environments::BindingLocatorError, vm::{BindingOpcode, Opcode}, }; -use boa_ast::expression::{ - access::{PropertyAccess, PropertyAccessField}, - operator::{assign::AssignOp, Assign}, +use boa_ast::{ + expression::{ + access::{PropertyAccess, PropertyAccessField}, + operator::{assign::AssignOp, Assign}, + }, + scope::BindingLocatorError, }; impl ByteCompiler<'_> { @@ -57,15 +59,14 @@ impl ByteCompiler<'_> { Access::Variable { name } => { let name = name.to_js_string(self.interner()); - let binding = self - .lexical_environment - .get_identifier_reference(name.clone()); - let index = self.get_or_insert_binding(binding.locator()); + let binding = self.lexical_scope.get_identifier_reference(name.clone()); + let is_lexical = binding.is_lexical(); + let index = self.get_or_insert_binding(binding); - if binding.is_lexical() { - self.emit_with_varying_operand(Opcode::GetName, index); + if is_lexical { + self.emit_binding_access(Opcode::GetName, &index); } else { - self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); + self.emit_binding_access(Opcode::GetNameAndLocator, &index); } if short_circuit { @@ -78,11 +79,11 @@ impl ByteCompiler<'_> { if use_expr { self.emit_opcode(Opcode::Dup); } - if binding.is_lexical() { - match self.lexical_environment.set_mutable_binding(name.clone()) { + if is_lexical { + match self.lexical_scope.set_mutable_binding(name.clone()) { Ok(binding) => { let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); + self.emit_binding_access(Opcode::SetName, &index); } Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_string(name); @@ -93,7 +94,7 @@ impl ByteCompiler<'_> { } } } else { - self.emit_opcode(Opcode::SetNameByLocator); + self.emit_binding_access(Opcode::SetNameByLocator, &index); } } Access::Property { access } => match access { diff --git a/core/engine/src/bytecompiler/expression/object_literal.rs b/core/engine/src/bytecompiler/expression/object_literal.rs index faa165ec4c..803e5e54a7 100644 --- a/core/engine/src/bytecompiler/expression/object_literal.rs +++ b/core/engine/src/bytecompiler/expression/object_literal.rs @@ -56,7 +56,7 @@ impl ByteCompiler<'_> { MethodKind::Set => Opcode::SetPropertySetterByName, MethodKind::Ordinary => Opcode::DefineOwnPropertyByName, }; - self.object_method((m, *name).into(), kind); + self.object_method(m.into(), kind); self.emit_opcode(Opcode::SetHomeObject); let index = self.get_or_insert_name((*name).into()); self.emit_with_varying_operand(opcode, index); diff --git a/core/engine/src/bytecompiler/expression/unary.rs b/core/engine/src/bytecompiler/expression/unary.rs index d5e380afce..298e80ae4c 100644 --- a/core/engine/src/bytecompiler/expression/unary.rs +++ b/core/engine/src/bytecompiler/expression/unary.rs @@ -28,11 +28,9 @@ impl ByteCompiler<'_> { match unary.target().flatten() { Expression::Identifier(identifier) => { let identifier = identifier.to_js_string(self.interner()); - let binding = self - .lexical_environment - .get_identifier_reference(identifier); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::GetNameOrUndefined, index); + let binding = self.lexical_scope.get_identifier_reference(identifier); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::GetNameOrUndefined, &index); } expr => self.compile_expr(expr, true), } diff --git a/core/engine/src/bytecompiler/expression/update.rs b/core/engine/src/bytecompiler/expression/update.rs index 7fd0b986ca..dd56788d64 100644 --- a/core/engine/src/bytecompiler/expression/update.rs +++ b/core/engine/src/bytecompiler/expression/update.rs @@ -1,11 +1,13 @@ use crate::{ bytecompiler::{Access, ByteCompiler, Operand, ToJsString}, - environments::BindingLocatorError, vm::Opcode, }; -use boa_ast::expression::{ - access::{PropertyAccess, PropertyAccessField}, - operator::{update::UpdateOp, Update}, +use boa_ast::{ + expression::{ + access::{PropertyAccess, PropertyAccessField}, + operator::{update::UpdateOp, Update}, + }, + scope::BindingLocatorError, }; impl ByteCompiler<'_> { @@ -24,15 +26,14 @@ impl ByteCompiler<'_> { match Access::from_update_target(update.target()) { Access::Variable { name } => { let name = name.to_js_string(self.interner()); - let binding = self - .lexical_environment - .get_identifier_reference(name.clone()); - let index = self.get_or_insert_binding(binding.locator()); + let binding = self.lexical_scope.get_identifier_reference(name.clone()); + let is_lexical = binding.is_lexical(); + let index = self.get_or_insert_binding(binding); - if binding.is_lexical() { - self.emit_with_varying_operand(Opcode::GetName, index); + if is_lexical { + self.emit_binding_access(Opcode::GetName, &index); } else { - self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); + self.emit_binding_access(Opcode::GetNameAndLocator, &index); } self.emit_opcode(opcode); @@ -42,11 +43,11 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Dup); } - if binding.is_lexical() { - match self.lexical_environment.set_mutable_binding(name.clone()) { + if is_lexical { + match self.lexical_scope.set_mutable_binding(name.clone()) { Ok(binding) => { let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); + self.emit_binding_access(Opcode::SetName, &index); } Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_string(name); @@ -57,7 +58,7 @@ impl ByteCompiler<'_> { } } } else { - self.emit_opcode(Opcode::SetNameByLocator); + self.emit_binding_access(Opcode::SetNameByLocator, &index); } } Access::Property { access } => match access { diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index b06fcf7006..3fef0a612d 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -1,14 +1,14 @@ -use std::rc::Rc; - use crate::{ builtins::function::ThisMode, bytecompiler::ByteCompiler, - environments::CompileTimeEnvironment, js_string, vm::{CodeBlock, CodeBlockFlags, Opcode}, JsString, }; -use boa_ast::function::{FormalParameterList, FunctionBody}; +use boa_ast::{ + function::{FormalParameterList, FunctionBody}, + scope::{FunctionScopes, Scope}, +}; use boa_gc::Gc; use boa_interner::Interner; @@ -23,7 +23,7 @@ pub(crate) struct FunctionCompiler { arrow: bool, method: bool, in_with: bool, - binding_identifier: Option, + name_scope: Option, } impl FunctionCompiler { @@ -37,7 +37,7 @@ impl FunctionCompiler { arrow: false, method: false, in_with: false, - binding_identifier: None, + name_scope: None, } } @@ -81,9 +81,9 @@ impl FunctionCompiler { self } - /// Indicate if the function has a binding identifier. - pub(crate) fn binding_identifier(mut self, binding_identifier: Option) -> Self { - self.binding_identifier = binding_identifier; + /// Provide the name scope of the function. + pub(crate) fn name_scope(mut self, name_scope: Option) -> Self { + self.name_scope = name_scope; self } @@ -98,8 +98,9 @@ impl FunctionCompiler { mut self, parameters: &FormalParameterList, body: &FunctionBody, - variable_environment: Rc, - lexical_environment: Rc, + variable_environment: Scope, + lexical_environment: Scope, + scopes: &FunctionScopes, interner: &mut Interner, ) -> Gc { self.strict = self.strict || body.strict(); @@ -127,16 +128,12 @@ impl FunctionCompiler { compiler.this_mode = ThisMode::Lexical; } - if let Some(binding_identifier) = self.binding_identifier { + if let Some(scope) = self.name_scope { compiler.code_block_flags |= CodeBlockFlags::HAS_BINDING_IDENTIFIER; - let _ = compiler.push_compile_environment(false); - compiler - .lexical_environment - .create_immutable_binding(binding_identifier, self.strict); + let _ = compiler.push_scope(&scope); } - // Function environment - let _ = compiler.push_compile_environment(true); + let _ = compiler.push_scope(scopes.function_scope()); // Taken from: // - 15.9.3 Runtime Semantics: EvaluateAsyncConciseBody: @@ -169,6 +166,7 @@ impl FunctionCompiler { self.arrow, self.strict, self.generator, + scopes, ); // Taken from: @@ -184,10 +182,12 @@ impl FunctionCompiler { } } - compiler.compile_statement_list(body.statements(), false, false); + compiler.compile_statement_list(body.statement_list(), false, false); compiler.params = parameters.clone(); - Gc::new(compiler.finish()) + let code = compiler.finish(); + + Gc::new(code) } } diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 74073baf20..da428e4e2d 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -12,11 +12,10 @@ mod register; mod statement; mod utils; -use std::{cell::Cell, rc::Rc}; +use std::cell::Cell; use crate::{ builtins::function::{arguments::MappedArguments, ThisMode}, - environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment}, js_string, vm::{ BindingOpcode, CallFrame, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, @@ -41,6 +40,7 @@ use boa_ast::{ operations::returns_value, pattern::Pattern, property::MethodDefinitionKind, + scope::{BindingLocator, BindingLocatorError, FunctionScopes, IdentifierReference, Scope}, Declaration, Expression, Statement, StatementList, StatementListItem, }; use boa_gc::Gc; @@ -120,7 +120,8 @@ pub(crate) struct FunctionSpec<'a> { pub(crate) name: Option, parameters: &'a FormalParameterList, body: &'a FunctionBody, - pub(crate) has_binding_identifier: bool, + pub(crate) scopes: &'a FunctionScopes, + pub(crate) name_scope: Option<&'a Scope>, } impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> { @@ -130,7 +131,8 @@ impl<'a> From<&'a FunctionDeclaration> for FunctionSpec<'a> { name: Some(function.name()), parameters: function.parameters(), body: function.body(), - has_binding_identifier: true, + scopes: function.scopes(), + name_scope: None, } } } @@ -142,7 +144,8 @@ impl<'a> From<&'a GeneratorDeclaration> for FunctionSpec<'a> { name: Some(function.name()), parameters: function.parameters(), body: function.body(), - has_binding_identifier: true, + scopes: function.scopes(), + name_scope: None, } } } @@ -154,7 +157,8 @@ impl<'a> From<&'a AsyncFunctionDeclaration> for FunctionSpec<'a> { name: Some(function.name()), parameters: function.parameters(), body: function.body(), - has_binding_identifier: true, + scopes: function.scopes(), + name_scope: None, } } } @@ -166,7 +170,8 @@ impl<'a> From<&'a AsyncGeneratorDeclaration> for FunctionSpec<'a> { name: Some(function.name()), parameters: function.parameters(), body: function.body(), - has_binding_identifier: true, + scopes: function.scopes(), + name_scope: None, } } } @@ -178,7 +183,8 @@ impl<'a> From<&'a FunctionExpression> for FunctionSpec<'a> { name: function.name(), parameters: function.parameters(), body: function.body(), - has_binding_identifier: function.has_binding_identifier(), + scopes: function.scopes(), + name_scope: function.name_scope(), } } } @@ -190,7 +196,8 @@ impl<'a> From<&'a ArrowFunction> for FunctionSpec<'a> { name: function.name(), parameters: function.parameters(), body: function.body(), - has_binding_identifier: false, + scopes: function.scopes(), + name_scope: None, } } } @@ -202,7 +209,8 @@ impl<'a> From<&'a AsyncArrowFunction> for FunctionSpec<'a> { name: function.name(), parameters: function.parameters(), body: function.body(), - has_binding_identifier: false, + scopes: function.scopes(), + name_scope: None, } } } @@ -214,7 +222,8 @@ impl<'a> From<&'a AsyncFunctionExpression> for FunctionSpec<'a> { name: function.name(), parameters: function.parameters(), body: function.body(), - has_binding_identifier: function.has_binding_identifier(), + scopes: function.scopes(), + name_scope: function.name_scope(), } } } @@ -226,7 +235,8 @@ impl<'a> From<&'a GeneratorExpression> for FunctionSpec<'a> { name: function.name(), parameters: function.parameters(), body: function.body(), - has_binding_identifier: function.has_binding_identifier(), + scopes: function.scopes(), + name_scope: function.name_scope(), } } } @@ -238,7 +248,8 @@ impl<'a> From<&'a AsyncGeneratorExpression> for FunctionSpec<'a> { name: function.name(), parameters: function.parameters(), body: function.body(), - has_binding_identifier: function.has_binding_identifier(), + scopes: function.scopes(), + name_scope: function.name_scope(), } } } @@ -257,7 +268,8 @@ impl<'a> From<&'a ClassMethodDefinition> for FunctionSpec<'a> { name: None, parameters: method.parameters(), body: method.body(), - has_binding_identifier: false, + scopes: method.scopes(), + name_scope: None, } } } @@ -273,29 +285,11 @@ impl<'a> From<&'a ObjectMethodDefinition> for FunctionSpec<'a> { FunctionSpec { kind, - name: None, + name: method.name().literal().map(Into::into), parameters: method.parameters(), body: method.body(), - has_binding_identifier: false, - } - } -} - -impl<'a> From<(&'a ObjectMethodDefinition, Sym)> for FunctionSpec<'a> { - fn from(method: (&'a ObjectMethodDefinition, Sym)) -> Self { - let kind = match method.0.kind() { - MethodDefinitionKind::Generator => FunctionKind::Generator, - MethodDefinitionKind::AsyncGenerator => FunctionKind::AsyncGenerator, - MethodDefinitionKind::Async => FunctionKind::Async, - _ => FunctionKind::Ordinary, - }; - - FunctionSpec { - kind, - name: Some(Identifier::new(method.1)), - parameters: method.0.parameters(), - body: method.0.body(), - has_binding_identifier: true, + scopes: method.scopes(), + name_scope: None, } } } @@ -402,11 +396,13 @@ pub struct ByteCompiler<'ctx> { /// Locators for all bindings in the codeblock. pub(crate) bindings: Vec, - /// The current variable environment. - pub(crate) variable_environment: Rc, + pub(crate) local_binding_registers: FxHashMap, + + /// The current variable scope. + pub(crate) variable_scope: Scope, - /// The current lexical environment. - pub(crate) lexical_environment: Rc, + /// The current lexical scope. + pub(crate) lexical_scope: Scope, pub(crate) current_open_environments_count: u32, current_stack_value_count: u32, @@ -436,6 +432,11 @@ pub struct ByteCompiler<'ctx> { pub(crate) annex_b_function_names: Vec, } +pub(crate) enum BindingKind { + Stack(u32), + Local(u32), +} + impl<'ctx> ByteCompiler<'ctx> { /// Represents a placeholder address that will be patched later. const DUMMY_ADDRESS: u32 = u32::MAX; @@ -449,8 +450,8 @@ impl<'ctx> ByteCompiler<'ctx> { name: JsString, strict: bool, json_parse: bool, - variable_environment: Rc, - lexical_environment: Rc, + variable_scope: Scope, + lexical_scope: Scope, is_async: bool, is_generator: bool, interner: &'ctx mut Interner, @@ -496,6 +497,7 @@ impl<'ctx> ByteCompiler<'ctx> { bytecode: Vec::default(), constants: ThinVec::default(), bindings: Vec::default(), + local_binding_registers: FxHashMap::default(), this_mode: ThisMode::Global, params: FormalParameterList::default(), current_open_environments_count: 0, @@ -512,8 +514,8 @@ impl<'ctx> ByteCompiler<'ctx> { jump_info: Vec::new(), async_handler: None, json_parse, - variable_environment, - lexical_environment, + variable_scope, + lexical_scope, interner, #[cfg(feature = "annex-b")] @@ -581,15 +583,24 @@ impl<'ctx> ByteCompiler<'ctx> { } #[inline] - pub(crate) fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 { - if let Some(index) = self.bindings_map.get(&binding) { - return *index; + pub(crate) fn get_or_insert_binding(&mut self, binding: IdentifierReference) -> BindingKind { + if binding.local() { + return BindingKind::Local( + *self + .local_binding_registers + .entry(binding) + .or_insert_with(|| self.register_allocator.alloc_persistent().index()), + ); + } + + if let Some(index) = self.bindings_map.get(&binding.locator()) { + return BindingKind::Stack(*index); } let index = self.bindings.len() as u32; - self.bindings.push(binding.clone()); - self.bindings_map.insert(binding, index); - index + self.bindings.push(binding.locator().clone()); + self.bindings_map.insert(binding.locator(), index); + BindingKind::Stack(index) } #[inline] @@ -603,47 +614,43 @@ impl<'ctx> ByteCompiler<'ctx> { fn emit_binding(&mut self, opcode: BindingOpcode, name: JsString) { match opcode { BindingOpcode::Var => { - let binding = self.variable_environment.get_identifier_reference(name); + let binding = self.variable_scope.get_identifier_reference(name); if !binding.locator().is_global() { - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::DefVar, index); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::DefVar, &index); } } - BindingOpcode::InitVar => { - match self.lexical_environment.set_mutable_binding(name.clone()) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::DefInitVar, index); - } - Err(BindingLocatorError::MutateImmutable) => { - let index = self.get_or_insert_string(name); - self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); - } - Err(BindingLocatorError::Silent) => { - self.emit_opcode(Opcode::Pop); - } + BindingOpcode::InitVar => match self.lexical_scope.set_mutable_binding(name.clone()) { + Ok(binding) => { + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::DefInitVar, &index); } - } + Err(BindingLocatorError::MutateImmutable) => { + let index = self.get_or_insert_string(name); + self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); + } + Err(BindingLocatorError::Silent) => { + self.emit_opcode(Opcode::Pop); + } + }, BindingOpcode::InitLexical => { - let binding = self.lexical_environment.get_identifier_reference(name); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::PutLexicalValue, index); + let binding = self.lexical_scope.get_identifier_reference(name); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::PutLexicalValue, &index); } - BindingOpcode::SetName => { - match self.lexical_environment.set_mutable_binding(name.clone()) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); - } - Err(BindingLocatorError::MutateImmutable) => { - let index = self.get_or_insert_string(name); - self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); - } - Err(BindingLocatorError::Silent) => { - self.emit_opcode(Opcode::Pop); - } + BindingOpcode::SetName => match self.lexical_scope.set_mutable_binding(name.clone()) { + Ok(binding) => { + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::SetName, &index); } - } + Err(BindingLocatorError::MutateImmutable) => { + let index = self.get_or_insert_string(name); + self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); + } + Err(BindingLocatorError::Silent) => { + self.emit_opcode(Opcode::Pop); + } + }, } } @@ -706,6 +713,29 @@ impl<'ctx> ByteCompiler<'ctx> { } } + pub(crate) fn emit_binding_access(&mut self, opcode: Opcode, binding: &BindingKind) { + match binding { + BindingKind::Stack(index) => match opcode { + Opcode::SetNameByLocator => self.emit_opcode(opcode), + _ => self.emit_with_varying_operand(opcode, *index), + }, + BindingKind::Local(index) => match opcode { + Opcode::GetName | Opcode::GetNameOrUndefined | Opcode::GetNameAndLocator => { + self.emit_with_varying_operand(Opcode::PushFromLocal, *index); + } + Opcode::GetLocator | Opcode::DefVar => {} + Opcode::SetName + | Opcode::DefInitVar + | Opcode::PutLexicalValue + | Opcode::SetNameByLocator => { + self.emit_with_varying_operand(Opcode::PopIntoLocal, *index); + } + Opcode::DeleteName => self.emit_opcode(Opcode::PushFalse), + _ => unreachable!("invalid opcode for binding access"), + }, + } + } + pub(crate) fn emit_operand(&mut self, operand: Operand, varying_kind: VaryingOperandKind) { match operand { Operand::Bool(v) => self.emit_u8(v.into()), @@ -934,9 +964,9 @@ impl<'ctx> ByteCompiler<'ctx> { match access { Access::Variable { name } => { let name = self.resolve_identifier_expect(name); - let binding = self.lexical_environment.get_identifier_reference(name); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::GetName, index); + let binding = self.lexical_scope.get_identifier_reference(name); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::GetName, &index); } Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { @@ -999,13 +1029,12 @@ impl<'ctx> ByteCompiler<'ctx> { match access { Access::Variable { name } => { let name = self.resolve_identifier_expect(name); - let binding = self - .lexical_environment - .get_identifier_reference(name.clone()); - let index = self.get_or_insert_binding(binding.locator()); + let binding = self.lexical_scope.get_identifier_reference(name.clone()); + let is_lexical = binding.is_lexical(); + let index = self.get_or_insert_binding(binding); - if !binding.is_lexical() { - self.emit_with_varying_operand(Opcode::GetLocator, index); + if !is_lexical { + self.emit_binding_access(Opcode::GetLocator, &index); } expr_fn(self, 0); @@ -1013,11 +1042,11 @@ impl<'ctx> ByteCompiler<'ctx> { self.emit(Opcode::Dup, &[]); } - if binding.is_lexical() { - match self.lexical_environment.set_mutable_binding(name.clone()) { + if is_lexical { + match self.lexical_scope.set_mutable_binding(name.clone()) { Ok(binding) => { let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); + self.emit_binding_access(Opcode::SetName, &index); } Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_string(name); @@ -1028,7 +1057,7 @@ impl<'ctx> ByteCompiler<'ctx> { } } } else { - self.emit_opcode(Opcode::SetNameByLocator); + self.emit_binding_access(Opcode::SetNameByLocator, &index); } } Access::Property { access } => match access { @@ -1112,9 +1141,9 @@ impl<'ctx> ByteCompiler<'ctx> { }, Access::Variable { name } => { let name = name.to_js_string(self.interner()); - let binding = self.lexical_environment.get_identifier_reference(name); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::DeleteName, index); + let binding = self.lexical_scope.get_identifier_reference(name); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::DeleteName, &index); } Access::This => { self.emit_opcode(Opcode::PushTrue); @@ -1333,13 +1362,11 @@ impl<'ctx> ByteCompiler<'ctx> { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner()); if let Some(expr) = variable.init() { - let binding = self - .lexical_environment - .get_identifier_reference(ident.clone()); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::GetLocator, index); + let binding = self.lexical_scope.get_identifier_reference(ident.clone()); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::GetLocator, &index); self.compile_expr(expr, true); - self.emit_opcode(Opcode::SetNameByLocator); + self.emit_binding_access(Opcode::SetNameByLocator, &index); } else { self.emit_binding(BindingOpcode::Var, ident); } @@ -1429,19 +1456,14 @@ impl<'ctx> ByteCompiler<'ctx> { let name = function.name(); if self.annex_b_function_names.contains(&name) { let name = name.to_js_string(self.interner()); - let binding = self - .lexical_environment - .get_identifier_reference(name.clone()); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::GetName, index); - - match self - .variable_environment - .set_mutable_binding_var(name.clone()) - { + let binding = self.lexical_scope.get_identifier_reference(name.clone()); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::GetName, &index); + + match self.variable_scope.set_mutable_binding_var(name.clone()) { Ok(binding) => { let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); + self.emit_binding_access(Opcode::SetName, &index); } Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_string(name); @@ -1471,7 +1493,8 @@ impl<'ctx> ByteCompiler<'ctx> { name, parameters, body, - has_binding_identifier, + scopes, + name_scope, .. } = function; @@ -1481,12 +1504,6 @@ impl<'ctx> ByteCompiler<'ctx> { Some(js_string!()) }; - let binding_identifier = if has_binding_identifier { - name.clone() - } else { - None - }; - let code = FunctionCompiler::new() .name(name) .generator(generator) @@ -1494,12 +1511,13 @@ impl<'ctx> ByteCompiler<'ctx> { .strict(self.strict()) .arrow(arrow) .in_with(self.in_with) - .binding_identifier(binding_identifier) + .name_scope(name_scope.cloned()) .compile( parameters, body, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), + scopes, self.interner, ); @@ -1510,16 +1528,12 @@ impl<'ctx> ByteCompiler<'ctx> { /// pushing it to the stack if necessary. pub(crate) fn function_with_binding( &mut self, - mut function: FunctionSpec<'_>, + function: FunctionSpec<'_>, node_kind: NodeKind, use_expr: bool, ) { let name = function.name; - if node_kind == NodeKind::Declaration { - function.has_binding_identifier = false; - } - let index = self.function(function); self.emit_with_varying_operand(Opcode::GetFunction, index); @@ -1550,7 +1564,8 @@ impl<'ctx> ByteCompiler<'ctx> { name, parameters, body, - has_binding_identifier, + scopes, + name_scope, .. } = function; @@ -1565,12 +1580,6 @@ impl<'ctx> ByteCompiler<'ctx> { Some(js_string!()) }; - let binding_identifier = if has_binding_identifier { - name.clone() - } else { - None - }; - let code = FunctionCompiler::new() .name(name) .generator(generator) @@ -1579,12 +1588,13 @@ impl<'ctx> ByteCompiler<'ctx> { .arrow(arrow) .method(true) .in_with(self.in_with) - .binding_identifier(binding_identifier) + .name_scope(name_scope.cloned()) .compile( parameters, body, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), + scopes, self.interner, ); @@ -1603,7 +1613,7 @@ impl<'ctx> ByteCompiler<'ctx> { name, parameters, body, - has_binding_identifier, + scopes, .. } = function; @@ -1613,12 +1623,6 @@ impl<'ctx> ByteCompiler<'ctx> { Some(js_string!()) }; - let binding_identifier = if has_binding_identifier { - name.clone() - } else { - None - }; - let code = FunctionCompiler::new() .name(name) .generator(generator) @@ -1627,12 +1631,13 @@ impl<'ctx> ByteCompiler<'ctx> { .arrow(arrow) .method(true) .in_with(self.in_with) - .binding_identifier(binding_identifier) + .name_scope(function.name_scope.cloned()) .compile( parameters, body, - self.variable_environment.clone(), - self.lexical_environment.clone(), + self.variable_scope.clone(), + self.lexical_scope.clone(), + scopes, self.interner, ); @@ -1669,9 +1674,9 @@ impl<'ctx> ByteCompiler<'ctx> { if self.in_with { let name = self.resolve_identifier_expect(*ident); - let binding = self.lexical_environment.get_identifier_reference(name); - let index = self.get_or_insert_binding(binding.locator()); - self.emit_with_varying_operand(Opcode::ThisForObjectEnvironmentName, index); + let binding = self.lexical_scope.get_identifier_reference(name); + let index = self.get_or_insert_binding(binding); + self.emit_binding_access(Opcode::ThisForObjectEnvironmentName, &index); } else { self.emit_opcode(Opcode::PushUndefined); } @@ -1709,9 +1714,21 @@ impl<'ctx> ByteCompiler<'ctx> { } match kind { - CallKind::CallEval if contains_spread => self.emit_opcode(Opcode::CallEvalSpread), CallKind::CallEval => { - self.emit_with_varying_operand(Opcode::CallEval, call.args().len() as u32); + let scope_index = self.constants.len() as u32; + self.constants + .push(Constant::Scope(self.lexical_scope.clone())); + if contains_spread { + self.emit_with_varying_operand(Opcode::CallEvalSpread, scope_index); + } else { + self.emit( + Opcode::CallEval, + &[ + Operand::Varying(call.args().len() as u32), + Operand::Varying(scope_index), + ], + ); + } } CallKind::Call if contains_spread => self.emit_opcode(Opcode::CallSpread), CallKind::Call => { @@ -1737,6 +1754,16 @@ impl<'ctx> ByteCompiler<'ctx> { } self.r#return(false); + let mapped_arguments_binding_indices = self + .emitted_mapped_arguments_object_opcode + .then(|| MappedArguments::binding_indices(&self.params)) + .unwrap_or_default(); + + let max_local_binding_register_index = + self.local_binding_registers.values().max().unwrap_or(&0); + let local_bindings_initialized = + vec![false; (max_local_binding_register_index + 1) as usize].into_boxed_slice(); + let register_count = self.register_allocator.finish(); // NOTE: Offset the handlers stack count so we don't pop the registers @@ -1745,12 +1772,6 @@ impl<'ctx> ByteCompiler<'ctx> { handler.stack_count += register_count; } - let mapped_arguments_binding_indices = if self.emitted_mapped_arguments_object_opcode { - MappedArguments::binding_indices(&self.params) - } else { - ThinVec::new() - }; - CodeBlock { name: self.function_name, length: self.length, @@ -1761,6 +1782,7 @@ impl<'ctx> ByteCompiler<'ctx> { bytecode: self.bytecode.into_boxed_slice(), constants: self.constants, bindings: self.bindings.into_boxed_slice(), + local_bindings_initialized, handlers: self.handlers, flags: Cell::new(self.code_block_flags), ic: self.ic.into_boxed_slice(), diff --git a/core/engine/src/bytecompiler/module.rs b/core/engine/src/bytecompiler/module.rs index 06f9521cce..f29d473d71 100644 --- a/core/engine/src/bytecompiler/module.rs +++ b/core/engine/src/bytecompiler/module.rs @@ -45,8 +45,6 @@ impl ByteCompiler<'_> { ExportDeclaration::DefaultClassDeclaration(cl) => self.class(cl.into(), false), ExportDeclaration::DefaultAssignmentExpression(expr) => { let name = Sym::DEFAULT_EXPORT.to_js_string(self.interner()); - self.lexical_environment - .create_mutable_binding(name.clone(), false); self.compile_expr(expr, true); if expr.is_anonymous_function_definition() { diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index 655f3ce642..f4fa8bdb63 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -4,16 +4,22 @@ use boa_ast::statement::Block; impl ByteCompiler<'_> { /// Compile a [`Block`] `boa_ast` node pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { - let old_lex_env = self.lexical_environment.clone(); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - let env = self.lexical_environment.clone(); + let outer_scope = if let Some(scope) = block.scope() { + let outer_scope = self.lexical_scope.clone(); + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); + Some(outer_scope) + } else { + None + }; - self.block_declaration_instantiation(block, &env); + self.block_declaration_instantiation(block); self.compile_statement_list(block.statement_list(), use_expr, true); - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; - self.emit_opcode(Opcode::PopEnvironment); + if let Some(outer_scope) = outer_scope { + self.pop_scope(); + self.lexical_scope = outer_scope; + self.emit_opcode(Opcode::PopEnvironment); + } } } diff --git a/core/engine/src/bytecompiler/statement/loop.rs b/core/engine/src/bytecompiler/statement/loop.rs index 71c6f453e3..e0589efd7d 100644 --- a/core/engine/src/bytecompiler/statement/loop.rs +++ b/core/engine/src/bytecompiler/statement/loop.rs @@ -1,6 +1,7 @@ use boa_ast::{ declaration::Binding, operations::bound_names, + scope::BindingLocatorError, statement::{ iteration::{ForLoopInitializer, IterableLoopInitializer}, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop, @@ -10,7 +11,6 @@ use boa_interner::Sym; use crate::{ bytecompiler::{Access, ByteCompiler, Operand, ToJsString}, - environments::BindingLocatorError, vm::{BindingOpcode, Opcode}, }; @@ -22,7 +22,7 @@ impl ByteCompiler<'_> { use_expr: bool, ) { let mut let_binding_indices = None; - let mut old_lex_env = None; + let mut outer_scope = None; if let Some(init) = for_loop.init() { match init { @@ -31,45 +31,42 @@ impl ByteCompiler<'_> { self.compile_var_decl(decl); } ForLoopInitializer::Lexical(decl) => { - old_lex_env = Some(self.lexical_environment.clone()); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + outer_scope = Some(self.lexical_scope.clone()); + let scope_index = self.push_scope(decl.scope()); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); - let names = bound_names(decl); - if decl.is_const() { - for name in &names { - let name = name.to_js_string(self.interner()); - self.lexical_environment - .create_immutable_binding(name, true); - } + let names = bound_names(decl.declaration()); + if decl.declaration().is_const() { } else { let mut indices = Vec::new(); for name in &names { let name = name.to_js_string(self.interner()); - let binding = - self.lexical_environment.create_mutable_binding(name, false); + let binding = self + .lexical_scope + .get_binding_reference(&name) + .expect("binding must exist"); let index = self.get_or_insert_binding(binding); indices.push(index); } - let_binding_indices = Some((indices, env_index)); + let_binding_indices = Some((indices, scope_index)); } - self.compile_lexical_decl(decl); + self.compile_lexical_decl(decl.declaration()); } } } self.push_empty_loop_jump_control(use_expr); - if let Some((let_binding_indices, env_index)) = &let_binding_indices { + if let Some((let_binding_indices, scope_index)) = &let_binding_indices { for index in let_binding_indices { - self.emit_with_varying_operand(Opcode::GetName, *index); + self.emit_binding_access(Opcode::GetName, index); } self.emit_opcode(Opcode::PopEnvironment); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index); + self.emit_with_varying_operand(Opcode::PushScope, *scope_index); for index in let_binding_indices.iter().rev() { - self.emit_with_varying_operand(Opcode::PutLexicalValue, *index); + self.emit_binding_access(Opcode::PutLexicalValue, index); } } @@ -83,16 +80,16 @@ impl ByteCompiler<'_> { .expect("jump_control must exist as it was just pushed") .set_start_address(start_address); - if let Some((let_binding_indices, env_index)) = &let_binding_indices { + if let Some((let_binding_indices, scope_index)) = &let_binding_indices { for index in let_binding_indices { - self.emit_with_varying_operand(Opcode::GetName, *index); + self.emit_binding_access(Opcode::GetName, index); } self.emit_opcode(Opcode::PopEnvironment); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index); + self.emit_with_varying_operand(Opcode::PushScope, *scope_index); for index in let_binding_indices.iter().rev() { - self.emit_with_varying_operand(Opcode::PutLexicalValue, *index); + self.emit_binding_access(Opcode::PutLexicalValue, index); } } @@ -118,9 +115,9 @@ impl ByteCompiler<'_> { self.patch_jump(exit); self.pop_loop_control_info(); - if let Some(old_lex_env) = old_lex_env { - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; + if let Some(outer_scope) = outer_scope { + self.pop_scope(); + self.lexical_scope = outer_scope; self.emit_opcode(Opcode::PopEnvironment); } } @@ -141,27 +138,16 @@ impl ByteCompiler<'_> { } } } - let initializer_bound_names = match for_in_loop.initializer() { - IterableLoopInitializer::Let(declaration) - | IterableLoopInitializer::Const(declaration) => bound_names(declaration), - _ => Vec::new(), - }; - if initializer_bound_names.is_empty() { + if let Some(scope) = for_in_loop.target_scope() { + let outer_scope = self.lexical_scope.clone(); + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); self.compile_expr(for_in_loop.target(), true); + self.pop_scope(); + self.lexical_scope = outer_scope; + self.emit_opcode(Opcode::PopEnvironment); } else { - let old_lex_env = self.lexical_environment.clone(); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - - for name in &initializer_bound_names { - let name = name.to_js_string(self.interner()); - self.lexical_environment.create_mutable_binding(name, false); - } self.compile_expr(for_in_loop.target(), true); - - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; - self.emit_opcode(Opcode::PopEnvironment); } let early_exit = self.jump_if_null_or_undefined(); @@ -177,12 +163,12 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::IteratorValue); - let mut old_lex_env = None; + let mut outer_scope = None; - if !initializer_bound_names.is_empty() { - old_lex_env = Some(self.lexical_environment.clone()); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if let Some(scope) = for_in_loop.scope() { + outer_scope = Some(self.lexical_scope.clone()); + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); } match for_in_loop.initializer() { @@ -206,35 +192,13 @@ impl ByteCompiler<'_> { self.compile_declaration_pattern(pattern, BindingOpcode::InitVar); } }, - IterableLoopInitializer::Let(declaration) => match declaration { - Binding::Identifier(ident) => { - let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_mutable_binding(ident.clone(), false); - self.emit_binding(BindingOpcode::InitLexical, ident); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_mutable_binding(ident, false); - } - self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); - } - }, - IterableLoopInitializer::Const(declaration) => match declaration { + IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_immutable_binding(ident.clone(), true); self.emit_binding(BindingOpcode::InitLexical, ident); } Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_immutable_binding(ident, true); - } self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); } }, @@ -245,9 +209,9 @@ impl ByteCompiler<'_> { self.compile_stmt(for_in_loop.body(), use_expr, true); - if let Some(old_lex_env) = old_lex_env { - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; + if let Some(outer_scope) = outer_scope { + self.pop_scope(); + self.lexical_scope = outer_scope; self.emit_opcode(Opcode::PopEnvironment); } @@ -270,27 +234,16 @@ impl ByteCompiler<'_> { label: Option, use_expr: bool, ) { - let initializer_bound_names = match for_of_loop.initializer() { - IterableLoopInitializer::Let(declaration) - | IterableLoopInitializer::Const(declaration) => bound_names(declaration), - _ => Vec::new(), - }; - if initializer_bound_names.is_empty() { + if let Some(scope) = for_of_loop.iterable_scope() { + let outer_scope = self.lexical_scope.clone(); + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); self.compile_expr(for_of_loop.iterable(), true); + self.pop_scope(); + self.lexical_scope = outer_scope; + self.emit_opcode(Opcode::PopEnvironment); } else { - let old_lex_env = self.lexical_environment.clone(); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - - for name in &initializer_bound_names { - let name = name.to_js_string(self.interner()); - self.lexical_environment.create_mutable_binding(name, false); - } self.compile_expr(for_of_loop.iterable(), true); - - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; - self.emit_opcode(Opcode::PopEnvironment); } if for_of_loop.r#await() { @@ -318,23 +271,23 @@ impl ByteCompiler<'_> { let exit = self.jump_if_true(); self.emit_opcode(Opcode::IteratorValue); - let mut old_lex_env = None; + let mut outer_scope = None; - if !initializer_bound_names.is_empty() { - old_lex_env = Some(self.lexical_environment.clone()); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - }; + if let Some(scope) = for_of_loop.scope() { + outer_scope = Some(self.lexical_scope.clone()); + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); + } let handler_index = self.push_handler(); match for_of_loop.initializer() { IterableLoopInitializer::Identifier(ref ident) => { let ident = ident.to_js_string(self.interner()); - match self.lexical_environment.set_mutable_binding(ident.clone()) { + match self.lexical_scope.set_mutable_binding(ident.clone()) { Ok(binding) => { let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::DefInitVar, index); + self.emit_binding_access(Opcode::DefInitVar, &index); } Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_string(ident); @@ -365,35 +318,13 @@ impl ByteCompiler<'_> { } } } - IterableLoopInitializer::Let(declaration) => match declaration { - Binding::Identifier(ident) => { - let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_mutable_binding(ident.clone(), false); - self.emit_binding(BindingOpcode::InitLexical, ident); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_mutable_binding(ident, false); - } - self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); - } - }, - IterableLoopInitializer::Const(declaration) => match declaration { + IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_immutable_binding(ident.clone(), true); self.emit_binding(BindingOpcode::InitLexical, ident); } Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - let ident = ident.to_js_string(self.interner()); - self.lexical_environment - .create_immutable_binding(ident, true); - } self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); } }, @@ -423,9 +354,9 @@ impl ByteCompiler<'_> { self.patch_jump(exit); } - if let Some(old_lex_env) = old_lex_env { - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; + if let Some(outer_scope) = outer_scope { + self.pop_scope(); + self.lexical_scope = outer_scope; self.emit_opcode(Opcode::PopEnvironment); } diff --git a/core/engine/src/bytecompiler/statement/switch.rs b/core/engine/src/bytecompiler/statement/switch.rs index 366f6e276f..d1bb4b8d38 100644 --- a/core/engine/src/bytecompiler/statement/switch.rs +++ b/core/engine/src/bytecompiler/statement/switch.rs @@ -6,12 +6,16 @@ impl ByteCompiler<'_> { pub(crate) fn compile_switch(&mut self, switch: &Switch, use_expr: bool) { self.compile_expr(switch.val(), true); - let old_lex_env = self.lexical_environment.clone(); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - let env = self.lexical_environment.clone(); + let outer_scope = if let Some(scope) = switch.scope() { + let outer_scope = self.lexical_scope.clone(); + let scope_index = self.push_scope(scope); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); + Some(outer_scope) + } else { + None + }; - self.block_declaration_instantiation(switch, &env); + self.block_declaration_instantiation(switch); let start_address = self.next_opcode_location(); self.push_switch_control_info(None, start_address, use_expr); @@ -52,8 +56,10 @@ impl ByteCompiler<'_> { self.pop_switch_control_info(); - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; - self.emit_opcode(Opcode::PopEnvironment); + if let Some(outer_scope) = outer_scope { + self.pop_scope(); + self.lexical_scope = outer_scope; + self.emit_opcode(Opcode::PopEnvironment); + } } } diff --git a/core/engine/src/bytecompiler/statement/try.rs b/core/engine/src/bytecompiler/statement/try.rs index c2030207fb..ad0b626c5a 100644 --- a/core/engine/src/bytecompiler/statement/try.rs +++ b/core/engine/src/bytecompiler/statement/try.rs @@ -4,7 +4,6 @@ use crate::{ }; use boa_ast::{ declaration::Binding, - operations::bound_names, statement::{Block, Catch, Finally, Try}, Statement, StatementListItem, }; @@ -111,23 +110,17 @@ impl ByteCompiler<'_> { pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, use_expr: bool) { // stack: exception - let old_lex_env = self.lexical_environment.clone(); - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); - let env = self.lexical_environment.clone(); + let outer_scope = self.lexical_scope.clone(); + let scope_index = self.push_scope(catch.scope()); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); if let Some(binding) = catch.parameter() { match binding { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner()); - env.create_mutable_binding(ident.clone(), false); self.emit_binding(BindingOpcode::InitLexical, ident); } Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - let ident = ident.to_js_string(self.interner()); - env.create_mutable_binding(ident, false); - } self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical); } } @@ -137,8 +130,8 @@ impl ByteCompiler<'_> { self.compile_catch_finally_block(catch.block(), use_expr); - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; + self.pop_scope(); + self.lexical_scope = outer_scope; self.emit_opcode(Opcode::PopEnvironment); } diff --git a/core/engine/src/bytecompiler/statement/with.rs b/core/engine/src/bytecompiler/statement/with.rs index db05b3d4dd..dcbf80d026 100644 --- a/core/engine/src/bytecompiler/statement/with.rs +++ b/core/engine/src/bytecompiler/statement/with.rs @@ -6,8 +6,8 @@ impl ByteCompiler<'_> { pub(crate) fn compile_with(&mut self, with: &With, use_expr: bool) { self.compile_expr(with.expression(), true); - let old_lex_env = self.lexical_environment.clone(); - let _ = self.push_compile_environment(false); + let outer_scope = self.lexical_scope.clone(); + let _ = self.push_scope(with.scope()); self.emit_opcode(Opcode::PushObjectEnvironment); let in_with = self.in_with; @@ -15,8 +15,8 @@ impl ByteCompiler<'_> { self.compile_stmt(with.statement(), use_expr, true); self.in_with = in_with; - self.pop_compile_environment(); - self.lexical_environment = old_lex_env; + self.pop_scope(); + self.lexical_scope = outer_scope; self.emit_opcode(Opcode::PopEnvironment); } } diff --git a/core/engine/src/environments/compile.rs b/core/engine/src/environments/compile.rs deleted file mode 100644 index 1169f91916..0000000000 --- a/core/engine/src/environments/compile.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use crate::{environments::runtime::BindingLocator, JsString}; -use boa_gc::{empty_trace, Finalize, Trace}; - -use rustc_hash::FxHashMap; - -use super::runtime::BindingLocatorError; - -/// A compile time binding represents a binding at bytecode compile time in a [`CompileTimeEnvironment`]. -/// -/// It contains the binding index and a flag to indicate if this is a mutable binding or not. -#[derive(Debug)] -struct CompileTimeBinding { - index: u32, - mutable: bool, - lex: bool, - strict: bool, -} - -/// A compile time environment maps bound identifiers to their binding positions. -/// -/// A compile time environment also indicates, if it is a function environment. -#[derive(Debug, Finalize)] -pub(crate) struct CompileTimeEnvironment { - outer: Option>, - environment_index: u32, - bindings: RefCell>, - function_scope: bool, -} - -// Safety: Nothing in this struct needs tracing, so this is safe. -unsafe impl Trace for CompileTimeEnvironment { - empty_trace!(); -} - -impl CompileTimeEnvironment { - /// Creates a new global compile time environment. - pub(crate) fn new_global() -> Self { - Self { - outer: None, - environment_index: 0, - bindings: RefCell::default(), - function_scope: true, - } - } - - /// Creates a new compile time environment. - pub(crate) fn new(parent: Rc, function_scope: bool) -> Self { - let index = parent.environment_index + 1; - Self { - outer: Some(parent), - environment_index: index, - bindings: RefCell::default(), - function_scope, - } - } - - /// Check if environment has a lexical binding with the given name. - pub(crate) fn has_lex_binding(&self, name: &JsString) -> bool { - self.bindings - .borrow() - .get(name) - .map_or(false, |binding| binding.lex) - } - - /// Check if the environment has a binding with the given name. - pub(crate) fn has_binding(&self, name: &JsString) -> bool { - self.bindings.borrow().contains_key(name) - } - - /// Get the binding locator for a binding with the given name. - /// Fall back to the global environment if the binding is not found. - pub(crate) fn get_identifier_reference(&self, name: JsString) -> IdentifierReference { - if let Some(binding) = self.bindings.borrow().get(&name) { - IdentifierReference::new( - BindingLocator::declarative(name, self.environment_index, binding.index), - binding.lex, - ) - } else if let Some(outer) = &self.outer { - outer.get_identifier_reference(name) - } else { - IdentifierReference::new(BindingLocator::global(name), false) - } - } - - /// Returns the number of bindings in this environment. - pub(crate) fn num_bindings(&self) -> u32 { - self.bindings.borrow().len() as u32 - } - - /// Returns the index of this environment. - pub(crate) fn environment_index(&self) -> u32 { - self.environment_index - } - - /// Check if the environment is a function environment. - pub(crate) const fn is_function(&self) -> bool { - self.function_scope - } - - /// Check if the environment is a global environment. - pub(crate) const fn is_global(&self) -> bool { - self.outer.is_none() - } - - /// Get the locator for a binding name. - pub(crate) fn get_binding(&self, name: &JsString) -> Option { - self.bindings.borrow().get(name).map(|binding| { - BindingLocator::declarative(name.clone(), self.environment_index, binding.index) - }) - } - - /// Create a mutable binding. - pub(crate) fn create_mutable_binding( - &self, - name: JsString, - function_scope: bool, - ) -> BindingLocator { - let binding_index = self.bindings.borrow().len() as u32; - self.bindings.borrow_mut().insert( - name.clone(), - CompileTimeBinding { - index: binding_index, - mutable: true, - lex: !function_scope, - strict: false, - }, - ); - BindingLocator::declarative(name, self.environment_index, binding_index) - } - - /// Crate an immutable binding. - pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) -> BindingLocator { - let binding_index = self.bindings.borrow().len() as u32; - self.bindings.borrow_mut().insert( - name.clone(), - CompileTimeBinding { - index: binding_index, - mutable: false, - lex: true, - strict, - }, - ); - BindingLocator::declarative(name, self.environment_index, binding_index) - } - - /// Return the binding locator for a mutable binding. - pub(crate) fn set_mutable_binding( - &self, - name: JsString, - ) -> Result { - Ok(match self.bindings.borrow().get(&name) { - Some(binding) if binding.mutable => { - BindingLocator::declarative(name, self.environment_index, binding.index) - } - Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable), - Some(_) => return Err(BindingLocatorError::Silent), - None => self.outer.as_ref().map_or_else( - || Ok(BindingLocator::global(name.clone())), - |outer| outer.set_mutable_binding(name.clone()), - )?, - }) - } - - #[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: JsString, - ) -> Result { - if !self.is_function() { - return self.outer.as_ref().map_or_else( - || Ok(BindingLocator::global(name.clone())), - |outer| outer.set_mutable_binding_var(name.clone()), - ); - } - - Ok(match self.bindings.borrow().get(&name) { - Some(binding) if binding.mutable => { - BindingLocator::declarative(name, self.environment_index, binding.index) - } - Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable), - Some(_) => return Err(BindingLocatorError::Silent), - None => self.outer.as_ref().map_or_else( - || Ok(BindingLocator::global(name.clone())), - |outer| outer.set_mutable_binding_var(name.clone()), - )?, - }) - } - - /// Gets the outer environment of this environment. - pub(crate) fn outer(&self) -> Option> { - self.outer.clone() - } -} - -/// A reference to an identifier in a compile time environment. -pub(crate) struct IdentifierReference { - locator: BindingLocator, - lexical: bool, -} - -impl IdentifierReference { - /// Create a new identifier reference. - pub(crate) fn new(locator: BindingLocator, lexical: bool) -> Self { - Self { locator, lexical } - } - - /// Get the binding locator for this identifier reference. - pub(crate) fn locator(&self) -> BindingLocator { - self.locator.clone() - } - - /// Check if this identifier reference is lexical. - pub(crate) fn is_lexical(&self) -> bool { - self.lexical - } -} diff --git a/core/engine/src/environments/mod.rs b/core/engine/src/environments/mod.rs index fd8819a08e..d752854df4 100644 --- a/core/engine/src/environments/mod.rs +++ b/core/engine/src/environments/mod.rs @@ -24,15 +24,11 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-environment-records -mod compile; mod runtime; -pub(crate) use { - compile::CompileTimeEnvironment, - runtime::{ - BindingLocator, BindingLocatorEnvironment, BindingLocatorError, DeclarativeEnvironment, - Environment, EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus, - }, +pub(crate) use runtime::{ + DeclarativeEnvironment, Environment, EnvironmentStack, FunctionSlots, PrivateEnvironment, + ThisBindingStatus, }; #[cfg(test)] diff --git a/core/engine/src/environments/runtime/declarative/function.rs b/core/engine/src/environments/runtime/declarative/function.rs index b1a7275c97..9630841af6 100644 --- a/core/engine/src/environments/runtime/declarative/function.rs +++ b/core/engine/src/environments/runtime/declarative/function.rs @@ -1,3 +1,4 @@ +use boa_ast::scope::Scope; use boa_gc::{custom_trace, Finalize, GcRefCell, Trace}; use crate::{builtins::function::OrdinaryFunction, JsNativeError, JsObject, JsResult, JsValue}; @@ -8,14 +9,25 @@ use super::PoisonableEnvironment; pub(crate) struct FunctionEnvironment { inner: PoisonableEnvironment, slots: Box, + + // Safety: Nothing in `Scope` needs tracing. + #[unsafe_ignore_trace] + scope: Scope, } impl FunctionEnvironment { /// Creates a new `FunctionEnvironment`. - pub(crate) fn new(bindings: u32, poisoned: bool, with: bool, slots: FunctionSlots) -> Self { + pub(crate) fn new( + bindings: u32, + poisoned: bool, + with: bool, + slots: FunctionSlots, + scope: Scope, + ) -> Self { Self { inner: PoisonableEnvironment::new(bindings, poisoned, with), slots: Box::new(slots), + scope, } } @@ -24,6 +36,11 @@ impl FunctionEnvironment { &self.slots } + /// Gets the compile time environment of this function environment. + pub(crate) const fn compile(&self) -> &Scope { + &self.scope + } + /// Gets the `poisonable_environment` of this function environment. pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment { &self.inner diff --git a/core/engine/src/environments/runtime/declarative/mod.rs b/core/engine/src/environments/runtime/declarative/mod.rs index 32cfc53469..5980587af2 100644 --- a/core/engine/src/environments/runtime/declarative/mod.rs +++ b/core/engine/src/environments/runtime/declarative/mod.rs @@ -8,9 +8,9 @@ pub(crate) use global::GlobalEnvironment; pub(crate) use lexical::LexicalEnvironment; pub(crate) use module::ModuleEnvironment; -use crate::{environments::CompileTimeEnvironment, JsResult, JsValue}; +use crate::{JsResult, JsValue}; use boa_gc::{Finalize, GcRefCell, Trace}; -use std::{cell::Cell, rc::Rc}; +use std::cell::Cell; /// A declarative environment holds binding values at runtime. /// @@ -35,10 +35,6 @@ use std::{cell::Cell, rc::Rc}; #[derive(Debug, Trace, Finalize)] pub(crate) struct DeclarativeEnvironment { kind: DeclarativeEnvironmentKind, - - // Safety: Nothing in CompileTimeEnvironment needs tracing. - #[unsafe_ignore_trace] - compile: Rc, } impl DeclarativeEnvironment { @@ -46,21 +42,12 @@ impl DeclarativeEnvironment { pub(crate) fn global() -> Self { Self { kind: DeclarativeEnvironmentKind::Global(GlobalEnvironment::new()), - compile: Rc::new(CompileTimeEnvironment::new_global()), } } /// Creates a new `DeclarativeEnvironment` from its kind and compile environment. - pub(crate) fn new( - kind: DeclarativeEnvironmentKind, - compile: Rc, - ) -> Self { - Self { kind, compile } - } - - /// Gets the compile time environment of this environment. - pub(crate) fn compile_env(&self) -> Rc { - self.compile.clone() + pub(crate) fn new(kind: DeclarativeEnvironmentKind) -> Self { + Self { kind } } /// Returns a reference to the the kind of the environment. @@ -68,6 +55,11 @@ impl DeclarativeEnvironment { &self.kind } + /// Returns whether this environment is a function environment. + pub(crate) fn is_function(&self) -> bool { + matches!(self.kind(), DeclarativeEnvironmentKind::Function(_)) + } + /// Gets the binding value from the environment by index. /// /// # Panics @@ -130,7 +122,7 @@ impl DeclarativeEnvironment { /// Extends the environment with the bindings from the compile time environment. pub(crate) fn extend_from_compile(&self) { if let Some(env) = self.kind().as_function() { - let compile_bindings_number = self.compile_env().num_bindings() as usize; + let compile_bindings_number = env.compile().num_bindings() as usize; let mut bindings = env.poisonable_environment().bindings().borrow_mut(); if compile_bindings_number > bindings.len() { bindings.resize(compile_bindings_number, None); @@ -280,8 +272,7 @@ pub(crate) struct PoisonableEnvironment { bindings: GcRefCell>>, #[unsafe_ignore_trace] poisoned: Cell, - #[unsafe_ignore_trace] - with: Cell, + with: bool, } impl PoisonableEnvironment { @@ -290,7 +281,7 @@ impl PoisonableEnvironment { Self { bindings: GcRefCell::new(vec![None; bindings_count as usize]), poisoned: Cell::new(poisoned), - with: Cell::new(with), + with, } } @@ -326,7 +317,7 @@ impl PoisonableEnvironment { /// Returns `true` if this environment is inside a `with` environment. fn with(&self) -> bool { - self.with.get() + self.with } /// Poisons this environment for future binding searches. diff --git a/core/engine/src/environments/runtime/declarative/module.rs b/core/engine/src/environments/runtime/declarative/module.rs index 6ffd88cb45..2acffe9d8d 100644 --- a/core/engine/src/environments/runtime/declarative/module.rs +++ b/core/engine/src/environments/runtime/declarative/module.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; +use boa_ast::scope::Scope; use boa_gc::{Finalize, GcRefCell, Trace}; use crate::{module::Module, JsString, JsValue}; @@ -36,16 +37,26 @@ enum BindingType { #[derive(Debug, Trace, Finalize)] pub(crate) struct ModuleEnvironment { bindings: GcRefCell>, + + // Safety: Nothing in CompileTimeEnvironment needs tracing. + #[unsafe_ignore_trace] + compile: Scope, } impl ModuleEnvironment { /// Creates a new `LexicalEnvironment`. - pub(crate) fn new(bindings: u32) -> Self { + pub(crate) fn new(bindings: u32, compile: Scope) -> Self { Self { bindings: GcRefCell::new(vec![BindingType::Direct(None); bindings as usize]), + compile, } } + /// Gets the compile time environment of this module environment. + pub(crate) const fn compile(&self) -> &Scope { + &self.compile + } + /// Get the binding value from the environment by it's index. /// /// # Panics @@ -63,7 +74,10 @@ impl ModuleEnvironment { match &*accessor.clone().borrow() { BindingAccessor::Identifier(name) => { let index = env - .compile_env() + .kind() + .as_module() + .expect("must be module environment") + .compile() .get_binding(name) .expect("linking must ensure the binding exists"); diff --git a/core/engine/src/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index 8e13398cfc..ba2a03dda5 100644 --- a/core/engine/src/environments/runtime/mod.rs +++ b/core/engine/src/environments/runtime/mod.rs @@ -1,11 +1,9 @@ -use std::rc::Rc; - use crate::{ - environments::CompileTimeEnvironment, object::{JsObject, PrivateName}, Context, JsResult, JsString, JsSymbol, JsValue, }; -use boa_gc::{empty_trace, Finalize, Gc, Trace}; +use boa_ast::scope::{BindingLocator, BindingLocatorScope, Scope}; +use boa_gc::{Finalize, Gc, Trace}; mod declarative; mod private; @@ -76,18 +74,18 @@ impl EnvironmentStack { } /// Gets the next outer function environment. - pub(crate) fn outer_function_environment(&self) -> &Gc { + pub(crate) fn outer_function_environment(&self) -> Option<(Gc, Scope)> { for env in self .stack .iter() .filter_map(Environment::as_declarative) .rev() { - if let DeclarativeEnvironmentKind::Function(_) = &env.kind() { - return env; + if let Some(function_env) = env.kind().as_function() { + return Some((env.clone(), function_env.compile().clone())); } } - self.global() + None } /// Pop all current environments except the global environment. @@ -157,9 +155,7 @@ impl EnvironmentStack { } /// Push a lexical environment on the environments stack and return it's index. - pub(crate) fn push_lexical(&mut self, compile_environment: Rc) -> u32 { - let num_bindings = compile_environment.num_bindings(); - + pub(crate) fn push_lexical(&mut self, bindings_count: u32) -> u32 { let (poisoned, with) = { // Check if the outer environment is a declarative environment. let with = if let Some(env) = self.stack.last() { @@ -180,26 +176,17 @@ impl EnvironmentStack { let index = self.stack.len() as u32; self.stack.push(Environment::Declarative(Gc::new( - DeclarativeEnvironment::new( - DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new( - num_bindings, - poisoned, - with, - )), - compile_environment, - ), + DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Lexical( + LexicalEnvironment::new(bindings_count, poisoned, with), + )), ))); index } /// Push a function environment on the environments stack. - pub(crate) fn push_function( - &mut self, - compile_environment: Rc, - function_slots: FunctionSlots, - ) { - let num_bindings = compile_environment.num_bindings(); + pub(crate) fn push_function(&mut self, scope: Scope, function_slots: FunctionSlots) { + let num_bindings = scope.num_bindings(); let (poisoned, with) = { // Check if the outer environment is a declarative environment. @@ -219,26 +206,19 @@ impl EnvironmentStack { }; self.stack.push(Environment::Declarative(Gc::new( - DeclarativeEnvironment::new( - DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( - num_bindings, - poisoned, - with, - function_slots, - )), - compile_environment, - ), + DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Function( + FunctionEnvironment::new(num_bindings, poisoned, with, function_slots, scope), + )), ))); } /// Push a module environment on the environments stack. - pub(crate) fn push_module(&mut self, compile_environment: Rc) { - let num_bindings = compile_environment.num_bindings(); + pub(crate) fn push_module(&mut self, scope: Scope) { + let num_bindings = scope.num_bindings(); self.stack.push(Environment::Declarative(Gc::new( - DeclarativeEnvironment::new( - DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings)), - compile_environment, - ), + DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Module( + ModuleEnvironment::new(num_bindings, scope), + )), ))); } @@ -258,16 +238,6 @@ impl EnvironmentStack { } } - /// Get the compile environment for the current runtime environment. - pub(crate) fn current_compile_environment(&self) -> Rc { - self.stack - .iter() - .filter_map(Environment::as_declarative) - .last() - .map(|env| env.compile_env()) - .unwrap_or(self.global().compile_env()) - } - /// Mark that there may be added bindings from the current environment to the next function /// environment. pub(crate) fn poison_until_last_function(&mut self) { @@ -278,7 +248,7 @@ impl EnvironmentStack { .filter_map(Environment::as_declarative) { env.poison(); - if env.compile_env().is_function() { + if env.is_function() { return; } } @@ -293,14 +263,15 @@ impl EnvironmentStack { #[track_caller] pub(crate) fn put_lexical_value( &mut self, - environment: BindingLocatorEnvironment, + environment: BindingLocatorScope, binding_index: u32, value: JsValue, ) { let env = match environment { - BindingLocatorEnvironment::GlobalObject - | BindingLocatorEnvironment::GlobalDeclarative => self.global(), - BindingLocatorEnvironment::Stack(index) => self + BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => { + self.global() + } + BindingLocatorScope::Stack(index) => self .stack .get(index as usize) .and_then(Environment::as_declarative) @@ -317,14 +288,15 @@ impl EnvironmentStack { #[track_caller] pub(crate) fn put_value_if_uninitialized( &mut self, - environment: BindingLocatorEnvironment, + environment: BindingLocatorScope, binding_index: u32, value: JsValue, ) { let env = match environment { - BindingLocatorEnvironment::GlobalObject - | BindingLocatorEnvironment::GlobalDeclarative => self.global(), - BindingLocatorEnvironment::Stack(index) => self + BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => { + self.global() + } + BindingLocatorScope::Stack(index) => self .stack .get(index as usize) .and_then(Environment::as_declarative) @@ -388,103 +360,6 @@ impl EnvironmentStack { } } -/// A binding locator contains all information about a binding that is needed to resolve it at runtime. -/// -/// Binding locators get created at bytecode compile time and are accessible at runtime via the [`crate::vm::CodeBlock`]. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Finalize)] -pub(crate) struct BindingLocator { - /// Name of the binding. - name: JsString, - - /// Environment of the binding. - /// - 0: Global object - /// - 1: Global declarative environment - /// - n: Stack environment at index n - 2 - environment: u32, - - /// Index of the binding in the environment. - binding_index: u32, -} - -unsafe impl Trace for BindingLocator { - empty_trace!(); -} - -impl BindingLocator { - /// Creates a new declarative binding locator that has knows indices. - pub(crate) const fn declarative( - name: JsString, - environment_index: u32, - binding_index: u32, - ) -> Self { - Self { - name, - environment: environment_index + 1, - binding_index, - } - } - - /// Creates a binding locator that indicates that the binding is on the global object. - pub(super) const fn global(name: JsString) -> Self { - Self { - name, - environment: 0, - binding_index: 0, - } - } - - /// Returns the name of the binding. - pub(crate) const fn name(&self) -> &JsString { - &self.name - } - - /// Returns if the binding is located on the global object. - pub(crate) const fn is_global(&self) -> bool { - self.environment == 0 - } - - /// Returns the environment of the binding. - pub(crate) fn environment(&self) -> BindingLocatorEnvironment { - match self.environment { - 0 => BindingLocatorEnvironment::GlobalObject, - 1 => BindingLocatorEnvironment::GlobalDeclarative, - n => BindingLocatorEnvironment::Stack(n - 2), - } - } - - /// Sets the environment of the binding. - fn set_environment(&mut self, environment: BindingLocatorEnvironment) { - self.environment = match environment { - BindingLocatorEnvironment::GlobalObject => 0, - BindingLocatorEnvironment::GlobalDeclarative => 1, - BindingLocatorEnvironment::Stack(index) => index + 2, - }; - } - - /// Returns the binding index of the binding. - pub(crate) const fn binding_index(&self) -> u32 { - self.binding_index - } -} - -/// Action that is returned when a fallible binding operation. -#[derive(Debug)] -pub(crate) enum BindingLocatorError { - /// Trying to mutate immutable binding, - MutateImmutable, - - /// Indicates that any action is silently ignored. - Silent, -} - -/// The environment in which a binding is located. -#[derive(Clone, Copy, Debug)] -pub(crate) enum BindingLocatorEnvironment { - GlobalObject, - GlobalDeclarative, - Stack(u32), -} - impl Context { /// Gets the corresponding runtime binding of the provided `BindingLocator`, modifying /// its indexes in place. @@ -502,10 +377,9 @@ impl Context { } } - let (global, min_index) = match locator.environment() { - BindingLocatorEnvironment::GlobalObject - | BindingLocatorEnvironment::GlobalDeclarative => (true, 0), - BindingLocatorEnvironment::Stack(index) => (false, index), + let (global, min_index) = match locator.scope() { + BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => (true, 0), + BindingLocatorScope::Stack(index) => (false, index), }; let max_index = self.vm.environments.stack.len() as u32; @@ -513,11 +387,10 @@ impl Context { match self.environment_expect(index) { Environment::Declarative(env) => { if env.poisoned() { - let compile = env.compile_env(); - if compile.is_function() { - if let Some(b) = compile.get_binding(locator.name()) { - locator.set_environment(b.environment()); - locator.binding_index = b.binding_index(); + if let Some(env) = env.kind().as_function() { + if let Some(b) = env.compile().get_binding(locator.name()) { + locator.set_scope(b.scope()); + locator.set_binding_index(b.binding_index()); return Ok(()); } } @@ -535,21 +408,17 @@ impl Context { continue; } } - locator.set_environment(BindingLocatorEnvironment::Stack(index)); + locator.set_scope(BindingLocatorScope::Stack(index)); return Ok(()); } } } } - if global { - let env = self.vm.environments.global(); - if env.poisoned() { - let compile = env.compile_env(); - if let Some(b) = compile.get_binding(locator.name()) { - locator.set_environment(b.environment()); - locator.binding_index = b.binding_index(); - } + if global && self.realm().environment().poisoned() { + if let Some(b) = self.realm().scope().get_binding(locator.name()) { + locator.set_scope(b.scope()); + locator.set_binding_index(b.binding_index()); } } @@ -567,10 +436,9 @@ impl Context { } } - let min_index = match locator.environment() { - BindingLocatorEnvironment::GlobalObject - | BindingLocatorEnvironment::GlobalDeclarative => 0, - BindingLocatorEnvironment::Stack(index) => index, + let min_index = match locator.scope() { + BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => 0, + BindingLocatorScope::Stack(index) => index, }; let max_index = self.vm.environments.stack.len() as u32; @@ -578,9 +446,10 @@ impl Context { match self.environment_expect(index) { Environment::Declarative(env) => { if env.poisoned() { - let compile = env.compile_env(); - if compile.is_function() && compile.get_binding(locator.name()).is_some() { - break; + if let Some(env) = env.kind().as_function() { + if env.compile().get_binding(locator.name()).is_some() { + break; + } } } else if !env.with() { break; @@ -611,17 +480,17 @@ impl Context { /// /// Panics if the environment or binding index are out of range. pub(crate) fn is_initialized_binding(&mut self, locator: &BindingLocator) -> JsResult { - match locator.environment() { - BindingLocatorEnvironment::GlobalObject => { + match locator.scope() { + BindingLocatorScope::GlobalObject => { let key = locator.name().clone(); let obj = self.global_object(); obj.has_property(key, self) } - BindingLocatorEnvironment::GlobalDeclarative => { + BindingLocatorScope::GlobalDeclarative => { let env = self.vm.environments.global(); Ok(env.get(locator.binding_index()).is_some()) } - BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { + BindingLocatorScope::Stack(index) => match self.environment_expect(index) { Environment::Declarative(env) => Ok(env.get(locator.binding_index()).is_some()), Environment::Object(obj) => { let key = locator.name().clone(); @@ -638,17 +507,17 @@ impl Context { /// /// Panics if the environment or binding index are out of range. pub(crate) fn get_binding(&mut self, locator: &BindingLocator) -> JsResult> { - match locator.environment() { - BindingLocatorEnvironment::GlobalObject => { + match locator.scope() { + BindingLocatorScope::GlobalObject => { let key = locator.name().clone(); let obj = self.global_object(); obj.try_get(key, self) } - BindingLocatorEnvironment::GlobalDeclarative => { + BindingLocatorScope::GlobalDeclarative => { let env = self.vm.environments.global(); Ok(env.get(locator.binding_index())) } - BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { + BindingLocatorScope::Stack(index) => match self.environment_expect(index) { Environment::Declarative(env) => Ok(env.get(locator.binding_index())), Environment::Object(obj) => { let key = locator.name().clone(); @@ -671,17 +540,17 @@ impl Context { value: JsValue, strict: bool, ) -> JsResult<()> { - match locator.environment() { - BindingLocatorEnvironment::GlobalObject => { + match locator.scope() { + BindingLocatorScope::GlobalObject => { let key = locator.name().clone(); let obj = self.global_object(); obj.set(key, value, strict, self)?; } - BindingLocatorEnvironment::GlobalDeclarative => { + BindingLocatorScope::GlobalDeclarative => { let env = self.vm.environments.global(); env.set(locator.binding_index(), value); } - BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { + BindingLocatorScope::Stack(index) => match self.environment_expect(index) { Environment::Declarative(decl) => { decl.set(locator.binding_index(), value); } @@ -703,14 +572,14 @@ impl Context { /// /// Panics if the environment or binding index are out of range. pub(crate) fn delete_binding(&mut self, locator: &BindingLocator) -> JsResult { - match locator.environment() { - BindingLocatorEnvironment::GlobalObject => { + match locator.scope() { + BindingLocatorScope::GlobalObject => { let key = locator.name().clone(); let obj = self.global_object(); obj.__delete__(&key.into(), &mut self.into()) } - BindingLocatorEnvironment::GlobalDeclarative => Ok(false), - BindingLocatorEnvironment::Stack(index) => match self.environment_expect(index) { + BindingLocatorScope::GlobalDeclarative => Ok(false), + BindingLocatorScope::Stack(index) => match self.environment_expect(index) { Environment::Declarative(_) => Ok(false), Environment::Object(obj) => { let key = locator.name().clone(); diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index c0cd78f610..c8692895b6 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -163,15 +163,17 @@ impl Module { ) -> JsResult { let _timer = Profiler::global().start_event("Module parsing", "Main"); let path = src.path().map(Path::to_path_buf); + let realm = realm.unwrap_or_else(|| context.realm().clone()); + let mut parser = Parser::new(src); parser.set_identifier(context.next_parser_identifier()); - let module = parser.parse_module(context.interner_mut())?; + let module = parser.parse_module(realm.scope(), context.interner_mut())?; let src = SourceTextModule::new(module, context.interner()); Ok(Self { inner: Gc::new(ModuleRepr { - realm: realm.unwrap_or_else(|| context.realm().clone()), + realm, namespace: GcRefCell::default(), kind: ModuleKind::SourceText(src), host_defined: HostDefined::default(), diff --git a/core/engine/src/module/namespace.rs b/core/engine/src/module/namespace.rs index 1874d481bb..196e0dd084 100644 --- a/core/engine/src/module/namespace.rs +++ b/core/engine/src/module/namespace.rs @@ -309,7 +309,10 @@ fn module_namespace_exotic_try_get( }; let locator = env - .compile_env() + .kind() + .as_module() + .expect("must be module environment") + .compile() .get_binding(&name) .expect("checked before that the name was reachable"); @@ -386,7 +389,10 @@ fn module_namespace_exotic_get( }; let locator = env - .compile_env() + .kind() + .as_module() + .expect("must be module environment") + .compile() .get_binding(&name) .expect("checked before that the name was reachable"); diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index d35a4ca521..e7f10c7f10 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -2,13 +2,14 @@ use std::{cell::Cell, collections::HashSet, hash::BuildHasherDefault, rc::Rc}; use boa_ast::{ declaration::{ - ExportEntry, ImportEntry, ImportName, IndirectExportEntry, LexicalDeclaration, - LocalExportEntry, ReExportImportName, + ExportEntry, ImportEntry, ImportName, IndirectExportEntry, LocalExportEntry, + ReExportImportName, }, operations::{ bound_names, contains, lexically_scoped_declarations, var_scoped_declarations, ContainsSymbol, LexicallyScopedDeclaration, }, + scope::BindingLocator, }; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_interner::Interner; @@ -19,9 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use crate::{ builtins::{promise::PromiseCapability, Promise}, bytecompiler::{ByteCompiler, FunctionSpec, ToJsString}, - environments::{ - BindingLocator, CompileTimeEnvironment, DeclarativeEnvironment, EnvironmentStack, - }, + environments::{DeclarativeEnvironment, EnvironmentStack}, js_string, module::ModuleKind, object::{FunctionObjectBuilder, JsPromise}, @@ -1423,15 +1422,14 @@ impl SourceTextModule { // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // 6. Set module.[[Environment]] to env. let global_env = realm.environment().clone(); - let global_compile_env = global_env.compile_env(); - let env = Rc::new(CompileTimeEnvironment::new(global_compile_env, true)); + let env = self.code.source.scope().clone(); let mut compiler = ByteCompiler::new( js_string!("
"), true, false, - env.clone(), - env.clone(), + self.code.source.scope().clone(), + self.code.source.scope().clone(), true, false, context.interner_mut(), @@ -1471,7 +1469,7 @@ impl SourceTextModule { // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). // 3. Perform ! env.InitializeBinding(in.[[LocalName]], namespace). let local_name = entry.local_name().to_js_string(compiler.interner()); - let locator = env.create_immutable_binding(local_name, true); + let locator = env.get_binding(&local_name).expect("binding must exist"); if let BindingName::Name(_) = resolution.binding_name { // 1. Perform env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], @@ -1493,10 +1491,8 @@ impl SourceTextModule { // b. If in.[[ImportName]] is namespace-object, then // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). // iii. Perform ! env.InitializeBinding(in.[[LocalName]], namespace). - let locator = env.create_immutable_binding( - entry.local_name().to_js_string(compiler.interner()), - true, - ); + let name = entry.local_name().to_js_string(compiler.interner()); + let locator = env.get_binding(&name).expect("binding must exist"); // i. Let namespace be GetModuleNamespace(importedModule). // deferred to initialization below @@ -1522,10 +1518,12 @@ impl SourceTextModule { if !declared_var_names.contains(&name) { // 1. Perform ! env.CreateMutableBinding(dn, false). // 2. Perform ! env.InitializeBinding(dn, undefined). - let binding = env.create_mutable_binding(name.clone(), false); + let binding = env + .get_binding_reference(&name) + .expect("binding must exist"); let index = compiler.get_or_insert_binding(binding); compiler.emit_opcode(Opcode::PushUndefined); - compiler.emit_with_varying_operand(Opcode::DefInitVar, index); + compiler.emit_binding_access(Opcode::DefInitVar, &index); // 3. Append dn to declaredVarNames. declared_var_names.push(name); @@ -1549,68 +1547,38 @@ impl SourceTextModule { // 2. Perform ! env.InitializeBinding(dn, fo). // // deferred to below. - let (mut spec, locator): (FunctionSpec<'_>, _) = match declaration { + let (spec, locator): (FunctionSpec<'_>, _) = match declaration { LexicallyScopedDeclaration::FunctionDeclaration(f) => { let name = bound_names(f)[0].to_js_string(compiler.interner()); - let locator = env.create_mutable_binding(name, false); + let locator = env.get_binding(&name).expect("binding must exist"); (f.into(), locator) } LexicallyScopedDeclaration::GeneratorDeclaration(g) => { let name = bound_names(g)[0].to_js_string(compiler.interner()); - let locator = env.create_mutable_binding(name, false); + let locator = env.get_binding(&name).expect("binding must exist"); (g.into(), locator) } LexicallyScopedDeclaration::AsyncFunctionDeclaration(af) => { let name = bound_names(af)[0].to_js_string(compiler.interner()); - let locator = env.create_mutable_binding(name, false); + let locator = env.get_binding(&name).expect("binding must exist"); (af.into(), locator) } LexicallyScopedDeclaration::AsyncGeneratorDeclaration(ag) => { let name = bound_names(ag)[0].to_js_string(compiler.interner()); - let locator = env.create_mutable_binding(name, false); + let locator = env.get_binding(&name).expect("binding must exist"); (ag.into(), locator) } - LexicallyScopedDeclaration::ClassDeclaration(class) => { - for name in bound_names(class) { - let name = name.to_js_string(compiler.interner()); - env.create_mutable_binding(name, false); - } - continue; - } - // i. If IsConstantDeclaration of d is true, then - LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const( - c, - )) => { - // a. For each element dn of the BoundNames of d, do - for name in bound_names(c) { - let name = name.to_js_string(compiler.interner()); - // 1. Perform ! env.CreateImmutableBinding(dn, true). - env.create_immutable_binding(name, true); - } - continue; - } - LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Let(l)) => { - for name in bound_names(l) { - let name = name.to_js_string(compiler.interner()); - env.create_mutable_binding(name, false); - } - continue; - } - LexicallyScopedDeclaration::AssignmentExpression(expr) => { - for name in bound_names(expr) { - let name = name.to_js_string(compiler.interner()); - env.create_mutable_binding(name, false); - } + LexicallyScopedDeclaration::ClassDeclaration(_) + | LexicallyScopedDeclaration::LexicalDeclaration(_) + | LexicallyScopedDeclaration::AssignmentExpression(_) => { continue; } }; - spec.has_binding_identifier = false; - functions.push((spec, locator)); } @@ -1628,7 +1596,7 @@ impl SourceTextModule { // 8. Let moduleContext be a new ECMAScript code execution context. let mut envs = EnvironmentStack::new(global_env); - envs.push_module(env); + envs.push_module(self.code.source.scope().clone()); // 9. Set the Function of moduleContext to null. // 10. Assert: module.[[Realm]] is not undefined. @@ -1656,7 +1624,7 @@ impl SourceTextModule { // i. Let namespace be GetModuleNamespace(importedModule). let namespace = module.namespace(context); context.vm.environments.put_lexical_value( - locator.environment(), + locator.scope(), locator.binding_index(), namespace.into(), ); @@ -1677,7 +1645,7 @@ impl SourceTextModule { BindingName::Namespace => { let namespace = export_locator.module.namespace(context); context.vm.environments.put_lexical_value( - locator.environment(), + locator.scope(), locator.binding_index(), namespace.into(), ); @@ -1693,7 +1661,7 @@ impl SourceTextModule { let function = create_function_object_fast(code, context); context.vm.environments.put_lexical_value( - locator.environment(), + locator.scope(), locator.binding_index(), function.into(), ); diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 381fe6a32c..5e8309e0b6 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -1,12 +1,11 @@ -use std::rc::Rc; - +use boa_ast::scope::Scope; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use rustc_hash::FxHashSet; use crate::{ builtins::promise::ResolvingFunctions, bytecompiler::ByteCompiler, - environments::{CompileTimeEnvironment, DeclarativeEnvironment, EnvironmentStack}, + environments::{DeclarativeEnvironment, EnvironmentStack}, js_string, object::JsPromise, vm::{ActiveRunnable, CallFrame, CodeBlock}, @@ -205,12 +204,18 @@ impl SyntheticModule { export_name.to_std_string_escaped() )) })?; - let locator = env.compile_env().get_binding(export_name).ok_or_else(|| { - JsNativeError::reference().with_message(format!( - "cannot set name `{}` which was not included in the list of exports", - export_name.to_std_string_escaped() - )) - })?; + let locator = env + .kind() + .as_module() + .expect("must be module environment") + .compile() + .get_binding(export_name) + .ok_or_else(|| { + JsNativeError::reference().with_message(format!( + "cannot set name `{}` which was not included in the list of exports", + export_name.to_std_string_escaped() + )) + })?; env.set(locator.binding_index(), export_value); Ok(()) @@ -275,8 +280,8 @@ impl SyntheticModule { // 2. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // 3. Set module.[[Environment]] to env. let global_env = module_self.realm().environment().clone(); - let global_compile_env = global_env.compile_env(); - let module_compile_env = Rc::new(CompileTimeEnvironment::new(global_compile_env, true)); + let global_scope = module_self.realm().scope().clone(); + let module_scope = Scope::new(global_scope, true); // TODO: A bit of a hack to be able to pass the currently active runnable without an // available codeblock to execute. @@ -284,8 +289,8 @@ impl SyntheticModule { js_string!("
"), true, false, - module_compile_env.clone(), - module_compile_env.clone(), + module_scope.clone(), + module_scope.clone(), false, false, context.interner_mut(), @@ -298,19 +303,19 @@ impl SyntheticModule { .iter() .map(|name| { // a. Perform ! env.CreateMutableBinding(exportName, false). - module_compile_env.create_mutable_binding(name.clone(), false) + module_scope.create_mutable_binding(name.clone(), false) }) .collect::>(); let cb = Gc::new(compiler.finish()); let mut envs = EnvironmentStack::new(global_env); - envs.push_module(module_compile_env); + envs.push_module(module_scope); for locator in exports { // b. Perform ! env.InitializeBinding(exportName, undefined). envs.put_lexical_value( - locator.environment(), + locator.scope(), locator.binding_index(), JsValue::undefined(), ); diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index 255d713024..b034420c2a 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -8,6 +8,7 @@ use std::any::TypeId; +use boa_ast::scope::Scope; use rustc_hash::FxHashMap; use crate::{ @@ -54,7 +55,16 @@ impl std::fmt::Debug for Realm { #[derive(Trace, Finalize)] struct Inner { intrinsics: Intrinsics, + + /// The global declarative environment of this realm. environment: Gc, + + /// The global scope of this realm. + /// This is directly related to the global declarative environment. + // Safety: Nothing in `Scope` needs tracing. + #[unsafe_ignore_trace] + scope: Scope, + global_object: JsObject, global_this: JsObject, template_map: GcRefCell>, @@ -79,11 +89,13 @@ impl Realm { .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); let environment = Gc::new(DeclarativeEnvironment::global()); + let scope = Scope::new_global(); let realm = Self { inner: Gc::new(Inner { intrinsics, environment, + scope, global_object, global_this, template_map: GcRefCell::default(), @@ -156,6 +168,12 @@ impl Realm { &self.inner.environment } + /// Returns the scope of this realm. + #[must_use] + pub fn scope(&self) -> &Scope { + &self.inner.scope + } + pub(crate) fn global_object(&self) -> &JsObject { &self.inner.global_object } @@ -170,7 +188,7 @@ impl Realm { /// Resizes the number of bindings on the global environment. pub(crate) fn resize_global_env(&self) { - let binding_number = self.environment().compile_env().num_bindings(); + let binding_number = self.scope().num_bindings(); let env = self .environment() .kind() diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index 17c76a4550..52c59110e2 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -90,7 +90,8 @@ impl Script { if context.is_strict() { parser.set_strict(); } - let mut code = parser.parse_script(context.interner_mut())?; + let scope = context.realm().scope().clone(); + let mut code = parser.parse_script(&scope, context.interner_mut())?; if !context.optimizer_options().is_empty() { context.optimize_statement_list(code.statements_mut()); } @@ -124,7 +125,7 @@ impl Script { global_declaration_instantiation_context( &mut annex_b_function_names, &self.inner.source, - &self.inner.realm.environment().compile_env(), + self.inner.realm.scope(), context, )?; @@ -132,8 +133,8 @@ impl Script { js_string!("
"), self.inner.source.strict(), false, - self.inner.realm.environment().compile_env(), - self.inner.realm.environment().compile_env(), + self.inner.realm.scope().clone(), + self.inner.realm.scope().clone(), false, false, context.interner_mut(), @@ -146,10 +147,7 @@ impl Script { } // TODO: move to `Script::evaluate` to make this operation infallible. - compiler.global_declaration_instantiation( - &self.inner.source, - &self.inner.realm.environment().compile_env(), - ); + compiler.global_declaration_instantiation(&self.inner.source); compiler.compile_statement_list(self.inner.source.statements(), true, false); let cb = Gc::new(compiler.finish()); diff --git a/core/engine/src/vm/call_frame/mod.rs b/core/engine/src/vm/call_frame/mod.rs index 1aee80f95b..3aca4f33a2 100644 --- a/core/engine/src/vm/call_frame/mod.rs +++ b/core/engine/src/vm/call_frame/mod.rs @@ -7,12 +7,13 @@ use crate::{ iterable::IteratorRecord, promise::{PromiseCapability, ResolvingFunctions}, }, - environments::{BindingLocator, EnvironmentStack}, + environments::EnvironmentStack, object::{JsFunction, JsObject}, realm::Realm, vm::CodeBlock, JsValue, }; +use boa_ast::scope::BindingLocator; use boa_gc::{Finalize, Gc, Trace}; use thin_vec::ThinVec; @@ -55,8 +56,14 @@ pub struct CallFrame { pub(crate) iterators: ThinVec, // The stack of bindings being updated. + // SAFETY: Nothing in `BindingLocator` requires tracing, so this is safe. + #[unsafe_ignore_trace] pub(crate) binding_stack: Vec, + // SAFETY: Nothing requires tracing, so this is safe. + #[unsafe_ignore_trace] + pub(crate) local_binings_initialized: Box<[bool]>, + /// How many iterations a loop has done. pub(crate) loop_iteration_count: u64, @@ -147,6 +154,7 @@ impl CallFrame { environments: EnvironmentStack, realm: Realm, ) -> Self { + let local_binings_initialized = code_block.local_bindings_initialized.clone(); Self { code_block, pc: 0, @@ -155,6 +163,7 @@ impl CallFrame { argument_count: 0, iterators: ThinVec::new(), binding_stack: Vec::new(), + local_binings_initialized, loop_iteration_count: 0, active_runnable, environments, diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 0a7c2871b8..6c461b1b21 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -7,14 +7,14 @@ use crate::{ function::{OrdinaryFunction, ThisMode}, OrdinaryObject, }, - environments::{BindingLocator, CompileTimeEnvironment}, object::JsObject, Context, JsBigInt, JsString, JsValue, }; use bitflags::bitflags; +use boa_ast::scope::{BindingLocator, Scope}; use boa_gc::{empty_trace, Finalize, Gc, Trace}; use boa_profiler::Profiler; -use std::{cell::Cell, fmt::Display, mem::size_of, rc::Rc}; +use std::{cell::Cell, fmt::Display, mem::size_of}; use thin_vec::ThinVec; use super::{InlineCache, Instruction, InstructionIterator}; @@ -112,11 +112,9 @@ pub(crate) enum Constant { Function(Gc), BigInt(#[unsafe_ignore_trace] JsBigInt), - /// Compile time environments in this function. - // Safety: Nothing in CompileTimeEnvironment needs tracing, so this is safe. - // - // TODO(#3034): Maybe changing this to Gc after garbage collection would be better than Rc. - CompileTimeEnvironment(#[unsafe_ignore_trace] Rc), + /// Declarative or function scope. + // Safety: Nothing in `Scope` needs tracing, so this is safe. + Scope(#[unsafe_ignore_trace] Scope), } /// The internal representation of a JavaScript function. @@ -157,6 +155,8 @@ pub struct CodeBlock { #[unsafe_ignore_trace] pub(crate) bindings: Box<[BindingLocator]>, + pub(crate) local_bindings_initialized: Box<[bool]>, + /// Exception [`Handler`]s. #[unsafe_ignore_trace] pub(crate) handlers: ThinVec, @@ -176,6 +176,7 @@ impl CodeBlock { bytecode: Box::default(), constants: ThinVec::default(), bindings: Box::default(), + local_bindings_initialized: Box::default(), name, flags: Cell::new(flags), length, @@ -308,21 +309,18 @@ impl CodeBlock { panic!("expected function constant at index {index}") } - /// Get the [`CompileTimeEnvironment`] constant from the [`CodeBlock`]. + /// Get the [`Scope`] constant from the [`CodeBlock`]. /// /// # Panics /// - /// If the type of the [`Constant`] is not [`Constant::CompileTimeEnvironment`]. + /// If the type of the [`Constant`] is not [`Constant::Scope`]. /// Or `index` is greater or equal to length of `constants`. - pub(crate) fn constant_compile_time_environment( - &self, - index: usize, - ) -> Rc { - if let Some(Constant::CompileTimeEnvironment(value)) = self.constants.get(index) { + pub(crate) fn constant_scope(&self, index: usize) -> Scope { + if let Some(Constant::Scope(value)) = self.constants.get(index) { return value.clone(); } - panic!("expected compile time environment constant at index {index}") + panic!("expected scope constant at index {index}") } } @@ -416,8 +414,11 @@ impl CodeBlock { | Instruction::Coalesce { exit: value } => value.to_string(), Instruction::CallEval { argument_count: value, + scope_index, + } => { + format!("{}, {}", value.value(), scope_index.value()) } - | Instruction::Call { + Instruction::Call { argument_count: value, } | Instruction::New { @@ -428,9 +429,9 @@ impl CodeBlock { } | Instruction::ConcatToString { value_count: value } | Instruction::GetArgument { index: value } => value.value().to_string(), - Instruction::PushDeclarativeEnvironment { - compile_environments_index, - } => compile_environments_index.value().to_string(), + Instruction::PushScope { index } | Instruction::CallEvalSpread { index } => { + index.value().to_string() + } Instruction::CopyDataProperties { excluded_key_count: value1, excluded_key_count_computed: value2, @@ -549,8 +550,12 @@ impl CodeBlock { } => { format!("is_anonymous_function: {is_anonymous_function}") } - Instruction::PopIntoRegister { dst } => format!("dst:reg{}", dst.value()), - Instruction::PushFromRegister { src } => format!("src:reg{}", src.value()), + Instruction::PopIntoRegister { dst } | Instruction::PopIntoLocal { dst } => { + format!("dst:reg{}", dst.value()) + } + Instruction::PushFromRegister { src } | Instruction::PushFromLocal { src } => { + format!("src:reg{}", src.value()) + } Instruction::Pop | Instruction::Dup | Instruction::Swap @@ -657,7 +662,6 @@ impl CodeBlock { | Instruction::NewTarget | Instruction::ImportMeta | Instruction::SuperCallPrepare - | Instruction::CallEvalSpread | Instruction::CallSpread | Instruction::NewSpread | Instruction::SuperCallSpread @@ -724,9 +728,7 @@ impl CodeBlock { | Instruction::Reserved46 | Instruction::Reserved47 | Instruction::Reserved48 - | Instruction::Reserved49 - | Instruction::Reserved50 - | Instruction::Reserved51 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"), } } } @@ -800,11 +802,11 @@ impl Display for CodeBlock { code.name().to_std_string_escaped(), code.length )?, - Constant::CompileTimeEnvironment(v) => { + Constant::Scope(v) => { writeln!( f, - "[ENVIRONMENT] index: {}, bindings: {}", - v.environment_index(), + "[SCOPE] index: {}, bindings: {}", + v.scope_index(), v.num_bindings() )?; } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 05d83a7ab1..baf607b03a 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -227,7 +227,7 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Instruction::PushDeclarativeEnvironment { .. } => { + Instruction::PushScope { .. } => { let random = rand::random(); graph.add_node( @@ -434,7 +434,7 @@ impl CodeBlock { | Instruction::Await | Instruction::NewTarget | Instruction::ImportMeta - | Instruction::CallEvalSpread + | Instruction::CallEvalSpread { .. } | Instruction::CallSpread | Instruction::NewSpread | Instruction::SuperCallSpread @@ -457,6 +457,8 @@ impl CodeBlock { | Instruction::CreateGlobalVarBinding { .. } | Instruction::PopIntoRegister { .. } | Instruction::PushFromRegister { .. } + | Instruction::PopIntoLocal { .. } + | Instruction::PushFromLocal { .. } | Instruction::Nop => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); @@ -514,9 +516,7 @@ impl CodeBlock { | Instruction::Reserved46 | Instruction::Reserved47 | Instruction::Reserved48 - | Instruction::Reserved49 - | Instruction::Reserved50 - | Instruction::Reserved51 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"), } } diff --git a/core/engine/src/vm/opcode/call/mod.rs b/core/engine/src/vm/opcode/call/mod.rs index 4519f589c9..7db445a0cd 100644 --- a/core/engine/src/vm/opcode/call/mod.rs +++ b/core/engine/src/vm/opcode/call/mod.rs @@ -15,7 +15,11 @@ use crate::{ pub(crate) struct CallEval; impl CallEval { - fn operation(context: &mut Context, argument_count: usize) -> JsResult { + fn operation( + context: &mut Context, + argument_count: usize, + scope_index: usize, + ) -> JsResult { let at = context.vm.stack.len() - argument_count; let func = &context.vm.stack[at - 1]; @@ -45,7 +49,14 @@ impl CallEval { // let strictCaller be true. Otherwise let strictCaller be false. // v. Return ? PerformEval(evalArg, strictCaller, true). let strict = context.vm.frame().code_block.strict(); - let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; + let scope = context.vm.frame().code_block().constant_scope(scope_index); + let result = crate::builtins::eval::Eval::perform_eval( + x, + true, + Some(scope), + strict, + context, + )?; context.vm.push(result); } else { // NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`. @@ -63,23 +74,24 @@ impl CallEval { impl Operation for CallEval { const NAME: &'static str = "CallEval"; const INSTRUCTION: &'static str = "INST - CallEval"; - // TODO: Calls will require a big refactor in order to track - // the cost of the call. const COST: u8 = 5; fn execute(context: &mut Context) -> JsResult { let argument_count = context.vm.read::(); - Self::operation(context, argument_count as usize) + let scope_index = context.vm.read::(); + Self::operation(context, argument_count as usize, scope_index as usize) } fn execute_with_u16_operands(context: &mut Context) -> JsResult { let argument_count = context.vm.read::() as usize; - Self::operation(context, argument_count) + let scope_index = context.vm.read::(); + Self::operation(context, argument_count, scope_index as usize) } fn execute_with_u32_operands(context: &mut Context) -> JsResult { let argument_count = context.vm.read::(); - Self::operation(context, argument_count as usize) + let scope_index = context.vm.read::(); + Self::operation(context, argument_count as usize, scope_index as usize) } } @@ -90,14 +102,8 @@ impl Operation for CallEval { #[derive(Debug, Clone, Copy)] pub(crate) struct CallEvalSpread; -impl Operation for CallEvalSpread { - const NAME: &'static str = "CallEvalSpread"; - const INSTRUCTION: &'static str = "INST - CallEvalSpread"; - // TODO: Calls will require a big refactor in order to track - // the cost of the call. - const COST: u8 = 5; - - fn execute(context: &mut Context) -> JsResult { +impl CallEvalSpread { + fn operation(context: &mut Context, index: usize) -> JsResult { // Get the arguments that are stored as an array object on the stack. let arguments_array = context.vm.pop(); let arguments_array_object = arguments_array @@ -137,7 +143,14 @@ impl Operation for CallEvalSpread { // let strictCaller be true. Otherwise let strictCaller be false. // v. Return ? PerformEval(evalArg, strictCaller, true). let strict = context.vm.frame().code_block.strict(); - let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; + let scope = context.vm.frame().code_block().constant_scope(index); + let result = crate::builtins::eval::Eval::perform_eval( + x, + true, + Some(scope), + strict, + context, + )?; context.vm.push(result); } else { // NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`. @@ -155,6 +168,27 @@ impl Operation for CallEvalSpread { } } +impl Operation for CallEvalSpread { + const NAME: &'static str = "CallEvalSpread"; + const INSTRUCTION: &'static str = "INST - CallEvalSpread"; + const COST: u8 = 5; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + Self::operation(context, index as usize) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + Self::operation(context, index as usize) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + Self::operation(context, index as usize) + } +} + /// `Call` implements the Opcode Operation for `Opcode::Call` /// /// Operation: diff --git a/core/engine/src/vm/opcode/define/mod.rs b/core/engine/src/vm/opcode/define/mod.rs index 71f5a20f20..ecc5cb4a7d 100644 --- a/core/engine/src/vm/opcode/define/mod.rs +++ b/core/engine/src/vm/opcode/define/mod.rs @@ -23,7 +23,7 @@ impl DefVar { let binding_locator = context.vm.frame().code_block.bindings[index].clone(); context.vm.environments.put_value_if_uninitialized( - binding_locator.environment(), + binding_locator.scope(), binding_locator.binding_index(), JsValue::undefined(), ); @@ -106,7 +106,7 @@ impl PutLexicalValue { let value = context.vm.pop(); let binding_locator = context.vm.frame().code_block.bindings[index].clone(); context.vm.environments.put_lexical_value( - binding_locator.environment(), + binding_locator.scope(), binding_locator.binding_index(), value, ); diff --git a/core/engine/src/vm/opcode/locals/mod.rs b/core/engine/src/vm/opcode/locals/mod.rs new file mode 100644 index 0000000000..20d4e62633 --- /dev/null +++ b/core/engine/src/vm/opcode/locals/mod.rs @@ -0,0 +1,89 @@ +use crate::{ + vm::{opcode::Operation, CompletionType}, + Context, JsNativeError, JsResult, +}; + +/// `PopIntoLocal` implements the Opcode Operation for `Opcode::PopIntoLocal` +/// +/// Operation: +/// - Pop value from the stack and push to a local binding register `dst`. +#[derive(Debug, Clone, Copy)] +pub(crate) struct PopIntoLocal; + +impl PopIntoLocal { + #[allow(clippy::unnecessary_wraps)] + #[allow(clippy::needless_pass_by_value)] + fn operation(dst: u32, context: &mut Context) -> JsResult { + context.vm.frame_mut().local_binings_initialized[dst as usize] = true; + let value = context.vm.pop(); + + let rp = context.vm.frame().rp; + context.vm.stack[(rp + dst) as usize] = value; + Ok(CompletionType::Normal) + } +} + +impl Operation for PopIntoLocal { + const NAME: &'static str = "PopIntoLocal"; + const INSTRUCTION: &'static str = "INST - PopIntoLocal"; + const COST: u8 = 2; + + fn execute(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let dst = context.vm.read::(); + Self::operation(dst, context) + } +} + +/// `PushFromLocal` implements the Opcode Operation for `Opcode::PushFromLocal` +/// +/// Operation: +/// - Copy value at local binding register `src` and push it into the stack. +#[derive(Debug, Clone, Copy)] +pub(crate) struct PushFromLocal; + +impl PushFromLocal { + #[allow(clippy::unnecessary_wraps)] + #[allow(clippy::needless_pass_by_value)] + fn operation(dst: u32, context: &mut Context) -> JsResult { + if !context.vm.frame().local_binings_initialized[dst as usize] { + return Err(JsNativeError::reference() + .with_message("access to uninitialized binding") + .into()); + } + let rp = context.vm.frame().rp; + let value = context.vm.stack[(rp + dst) as usize].clone(); + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +impl Operation for PushFromLocal { + const NAME: &'static str = "PushFromLocal"; + const INSTRUCTION: &'static str = "INST - PushFromLocal"; + const COST: u8 = 2; + + fn execute(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let dst = context.vm.read::(); + Self::operation(dst, context) + } +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 92812e4642..5d2f075c8a 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -19,6 +19,7 @@ mod generator; mod get; mod global; mod iteration; +mod locals; mod meta; mod modifier; mod new; @@ -65,6 +66,8 @@ pub(crate) use global::*; #[doc(inline)] pub(crate) use iteration::*; #[doc(inline)] +pub(crate) use locals::*; +#[doc(inline)] pub(crate) use meta::*; #[doc(inline)] pub(crate) use modifier::*; @@ -1698,17 +1701,17 @@ generate_opcodes! { /// Call a function named "eval". /// - /// Operands: argument_count: `VaryingOperand` + /// Operands: argument_count: `VaryingOperand`, scope_index: `VaryingOperand` /// /// Stack: this, func, argument_1, ... argument_n **=>** result - CallEval { argument_count: VaryingOperand }, + CallEval { argument_count: VaryingOperand, scope_index: VaryingOperand }, /// Call a function named "eval" where the arguments contain spreads. /// /// Operands: /// /// Stack: this, func, arguments_array **=>** result - CallEvalSpread, + CallEvalSpread { index: VaryingOperand }, /// Call a function. /// @@ -1794,12 +1797,26 @@ generate_opcodes! { /// Stack: **=>** value PushFromRegister { src: VaryingOperand }, + /// Pop value from the stack and push to a local binding register `dst`. + /// + /// Operands: + /// + /// Stack: value **=>** + PopIntoLocal { dst: VaryingOperand }, + + /// Copy value at local binding register `src` and push it into the stack. + /// + /// Operands: + /// + /// Stack: **=>** value + PushFromLocal { src: VaryingOperand }, + /// Push a declarative environment. /// - /// Operands: compile_environments_index: `VaryingOperand` + /// Operands: index: `VaryingOperand` /// /// Stack: **=>** - PushDeclarativeEnvironment { compile_environments_index: VaryingOperand }, + PushScope { index: VaryingOperand }, /// Push an object environment. /// @@ -2265,10 +2282,6 @@ generate_opcodes! { Reserved48 => Reserved, /// Reserved [`Opcode`]. Reserved49 => Reserved, - /// Reserved [`Opcode`]. - Reserved50 => Reserved, - /// Reserved [`Opcode`]. - Reserved51 => Reserved, } /// Specific opcodes for bindings. diff --git a/core/engine/src/vm/opcode/push/environment.rs b/core/engine/src/vm/opcode/push/environment.rs index ca2a5e2e64..cfd66eca84 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -6,47 +6,40 @@ use crate::{ }; use boa_gc::Gc; -/// `PushDeclarativeEnvironment` implements the Opcode Operation for `Opcode::PushDeclarativeEnvironment` +/// `PushScope` implements the Opcode Operation for `Opcode::PushScope` /// /// Operation: /// - Push a declarative environment #[derive(Debug, Clone, Copy)] -pub(crate) struct PushDeclarativeEnvironment; +pub(crate) struct PushScope; -impl PushDeclarativeEnvironment { +impl PushScope { #[allow(clippy::unnecessary_wraps)] - fn operation( - context: &mut Context, - compile_environments_index: usize, - ) -> JsResult { - let compile_environment = context - .vm - .frame() - .code_block() - .constant_compile_time_environment(compile_environments_index); - context.vm.environments.push_lexical(compile_environment); + fn operation(context: &mut Context, index: usize) -> JsResult { + let scope = context.vm.frame().code_block().constant_scope(index); + context.vm.environments.push_lexical(scope.num_bindings()); Ok(CompletionType::Normal) } } -impl Operation for PushDeclarativeEnvironment { - const NAME: &'static str = "PushDeclarativeEnvironment"; - const INSTRUCTION: &'static str = "INST - PushDeclarativeEnvironment"; +impl Operation for PushScope { + const NAME: &'static str = "PushScope"; + const INSTRUCTION: &'static str = "INST - PushScope"; const COST: u8 = 3; fn execute(context: &mut Context) -> JsResult { - let compile_environments_index = context.vm.read::() as usize; - Self::operation(context, compile_environments_index) + let index = context.vm.read::() as usize; + Self::operation(context, index) } fn execute_with_u16_operands(context: &mut Context) -> JsResult { - let compile_environments_index = context.vm.read::() as usize; - Self::operation(context, compile_environments_index) + let index = context.vm.read::() as usize; + Self::operation(context, index) } fn execute_with_u32_operands(context: &mut Context) -> JsResult { - let compile_environments_index = context.vm.read::() as usize; - Self::operation(context, compile_environments_index) + let index = context.vm.read::() as usize; + Self::operation(context, index) } } diff --git a/core/engine/src/vm/opcode/set/name.rs b/core/engine/src/vm/opcode/set/name.rs index 244c25e6c6..2d2fa2ec37 100644 --- a/core/engine/src/vm/opcode/set/name.rs +++ b/core/engine/src/vm/opcode/set/name.rs @@ -1,5 +1,7 @@ +use boa_ast::scope::{BindingLocator, BindingLocatorScope}; + use crate::{ - environments::{BindingLocator, BindingLocatorEnvironment, Environment}, + environments::Environment, vm::{opcode::Operation, CompletionType}, Context, JsNativeError, JsResult, }; @@ -125,17 +127,17 @@ fn verify_initialized(locator: &BindingLocator, context: &mut Context) -> JsResu let key = locator.name(); let strict = context.vm.frame().code_block.strict(); - let message = match locator.environment() { - BindingLocatorEnvironment::GlobalObject if strict => Some(format!( + let message = match locator.scope() { + BindingLocatorScope::GlobalObject if strict => Some(format!( "cannot assign to uninitialized global property `{}`", key.to_std_string_escaped() )), - BindingLocatorEnvironment::GlobalObject => None, - BindingLocatorEnvironment::GlobalDeclarative => Some(format!( + BindingLocatorScope::GlobalObject => None, + BindingLocatorScope::GlobalDeclarative => Some(format!( "cannot assign to uninitialized binding `{}`", key.to_std_string_escaped() )), - BindingLocatorEnvironment::Stack(index) => match context.environment_expect(index) { + BindingLocatorScope::Stack(index) => match context.environment_expect(index) { Environment::Declarative(_) => Some(format!( "cannot assign to uninitialized binding `{}`", key.to_std_string_escaped() diff --git a/core/engine/src/vm/runtime_limits.rs b/core/engine/src/vm/runtime_limits.rs index bc78205ffa..e62a8e433d 100644 --- a/core/engine/src/vm/runtime_limits.rs +++ b/core/engine/src/vm/runtime_limits.rs @@ -17,7 +17,7 @@ impl Default for RuntimeLimits { Self { loop_iteration_limit: u64::MAX, resursion_limit: 512, - stack_size_limit: 1024, + stack_size_limit: 1024 * 10, } } } diff --git a/core/parser/src/parser/expression/assignment/arrow_function.rs b/core/parser/src/parser/expression/assignment/arrow_function.rs index 2193187432..dbf4ec4560 100644 --- a/core/parser/src/parser/expression/assignment/arrow_function.rs +++ b/core/parser/src/parser/expression/assignment/arrow_function.rs @@ -26,7 +26,7 @@ use boa_ast::{ function::{FormalParameter, FormalParameterList}, operations::{contains, ContainsSymbol}, statement::Return, - Expression, Punctuator, StatementList, + Expression, Punctuator, }; use boa_interner::Interner; use boa_profiler::Profiler; @@ -98,18 +98,6 @@ where ) }; - cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?; - - cursor.expect( - TokenKind::Punctuator(Punctuator::Arrow), - "arrow function", - interner, - )?; - let arrow = cursor.arrow(); - cursor.set_arrow(true); - let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?; - cursor.set_arrow(arrow); - // Early Error: ArrowFormalParameters are UniqueFormalParameters. if params.has_duplicates() { return Err(Error::lex(LexError::Syntax( @@ -134,6 +122,18 @@ where ))); } + cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?; + + cursor.expect( + TokenKind::Punctuator(Punctuator::Arrow), + "arrow function", + interner, + )?; + let arrow = cursor.arrow(); + cursor.set_arrow(true); + let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?; + cursor.set_arrow(arrow); + // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true // and IsSimpleParameterList of ArrowParameters is false. if body.strict() && !params.is_simple() { @@ -182,23 +182,23 @@ where type Output = ast::function::FunctionBody; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - let stmts = - match cursor.peek(0, interner).or_abrupt()?.kind() { - TokenKind::Punctuator(Punctuator::OpenBlock) => { - cursor.advance(interner); - let body = FunctionBody::new(false, false).parse(cursor, interner)?; - cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?; - body - } - _ => ast::function::FunctionBody::new(StatementList::from(vec![ - ast::Statement::Return(Return::new( - ExpressionBody::new(self.allow_in, false) - .parse(cursor, interner)? - .into(), - )) - .into(), - ])), - }; + let stmts = match cursor.peek(0, interner).or_abrupt()?.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + cursor.advance(interner); + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + cursor.expect(Punctuator::CloseBlock, "arrow function", interner)?; + body + } + _ => ast::function::FunctionBody::new( + [ast::Statement::Return(Return::new( + ExpressionBody::new(self.allow_in, false) + .parse(cursor, interner)? + .into(), + )) + .into()], + false, + ), + }; Ok(stmts) } diff --git a/core/parser/src/parser/expression/assignment/async_arrow_function.rs b/core/parser/src/parser/expression/assignment/async_arrow_function.rs index 1698993f36..db8a4940cf 100644 --- a/core/parser/src/parser/expression/assignment/async_arrow_function.rs +++ b/core/parser/src/parser/expression/assignment/async_arrow_function.rs @@ -27,7 +27,7 @@ use boa_ast::{ declaration::Variable, function::{FormalParameter, FormalParameterList}, statement::Return, - Punctuator, StatementList, + Punctuator, }; use boa_interner::Interner; use boa_profiler::Profiler; @@ -173,23 +173,23 @@ where type Output = ast::function::FunctionBody; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { - let body = - match cursor.peek(0, interner).or_abrupt()?.kind() { - TokenKind::Punctuator(Punctuator::OpenBlock) => { - cursor.advance(interner); - let body = FunctionBody::new(false, true).parse(cursor, interner)?; - cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?; - body - } - _ => ast::function::FunctionBody::new(StatementList::from(vec![ - ast::Statement::Return(Return::new( - ExpressionBody::new(self.allow_in, true) - .parse(cursor, interner)? - .into(), - )) - .into(), - ])), - }; + let body = match cursor.peek(0, interner).or_abrupt()?.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + cursor.advance(interner); + let body = FunctionBody::new(false, true).parse(cursor, interner)?; + cursor.expect(Punctuator::CloseBlock, "async arrow function", interner)?; + body + } + _ => ast::function::FunctionBody::new( + [ast::Statement::Return(Return::new( + ExpressionBody::new(self.allow_in, true) + .parse(cursor, interner)? + .into(), + )) + .into()], + false, + ), + }; Ok(body) } diff --git a/core/parser/src/parser/expression/primary/async_function_expression/tests.rs b/core/parser/src/parser/expression/primary/async_function_expression/tests.rs index c4122e5ec3..b2b7da43ef 100644 --- a/core/parser/src/parser/expression/primary/async_function_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/async_function_expression/tests.rs @@ -27,10 +27,10 @@ fn check_async_expression() { Some(add.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), - ))] - .into(), + ))], + false, ), false, ) @@ -65,7 +65,7 @@ fn check_nested_async_expression() { Some(a.into()), FormalParameterList::default(), FunctionBody::new( - vec![Declaration::Lexical(LexicalDeclaration::Const( + [Declaration::Lexical(LexicalDeclaration::Const( vec![Variable::from_identifier( b.into(), Some( @@ -73,11 +73,11 @@ fn check_nested_async_expression() { Some(b.into()), FormalParameterList::default(), FunctionBody::new( - vec![Statement::Return(Return::new(Some( + [Statement::Return(Return::new(Some( Literal::from(1).into(), ))) - .into()] - .into(), + .into()], + false, ), false, ) @@ -87,8 +87,8 @@ fn check_nested_async_expression() { .try_into() .unwrap(), )) - .into()] - .into(), + .into()], + false, ), false, ) diff --git a/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs b/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs index 82a8fdc7c5..fc967459ea 100644 --- a/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/async_generator_expression/tests.rs @@ -28,10 +28,10 @@ fn check_async_generator_expr() { Some(add.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), - ))] - .into(), + ))], + false, ), false, ) @@ -66,7 +66,7 @@ fn check_nested_async_generator_expr() { Some(a.into()), FormalParameterList::default(), FunctionBody::new( - vec![Declaration::Lexical(LexicalDeclaration::Const( + [Declaration::Lexical(LexicalDeclaration::Const( vec![Variable::from_identifier( b.into(), Some( @@ -74,12 +74,10 @@ fn check_nested_async_generator_expr() { Some(b.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement( - Statement::Return(Return::new(Some( - Literal::from(1).into(), - ))), - )] - .into(), + [StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(1).into())), + ))], + false, ), false, ) @@ -89,8 +87,8 @@ fn check_nested_async_generator_expr() { .try_into() .unwrap(), )) - .into()] - .into(), + .into()], + false, ), false, ) diff --git a/core/parser/src/parser/expression/primary/function_expression/tests.rs b/core/parser/src/parser/expression/primary/function_expression/tests.rs index e56edc5bc8..8786bde77a 100644 --- a/core/parser/src/parser/expression/primary/function_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/function_expression/tests.rs @@ -27,10 +27,10 @@ fn check_function_expression() { Some(add.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(1).into())), - ))] - .into(), + ))], + false, ), false, ) @@ -65,7 +65,7 @@ fn check_nested_function_expression() { Some(a.into()), FormalParameterList::default(), FunctionBody::new( - vec![Declaration::Lexical(LexicalDeclaration::Const( + [Declaration::Lexical(LexicalDeclaration::Const( vec![Variable::from_identifier( b.into(), Some( @@ -73,12 +73,10 @@ fn check_nested_function_expression() { Some(b.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement( - Statement::Return(Return::new(Some( - Literal::from(1).into(), - ))), - )] - .into(), + [StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(1).into())), + ))], + false, ), false, ) @@ -88,8 +86,8 @@ fn check_nested_function_expression() { .try_into() .unwrap(), )) - .into()] - .into(), + .into()], + false, ), false, ) @@ -116,13 +114,14 @@ fn check_function_non_reserved_keyword() { Some($interner.get_or_intern_static($keyword, utf16!($keyword)).into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement( + [StatementListItem::Statement( Statement::Return( Return::new( Some(Literal::from(1).into()) ) ) - )].into() + )], + false, ), true, ) diff --git a/core/parser/src/parser/expression/primary/generator_expression/tests.rs b/core/parser/src/parser/expression/primary/generator_expression/tests.rs index 5b633992ef..6e46f517c1 100644 --- a/core/parser/src/parser/expression/primary/generator_expression/tests.rs +++ b/core/parser/src/parser/expression/primary/generator_expression/tests.rs @@ -25,10 +25,10 @@ fn check_generator_function_expression() { Some(gen.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Expression( + [StatementListItem::Statement(Statement::Expression( Expression::from(Yield::new(Some(Literal::from(1).into()), false)), - ))] - .into(), + ))], + false, ), false, ) @@ -60,10 +60,10 @@ fn check_generator_function_delegate_yield_expression() { Some(gen.into()), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Expression( + [StatementListItem::Statement(Statement::Expression( Expression::from(Yield::new(Some(Literal::from(1).into()), true)), - ))] - .into(), + ))], + false, ), false, ) diff --git a/core/parser/src/parser/expression/primary/object_initializer/mod.rs b/core/parser/src/parser/expression/primary/object_initializer/mod.rs index 8ec83d6c1d..c292502627 100644 --- a/core/parser/src/parser/expression/primary/object_initializer/mod.rs +++ b/core/parser/src/parser/expression/primary/object_initializer/mod.rs @@ -31,12 +31,15 @@ use boa_ast::{ }, Identifier, }, - function::{ClassElementName as ClassElementNameNode, FormalParameterList, PrivateName}, + function::{ + ClassElementName as ClassElementNameNode, FormalParameterList, + FunctionBody as FunctionBodyAst, PrivateName, + }, operations::{ bound_names, contains, has_direct_super_new, lexically_declared_names, ContainsSymbol, }, property::{MethodDefinitionKind, PropertyName as PropertyNameNode}, - Expression, Keyword, Punctuator, Script, + Expression, Keyword, Punctuator, }; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; @@ -749,7 +752,7 @@ impl TokenParser for GeneratorMethod where R: ReadChar, { - type Output = (ClassElementNameNode, FormalParameterList, Script); + type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst); fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("GeneratorMethod", "Parsing"); @@ -845,7 +848,7 @@ impl TokenParser for AsyncGeneratorMethod where R: ReadChar, { - type Output = (ClassElementNameNode, FormalParameterList, Script); + type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst); fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("AsyncGeneratorMethod", "Parsing"); @@ -955,7 +958,7 @@ impl TokenParser for AsyncMethod where R: ReadChar, { - type Output = (ClassElementNameNode, FormalParameterList, Script); + type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst); fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("AsyncMethod", "Parsing"); diff --git a/core/parser/src/parser/expression/tests.rs b/core/parser/src/parser/expression/tests.rs index 3d3b6ef6c6..37754d63ea 100644 --- a/core/parser/src/parser/expression/tests.rs +++ b/core/parser/src/parser/expression/tests.rs @@ -10,8 +10,8 @@ use boa_ast::{ }, Call, Identifier, Parenthesized, RegExpLiteral, }, - function::{AsyncArrowFunction, FormalParameter, FormalParameterList}, - Declaration, Expression, Script, Statement, + function::{AsyncArrowFunction, FormalParameter, FormalParameterList, FunctionBody}, + Declaration, Expression, Statement, }; use boa_interner::{Interner, Sym}; use boa_macros::utf16; @@ -701,7 +701,7 @@ fn parse_async_arrow_function_named_of() { ), false, )]), - Script::default(), + FunctionBody::default(), ))) .into(), ], diff --git a/core/parser/src/parser/function/mod.rs b/core/parser/src/parser/function/mod.rs index 1f48e08612..8e51d20455 100644 --- a/core/parser/src/parser/function/mod.rs +++ b/core/parser/src/parser/function/mod.rs @@ -455,7 +455,7 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("FunctionStatementList", "Parsing"); - let statement_list = StatementList::new( + let body = StatementList::new( self.allow_yield, self.allow_await, true, @@ -465,20 +465,20 @@ where ) .parse(cursor, interner)?; - if let Err(error) = check_labels(&statement_list) { + if let Err(error) = check_labels(&body) { return Err(Error::lex(LexError::Syntax( error.message(interner).into(), Position::new(1, 1), ))); } - if contains_invalid_object_literal(&statement_list) { + if contains_invalid_object_literal(&body) { return Err(Error::lex(LexError::Syntax( "invalid object literal in function statement list".into(), Position::new(1, 1), ))); } - Ok(ast::function::FunctionBody::new(statement_list)) + Ok(body.into()) } } diff --git a/core/parser/src/parser/function/tests.rs b/core/parser/src/parser/function/tests.rs index f3b9c7050d..487a03bf45 100644 --- a/core/parser/src/parser/function/tests.rs +++ b/core/parser/src/parser/function/tests.rs @@ -32,12 +32,12 @@ fn check_basic() { interner.get_or_intern_static("foo", utf16!("foo")).into(), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), )), - ))] - .into(), + ))], + false, ), )) .into()], @@ -70,12 +70,12 @@ fn check_duplicates_strict_off() { interner.get_or_intern_static("foo", utf16!("foo")).into(), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), )), - ))] - .into(), + ))], + false, ), )) .into()], @@ -106,12 +106,12 @@ fn check_basic_semicolon_insertion() { interner.get_or_intern_static("foo", utf16!("foo")).into(), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::from(interner.get_or_intern_static("a", utf16!("a"))).into(), )), - ))] - .into(), + ))], + false, ), )) .into()], @@ -135,10 +135,10 @@ fn check_empty_return() { interner.get_or_intern_static("foo", utf16!("foo")).into(), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(None), - ))] - .into(), + ))], + false, ), )) .into()], @@ -162,10 +162,10 @@ fn check_empty_return_semicolon_insertion() { interner.get_or_intern_static("foo", utf16!("foo")).into(), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(None), - ))] - .into(), + ))], + false, ), )) .into()], @@ -286,7 +286,7 @@ fn check_arrow() { None, params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Binary::new( ArithmeticOp::Add.into(), @@ -295,8 +295,8 @@ fn check_arrow() { ) .into(), )), - ))] - .into(), + ))], + false, ), ))) .into()], @@ -324,7 +324,7 @@ fn check_arrow_semicolon_insertion() { None, params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Binary::new( ArithmeticOp::Add.into(), @@ -333,8 +333,8 @@ fn check_arrow_semicolon_insertion() { ) .into(), )), - ))] - .into(), + ))], + false, ), ))) .into()], @@ -362,10 +362,10 @@ fn check_arrow_epty_return() { None, params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(None), - ))] - .into(), + ))], + false, ), ))) .into()], @@ -393,10 +393,10 @@ fn check_arrow_empty_return_semicolon_insertion() { None, params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(None), - ))] - .into(), + ))], + false, ), ))) .into()], @@ -423,15 +423,15 @@ fn check_arrow_assignment() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -464,15 +464,15 @@ fn check_arrow_assignment_nobrackets() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -505,15 +505,15 @@ fn check_arrow_assignment_noparenthesis() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -546,15 +546,15 @@ fn check_arrow_assignment_noparenthesis_nobrackets() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -593,15 +593,15 @@ fn check_arrow_assignment_2arg() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -640,15 +640,15 @@ fn check_arrow_assignment_2arg_nobrackets() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -691,15 +691,15 @@ fn check_arrow_assignment_3arg() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), @@ -742,15 +742,15 @@ fn check_arrow_assignment_3arg_nobrackets() { Some(interner.get_or_intern_static("foo", utf16!("foo")).into()), params, FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some( Identifier::new( interner.get_or_intern_static("a", utf16!("a")), ) .into(), )), - ))] - .into(), + ))], + false, ), ) .into(), diff --git a/core/parser/src/parser/mod.rs b/core/parser/src/parser/mod.rs index ada983b6e2..2dc352c860 100644 --- a/core/parser/src/parser/mod.rs +++ b/core/parser/src/parser/mod.rs @@ -26,6 +26,7 @@ use boa_ast::{ all_private_identifiers_valid, check_labels, contains, contains_invalid_object_literal, lexically_declared_names, var_declared_names, ContainsSymbol, }, + scope::Scope, Position, StatementList, }; use boa_interner::Interner; @@ -139,9 +140,20 @@ impl<'a, R: ReadChar> Parser<'a, R> { /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// /// [spec]: https://tc39.es/ecma262/#prod-Script - pub fn parse_script(&mut self, interner: &mut Interner) -> ParseResult { + pub fn parse_script( + &mut self, + scope: &Scope, + interner: &mut Interner, + ) -> ParseResult { self.cursor.set_goal(InputElement::HashbangOrRegExp); - ScriptParser::new(false).parse(&mut self.cursor, interner) + let mut ast = ScriptParser::new(false).parse(&mut self.cursor, interner)?; + if !ast.analyze_scope(scope, interner) { + return Err(Error::general( + "invalid scope analysis", + Position::new(1, 1), + )); + } + Ok(ast) } /// Parse the full input as an [ECMAScript Module][spec] into the boa AST representation. @@ -152,12 +164,23 @@ impl<'a, R: ReadChar> Parser<'a, R> { /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// /// [spec]: https://tc39.es/ecma262/#prod-Module - pub fn parse_module(&mut self, interner: &mut Interner) -> ParseResult + pub fn parse_module( + &mut self, + scope: &Scope, + interner: &mut Interner, + ) -> ParseResult where R: ReadChar, { self.cursor.set_goal(InputElement::HashbangOrRegExp); - ModuleParser.parse(&mut self.cursor, interner) + let mut module = ModuleParser.parse(&mut self.cursor, interner)?; + if !module.analyze_scope(scope, interner) { + return Err(Error::general( + "invalid scope analysis", + Position::new(1, 1), + )); + } + Ok(module) } /// [`19.2.1.1 PerformEval ( x, strictCaller, direct )`][spec] diff --git a/core/parser/src/parser/statement/block/tests.rs b/core/parser/src/parser/statement/block/tests.rs index 5270bd2d0b..1d1e917093 100644 --- a/core/parser/src/parser/statement/block/tests.rs +++ b/core/parser/src/parser/statement/block/tests.rs @@ -82,10 +82,10 @@ fn non_empty() { hello.into(), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(10).into())), - ))] - .into(), + ))], + false, ), )) .into(), @@ -139,10 +139,10 @@ fn hoisting() { hello.into(), FormalParameterList::default(), FunctionBody::new( - vec![StatementListItem::Statement(Statement::Return( + [StatementListItem::Statement(Statement::Return( Return::new(Some(Literal::from(10).into())), - ))] - .into(), + ))], + false, ), )) .into(), diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs index de2d0e2f84..d878d77d96 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -26,8 +26,9 @@ use boa_ast::{ self as ast, expression::Identifier, function::{ - self, ClassDeclaration as ClassDeclarationNode, ClassElementName, ClassMethodDefinition, - FormalParameterList, FunctionExpression, + self, ClassDeclaration as ClassDeclarationNode, ClassElementName, ClassFieldDefinition, + ClassMethodDefinition, FormalParameterList, FunctionExpression, PrivateFieldDefinition, + StaticBlockBody, }, operations::{contains, contains_arguments, ContainsSymbol}, Expression, Keyword, Punctuator, @@ -414,8 +415,8 @@ where } } } - function::ClassElement::PrivateFieldDefinition(name, init) => { - if let Some(node) = init { + function::ClassElement::PrivateFieldDefinition(field) => { + if let Some(node) = field.field() { if contains(node, ContainsSymbol::SuperCall) { return Err(Error::lex(LexError::Syntax( "invalid super usage".into(), @@ -424,7 +425,7 @@ where } } if private_elements_names - .insert(name.description(), PrivateElement::Value) + .insert(field.name().description(), PrivateElement::Value) .is_some() { return Err(Error::general( @@ -452,16 +453,18 @@ where )); } } - function::ClassElement::FieldDefinition(_, Some(node)) - | function::ClassElement::StaticFieldDefinition(_, Some(node)) => { - if contains(node, ContainsSymbol::SuperCall) { - return Err(Error::lex(LexError::Syntax( - "invalid super usage".into(), - position, - ))); + function::ClassElement::FieldDefinition(field) + | function::ClassElement::StaticFieldDefinition(field) => { + if let Some(field) = field.field() { + if contains(field, ContainsSymbol::SuperCall) { + return Err(Error::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } } } - _ => {} + function::ClassElement::StaticBlock(_) => {} } elements.push(element); } @@ -675,7 +678,7 @@ where cursor.set_strict(strict); statement_list }; - function::ClassElement::StaticBlock(function::FunctionBody::new(statement_list)) + function::ClassElement::StaticBlock(StaticBlockBody::new(statement_list.into())) } TokenKind::Punctuator(Punctuator::Mul) => { let token = cursor.peek(1, interner).or_abrupt()?; @@ -937,16 +940,14 @@ where } _ => { cursor.expect_semicolon("expected semicolon", interner)?; + let field = ClassFieldDefinition::new( + ast::property::PropertyName::Literal(Sym::GET), + None, + ); if r#static { - function::ClassElement::StaticFieldDefinition( - ast::property::PropertyName::Literal(Sym::GET), - None, - ) + function::ClassElement::StaticFieldDefinition(field) } else { - function::ClassElement::FieldDefinition( - ast::property::PropertyName::Literal(Sym::GET), - None, - ) + function::ClassElement::FieldDefinition(field) } } } @@ -1055,16 +1056,14 @@ where } _ => { cursor.expect_semicolon("expected semicolon", interner)?; + let field = ClassFieldDefinition::new( + ast::property::PropertyName::Literal(Sym::SET), + None, + ); if r#static { - function::ClassElement::StaticFieldDefinition( - ast::property::PropertyName::Literal(Sym::SET), - None, - ) + function::ClassElement::StaticFieldDefinition(field) } else { - function::ClassElement::FieldDefinition( - ast::property::PropertyName::Literal(Sym::SET), - None, - ) + function::ClassElement::FieldDefinition(field) } } } @@ -1102,8 +1101,7 @@ where ) } else { function::ClassElement::PrivateFieldDefinition( - PrivateName::new(name), - Some(rhs), + PrivateFieldDefinition::new(PrivateName::new(name), Some(rhs)), ) } } @@ -1150,8 +1148,7 @@ where ) } else { function::ClassElement::PrivateFieldDefinition( - PrivateName::new(name), - None, + PrivateFieldDefinition::new(PrivateName::new(name), None), ) } } @@ -1195,10 +1192,11 @@ where if let Some(name) = name.literal() { rhs.set_anonymous_function_definition_name(&Identifier::new(name)); } + let field = ClassFieldDefinition::new(name, Some(rhs)); if r#static { - function::ClassElement::StaticFieldDefinition(name, Some(rhs)) + function::ClassElement::StaticFieldDefinition(field) } else { - function::ClassElement::FieldDefinition(name, Some(rhs)) + function::ClassElement::FieldDefinition(field) } } TokenKind::Punctuator(Punctuator::OpenParen) => { @@ -1259,10 +1257,11 @@ where } } cursor.expect_semicolon("expected semicolon", interner)?; + let field = ClassFieldDefinition::new(name, None); if r#static { - function::ClassElement::StaticFieldDefinition(name, None) + function::ClassElement::StaticFieldDefinition(field) } else { - function::ClassElement::FieldDefinition(name, None) + function::ClassElement::FieldDefinition(field) } } } @@ -1273,10 +1272,28 @@ where match &element { // FieldDefinition : ClassElementName Initializer [opt] // It is a Syntax Error if Initializer is present and ContainsArguments of Initializer is true. - function::ClassElement::FieldDefinition(_, Some(node)) - | function::ClassElement::StaticFieldDefinition(_, Some(node)) - | function::ClassElement::PrivateFieldDefinition(_, Some(node)) - | function::ClassElement::PrivateStaticFieldDefinition(_, Some(node)) => { + function::ClassElement::FieldDefinition(field) + | function::ClassElement::StaticFieldDefinition(field) => { + if let Some(field) = field.field() { + if contains_arguments(field) { + return Err(Error::general( + "'arguments' not allowed in class field definition", + position, + )); + } + } + } + function::ClassElement::PrivateFieldDefinition(field) => { + if let Some(node) = field.field() { + if contains_arguments(node) { + return Err(Error::general( + "'arguments' not allowed in class field definition", + position, + )); + } + } + } + function::ClassElement::PrivateStaticFieldDefinition(_, Some(node)) => { if contains_arguments(node) { return Err(Error::general( "'arguments' not allowed in class field definition", diff --git a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs index cce7f5b748..95305bd81e 100644 --- a/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs +++ b/core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs @@ -7,11 +7,11 @@ use boa_ast::{ Call, Identifier, }, function::{ - ClassDeclaration, ClassElement, ClassMethodDefinition, FormalParameterList, FunctionBody, - FunctionExpression, + ClassDeclaration, ClassElement, ClassFieldDefinition, ClassMethodDefinition, + FormalParameterList, FunctionBody, FunctionExpression, }, property::{MethodDefinitionKind, PropertyName}, - Declaration, Expression, Statement, StatementList, StatementListItem, + Declaration, Expression, Statement, StatementListItem, }; use boa_interner::Interner; use boa_macros::utf16; @@ -50,10 +50,10 @@ fn check_async_ordinary_method() { fn check_async_field_initialization() { let interner = &mut Interner::default(); - let elements = vec![ClassElement::FieldDefinition( + let elements = vec![ClassElement::FieldDefinition(ClassFieldDefinition::new( PropertyName::Literal(interner.get_or_intern_static("async", utf16!("async"))), Some(Literal::from(1).into()), - )]; + ))]; check_script_parser( "class A { @@ -76,10 +76,10 @@ fn check_async_field_initialization() { fn check_async_field() { let interner = &mut Interner::default(); - let elements = vec![ClassElement::FieldDefinition( + let elements = vec![ClassElement::FieldDefinition(ClassFieldDefinition::new( PropertyName::Literal(interner.get_or_intern_static("async", utf16!("async"))), None, - )]; + ))]; check_script_parser( "class A { @@ -121,10 +121,7 @@ fn check_new_target_with_property_access() { let constructor = FunctionExpression::new( Some(interner.get_or_intern_static("A", utf16!("A")).into()), FormalParameterList::default(), - FunctionBody::new(StatementList::new( - [Statement::Expression(console).into()], - false, - )), + FunctionBody::new([Statement::Expression(console).into()], false), false, ); diff --git a/core/parser/src/parser/statement/iteration/for_statement.rs b/core/parser/src/parser/statement/iteration/for_statement.rs index 5c0363b4e8..cd39238255 100644 --- a/core/parser/src/parser/statement/iteration/for_statement.rs +++ b/core/parser/src/parser/statement/iteration/for_statement.rs @@ -262,16 +262,15 @@ where (init, _) => init, }; - if let Some(ForLoopInitializer::Lexical(ast::declaration::LexicalDeclaration::Const( - ref list, - ))) = init - { - for decl in list.as_ref() { - if decl.init().is_none() { - return Err(Error::general( - "Expected initializer for const declaration", - position, - )); + if let Some(ForLoopInitializer::Lexical(initializer)) = &init { + if let ast::declaration::LexicalDeclaration::Const(list) = initializer.declaration() { + for decl in list.as_ref() { + if decl.init().is_none() { + return Err(Error::general( + "Expected initializer for const declaration", + position, + )); + } } } } @@ -313,9 +312,9 @@ where // Early Error: It is a Syntax Error if any element of the BoundNames of // LexicalDeclaration also occurs in the VarDeclaredNames of Statement. // Note: only applies to lexical bindings. - if let Some(ForLoopInitializer::Lexical(ref decl)) = init { + if let Some(ForLoopInitializer::Lexical(initializer)) = &init { let vars = var_declared_names(&body); - for name in bound_names(decl) { + for name in bound_names(initializer.declaration()) { if vars.contains(&name) { return Err(Error::general( "For loop initializer declared in loop body", @@ -381,30 +380,32 @@ fn initializer_to_iterable_loop_initializer( ))), } } - ForLoopInitializer::Lexical(decl) => match decl.variable_list().as_ref() { - [declaration] => { - if declaration.init().is_some() { - return Err(Error::lex(LexError::Syntax( + ForLoopInitializer::Lexical(initializer) => { + match initializer.declaration().variable_list().as_ref() { + [decl] => { + if decl.init().is_some() { + return Err(Error::lex(LexError::Syntax( format!("a lexical declaration in the head of a {loop_type} loop can't have an initializer") .into(), position, ))); - } - Ok(match decl { - ast::declaration::LexicalDeclaration::Const(_) => { - IterableLoopInitializer::Const(declaration.binding().clone()) - } - ast::declaration::LexicalDeclaration::Let(_) => { - IterableLoopInitializer::Let(declaration.binding().clone()) } - }) + Ok(match initializer.declaration() { + ast::declaration::LexicalDeclaration::Const(_) => { + IterableLoopInitializer::Const(decl.binding().clone()) + } + ast::declaration::LexicalDeclaration::Let(_) => { + IterableLoopInitializer::Let(decl.binding().clone()) + } + }) + } + _ => Err(Error::lex(LexError::Syntax( + format!("only one variable can be declared in the head of a {loop_type} loop") + .into(), + position, + ))), } - _ => Err(Error::lex(LexError::Syntax( - format!("only one variable can be declared in the head of a {loop_type} loop") - .into(), - position, - ))), - }, + } ForLoopInitializer::Var(decl) => match decl.0.as_ref() { [declaration] => { // https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads diff --git a/core/parser/src/parser/tests/format/mod.rs b/core/parser/src/parser/tests/format/mod.rs index f55892f7b5..8c57395dbb 100644 --- a/core/parser/src/parser/tests/format/mod.rs +++ b/core/parser/src/parser/tests/format/mod.rs @@ -17,6 +17,7 @@ fn test_formatting(source: &'static str) { // Remove preceding newline. use crate::{Parser, Source}; + use boa_ast::scope::Scope; use boa_interner::{Interner, ToInternedString}; let source = &source[1..]; @@ -33,7 +34,7 @@ fn test_formatting(source: &'static str) { let source = Source::from_bytes(source); let interner = &mut Interner::default(); let result = Parser::new(source) - .parse_script(interner) + .parse_script(&Scope::new_global(), interner) .expect("parsing failed") .to_interned_string(interner); if scenario != result { diff --git a/core/parser/src/parser/tests/mod.rs b/core/parser/src/parser/tests/mod.rs index f248cbf310..f4cf4ce27b 100644 --- a/core/parser/src/parser/tests/mod.rs +++ b/core/parser/src/parser/tests/mod.rs @@ -22,6 +22,7 @@ use boa_ast::{ ArrowFunction, FormalParameter, FormalParameterList, FormalParameterListFlags, FunctionBody, FunctionDeclaration, }, + scope::Scope, statement::{If, Return}, Expression, Script, Statement, StatementList, StatementListItem, }; @@ -34,11 +35,14 @@ pub(super) fn check_script_parser(js: &str, expr: L, interner: &mut Interner) where L: Into>, { + let mut script = Script::new(StatementList::from(expr.into())); + let scope = Scope::new_global(); + script.analyze_scope(&scope, interner); assert_eq!( Parser::new(Source::from_bytes(js)) - .parse_script(interner) + .parse_script(&Scope::new_global(), interner) .expect("failed to parse"), - Script::new(StatementList::from(expr.into())) + script, ); } @@ -46,7 +50,7 @@ where #[track_caller] pub(super) fn check_invalid_script(js: &str) { assert!(Parser::new(Source::from_bytes(js)) - .parse_script(&mut Interner::default()) + .parse_script(&Scope::new_global(), &mut Interner::default()) .is_err()); } @@ -126,8 +130,8 @@ fn hoisting() { hello.into(), FormalParameterList::default(), FunctionBody::new( - vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()] - .into(), + [Statement::Return(Return::new(Some(Literal::from(10).into()))).into()], + false, ), )) .into(), @@ -508,7 +512,8 @@ fn spread_in_arrow_function() { None, params, FunctionBody::new( - vec![Statement::Expression(Expression::from(Identifier::from(b))).into()].into(), + [Statement::Expression(Expression::from(Identifier::from(b))).into()], + false, ), ))) .into()], diff --git a/examples/src/bin/commuter_visitor.rs b/examples/src/bin/commuter_visitor.rs index f0040103b2..7b9e001fca 100644 --- a/examples/src/bin/commuter_visitor.rs +++ b/examples/src/bin/commuter_visitor.rs @@ -63,7 +63,8 @@ fn main() { let mut parser = Parser::new(Source::from_filepath(Path::new("./scripts/calc.js")).unwrap()); let mut ctx = Context::default(); - let mut script = parser.parse_script(ctx.interner_mut()).unwrap(); + let scope = ctx.realm().scope().clone(); + let mut script = parser.parse_script(&scope, ctx.interner_mut()).unwrap(); let mut visitor = CommutorVisitor::default(); diff --git a/examples/src/bin/symbol_visitor.rs b/examples/src/bin/symbol_visitor.rs index 76086db169..3d33bb421a 100644 --- a/examples/src/bin/symbol_visitor.rs +++ b/examples/src/bin/symbol_visitor.rs @@ -27,7 +27,8 @@ fn main() { let mut parser = Parser::new(Source::from_filepath(Path::new("./scripts/calc.js")).unwrap()); let mut ctx = Context::default(); - let script = parser.parse_script(ctx.interner_mut()).unwrap(); + let scope = ctx.realm().scope().clone(); + let script = parser.parse_script(&scope, ctx.interner_mut()).unwrap(); let mut visitor = SymbolVisitor::default(); diff --git a/tests/fuzz/fuzz_targets/parser-idempotency.rs b/tests/fuzz/fuzz_targets/parser-idempotency.rs index af9f2bf15c..ab4196eea0 100644 --- a/tests/fuzz/fuzz_targets/parser-idempotency.rs +++ b/tests/fuzz/fuzz_targets/parser-idempotency.rs @@ -3,6 +3,7 @@ mod common; use crate::common::FuzzData; +use boa_ast::scope::Scope; use boa_interner::ToInternedString; use boa_parser::{Parser, Source}; use libfuzzer_sys::{fuzz_target, Corpus}; @@ -15,11 +16,11 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box> { let original = data.ast.to_interned_string(&data.interner); let mut parser = Parser::new(Source::from_reader(Cursor::new(&original), None)); - + let scope = Scope::new_global(); let before = data.interner.len(); // For a variety of reasons, we may not actually produce valid code here (e.g., nameless function). // Fail fast and only make the next checks if we were valid. - if let Ok(first) = parser.parse_script(&mut data.interner) { + if let Ok(first) = parser.parse_script(&scope, &mut data.interner) { let after_first = data.interner.len(); let first_interned = first.to_interned_string(&data.interner); @@ -33,10 +34,11 @@ fn do_fuzz(mut data: FuzzData) -> Result<(), Box> { first ); let mut parser = Parser::new(Source::from_reader(Cursor::new(&first_interned), None)); + let second_scope = Scope::new_global(); // Now, we most assuredly should produce valid code. It has already gone through a first pass. let second = parser - .parse_script(&mut data.interner) + .parse_script(&second_scope, &mut data.interner) .expect("Could not parse the first-pass interned copy."); let second_interned = second.to_interned_string(&data.interner); let after_second = data.interner.len();