Browse Source

Implement scope analysis and local variables (#3988)

* Remove unnecessary compile time environment clones

* Remove compile time environments from every runtime DeclarativeEnvironment

* Implement scope analysis and local variables

* fix docs and fuzzer errors

* Apply suggestions

* Align `parse_script` and `parse_module` arguments

* Fix fuzzer
pull/4003/head
raskad 2 months ago committed by GitHub
parent
commit
aa09d98787
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      Cargo.lock
  2. 6
      cli/src/main.rs
  3. 2
      core/ast/Cargo.toml
  4. 31
      core/ast/src/expression/literal/object.rs
  5. 35
      core/ast/src/function/arrow_function.rs
  6. 35
      core/ast/src/function/async_arrow_function.rs
  7. 74
      core/ast/src/function/async_function.rs
  8. 73
      core/ast/src/function/async_generator.rs
  9. 262
      core/ast/src/function/class.rs
  10. 76
      core/ast/src/function/generator.rs
  11. 90
      core/ast/src/function/mod.rs
  12. 85
      core/ast/src/function/ordinary_function.rs
  13. 31
      core/ast/src/lib.rs
  14. 159
      core/ast/src/operations.rs
  15. 529
      core/ast/src/scope.rs
  16. 1992
      core/ast/src/scope_analyzer.rs
  17. 78
      core/ast/src/source.rs
  18. 21
      core/ast/src/statement/block.rs
  19. 37
      core/ast/src/statement/iteration/for_in_loop.rs
  20. 75
      core/ast/src/statement/iteration/for_loop.rs
  21. 37
      core/ast/src/statement/iteration/for_of_loop.rs
  22. 2
      core/ast/src/statement/iteration/mod.rs
  23. 28
      core/ast/src/statement/switch.rs
  24. 30
      core/ast/src/statement/try.rs
  25. 15
      core/ast/src/statement/with.rs
  26. 2
      core/ast/src/statement_list.rs
  27. 7
      core/ast/src/visitor.rs
  28. 2
      core/engine/Cargo.toml
  29. 70
      core/engine/src/builtins/eval/mod.rs
  30. 1
      core/engine/src/builtins/function/arguments.rs
  31. 43
      core/engine/src/builtins/function/mod.rs
  32. 7
      core/engine/src/builtins/json/mod.rs
  33. 101
      core/engine/src/bytecompiler/class.rs
  34. 656
      core/engine/src/bytecompiler/declarations.rs
  35. 25
      core/engine/src/bytecompiler/env.rs
  36. 31
      core/engine/src/bytecompiler/expression/assign.rs
  37. 2
      core/engine/src/bytecompiler/expression/object_literal.rs
  38. 8
      core/engine/src/bytecompiler/expression/unary.rs
  39. 31
      core/engine/src/bytecompiler/expression/update.rs
  40. 40
      core/engine/src/bytecompiler/function.rs
  41. 346
      core/engine/src/bytecompiler/mod.rs
  42. 2
      core/engine/src/bytecompiler/module.rs
  43. 22
      core/engine/src/bytecompiler/statement/block.rs
  44. 191
      core/engine/src/bytecompiler/statement/loop.rs
  45. 22
      core/engine/src/bytecompiler/statement/switch.rs
  46. 17
      core/engine/src/bytecompiler/statement/try.rs
  47. 8
      core/engine/src/bytecompiler/statement/with.rs
  48. 219
      core/engine/src/environments/compile.rs
  49. 10
      core/engine/src/environments/mod.rs
  50. 19
      core/engine/src/environments/runtime/declarative/function.rs
  51. 35
      core/engine/src/environments/runtime/declarative/mod.rs
  52. 18
      core/engine/src/environments/runtime/declarative/module.rs
  53. 263
      core/engine/src/environments/runtime/mod.rs
  54. 6
      core/engine/src/module/mod.rs
  55. 10
      core/engine/src/module/namespace.rs
  56. 84
      core/engine/src/module/source.rs
  57. 37
      core/engine/src/module/synthetic.rs
  58. 20
      core/engine/src/realm.rs
  59. 14
      core/engine/src/script.rs
  60. 11
      core/engine/src/vm/call_frame/mod.rs
  61. 58
      core/engine/src/vm/code_block.rs
  62. 10
      core/engine/src/vm/flowgraph/mod.rs
  63. 66
      core/engine/src/vm/opcode/call/mod.rs
  64. 4
      core/engine/src/vm/opcode/define/mod.rs
  65. 89
      core/engine/src/vm/opcode/locals/mod.rs
  66. 31
      core/engine/src/vm/opcode/mod.rs
  67. 37
      core/engine/src/vm/opcode/push/environment.rs
  68. 14
      core/engine/src/vm/opcode/set/name.rs
  69. 2
      core/engine/src/vm/runtime_limits.rs
  70. 60
      core/parser/src/parser/expression/assignment/arrow_function.rs
  71. 36
      core/parser/src/parser/expression/assignment/async_arrow_function.rs
  72. 18
      core/parser/src/parser/expression/primary/async_function_expression/tests.rs
  73. 22
      core/parser/src/parser/expression/primary/async_generator_expression/tests.rs
  74. 27
      core/parser/src/parser/expression/primary/function_expression/tests.rs
  75. 12
      core/parser/src/parser/expression/primary/generator_expression/tests.rs
  76. 13
      core/parser/src/parser/expression/primary/object_initializer/mod.rs
  77. 6
      core/parser/src/parser/expression/tests.rs
  78. 8
      core/parser/src/parser/function/mod.rs
  79. 102
      core/parser/src/parser/function/tests.rs
  80. 31
      core/parser/src/parser/mod.rs
  81. 12
      core/parser/src/parser/statement/block/tests.rs
  82. 101
      core/parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs
  83. 19
      core/parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs
  84. 61
      core/parser/src/parser/statement/iteration/for_statement.rs
  85. 3
      core/parser/src/parser/tests/format/mod.rs
  86. 17
      core/parser/src/parser/tests/mod.rs
  87. 3
      examples/src/bin/commuter_visitor.rs
  88. 3
      examples/src/bin/symbol_visitor.rs
  89. 8
      tests/fuzz/fuzz_targets/parser-idempotency.rs

1
Cargo.lock generated

@ -315,6 +315,7 @@ dependencies = [
"bitflags 2.6.0",
"boa_interner",
"boa_macros",
"boa_string",
"indexmap",
"num-bigint",
"rustc-hash 2.0.0",

6
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 {

2
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

31
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(&parameters, 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<V::BreakTy>
@ -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)
}
}

35
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<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 ArrowFunction {
/// Creates a new `ArrowFunctionDecl` AST Expression.
#[inline]
#[must_use]
pub const fn new(
pub fn new(
name: Option<Identifier>,
params: FormalParameterList,
parameters: FormalParameterList,
body: FunctionBody,
) -> Self {
let contains_direct_eval = contains(&parameters, 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<Identifier> {
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<Identifier>) {
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<V::BreakTy>
@ -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)
}
}

35
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<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 AsyncArrowFunction {
/// Creates a new `AsyncArrowFunction` AST Expression.
#[inline]
#[must_use]
pub const fn new(
pub fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: FunctionBody,
) -> Self {
let contains_direct_eval = contains(&parameters, 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<Identifier> {
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<Identifier>) {
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<V::BreakTy>
@ -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)
}
}

74
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(&parameters, 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<V::BreakTy>
@ -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<AsyncFunctionDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncFunctionExpression {
pub(crate) name: Option<Identifier>,
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<Scope>,
#[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<Identifier>,
parameters: FormalParameterList,
body: FunctionBody,
has_binding_identifier: bool,
) -> Self {
let contains_direct_eval = contains(&parameters, 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<V::BreakTy>
@ -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)
}
}

73
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(&parameters, 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<V::BreakTy>
@ -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<AsyncGeneratorDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncGeneratorExpression {
pub(crate) name: Option<Identifier>,
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<Scope>,
#[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<Identifier>,
parameters: FormalParameterList,
body: FunctionBody,
has_binding_identifier: bool,
) -> Self {
let contains_direct_eval = contains(&parameters, 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<V::BreakTy>
@ -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)
}
}

262
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<Expression>,
pub(crate) super_ref: Option<Expression>,
pub(crate) constructor: Option<FunctionExpression>,
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<ClassDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)]
pub struct ClassExpression {
pub(crate) name: Option<Identifier>,
super_ref: Option<Expression>,
pub(crate) super_ref: Option<Expression>,
pub(crate) constructor: Option<FunctionExpression>,
pub(crate) elements: Box<[ClassElement]>,
has_binding_identifier: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) name_scope: Option<Scope>,
}
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<Expression>),
FieldDefinition(ClassFieldDefinition),
/// A static field definition, accessible from the class constructor object
StaticFieldDefinition(PropertyName, Option<Expression>),
StaticFieldDefinition(ClassFieldDefinition),
/// A private field definition, only accessible inside the class declaration.
PrivateFieldDefinition(PrivateName, Option<Expression>),
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<Expression>,
#[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<Expression>) -> 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<Expression>,
#[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<Expression>) -> 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(&parameters, 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")
}
}

76
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(&parameters, 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<V::BreakTy>
@ -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<GeneratorDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)]
pub struct GeneratorExpression {
pub(crate) name: Option<Identifier>,
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<Scope>,
#[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<Identifier>,
parameters: FormalParameterList,
body: FunctionBody,
has_binding_identifier: bool,
) -> Self {
let contains_direct_eval = contains(&parameters, 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<V::BreakTy>
@ -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)
}
}

90
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<S>(statements: S, strict: bool) -> Self
where
S: Into<Box<[StatementListItem]>>,
{
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<StatementList> 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<V::BreakTy>
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<V::BreakTy>
where
V: VisitorMut<'a>,
{
for statement in &mut *self.statements.statements {
try_break!(visitor.visit_statement_list_item_mut(statement));
}
ControlFlow::Continue(())
}
}

85
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(&parameters, 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<V::BreakTy>
@ -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<FunctionDeclaration> for Declaration {
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionExpression {
pub(crate) name: Option<Identifier>,
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<Scope>,
#[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<Identifier>,
parameters: FormalParameterList,
body: FunctionBody,
has_binding_identifier: bool,
) -> Self {
let contains_direct_eval = contains(&parameters, 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<V::BreakTy>
@ -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)
}
}

31
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::<Vec<_>>();
JsString::from(JsStr::latin1(&string))
}
}
impl ToJsString for Identifier {
fn to_js_string(&self, interner: &Interner) -> JsString {
self.sym().to_js_string(interner)
}
}

159
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<Self::BreakTy> {
try_break!(node.expression().visit_with(self));
node.statement().visit_with(self)
}
fn visit_call(&mut self, node: &'ast Call) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
top_level_lexicals(node.statement_list(), self.0);
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node {
// ModuleItem : ImportDeclaration
@ -655,72 +692,72 @@ impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T>
&mut self,
node: &'ast FunctionExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_function_declaration(
&mut self,
node: &'ast FunctionDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_function_expression(
&mut self,
node: &'ast AsyncFunctionExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_function_declaration(
&mut self,
node: &'ast AsyncFunctionDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_generator_expression(
&mut self,
node: &'ast GeneratorExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_generator_declaration(
&mut self,
node: &'ast GeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_generator_expression(
&mut self,
node: &'ast AsyncGeneratorExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_generator_declaration(
&mut self,
node: &'ast AsyncGeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
top_level_vars(node.statement_list(), self.0);
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement())
}
@ -933,61 +975,61 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
&mut self,
node: &'ast FunctionExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_function_declaration(
&mut self,
node: &'ast FunctionDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_function_expression(
&mut self,
node: &'ast AsyncFunctionExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_function_declaration(
&mut self,
node: &'ast AsyncFunctionDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_generator_expression(
&mut self,
node: &'ast GeneratorExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_generator_declaration(
&mut self,
node: &'ast GeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_generator_expression(
&mut self,
node: &'ast AsyncGeneratorExpression,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_async_generator_declaration(
&mut self,
node: &'ast AsyncGeneratorDeclaration,
) -> ControlFlow<Self::BreakTy> {
self.visit_script(node.body())
self.visit_function_body(node.body())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
// 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<Self::BreakTy> {
// 1. Return TopLevelVarScopedDeclarations of StatementList.
TopLevelVarScopedDeclarationsVisitor(self.0).visit_statement_list(node.statement_list())
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
fn visit_with(&mut self, node: &'ast With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement())
}
}

529
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<Inner>,
}
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<Self> {
Ok(Self::new_global())
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct Inner {
outer: Option<Scope>,
index: u32,
bindings: RefCell<FxHashMap<JsString, Binding>>,
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<BindingLocator> {
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<IdentifierReference> {
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) = &current.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<IdentifierReference, BindingLocatorError> {
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<IdentifierReference, BindingLocatorError> {
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> {
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<Scope>,
pub(crate) parameters_scope: Option<Scope>,
pub(crate) lexical_scope: Option<Scope>,
}
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<Self> {
Ok(Self {
function_scope: Scope::new_global(),
parameters_eval_scope: None,
parameters_scope: None,
lexical_scope: None,
})
}
}

1992
core/ast/src/scope_analyzer.rs

File diff suppressed because it is too large Load Diff

78
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<EvalDeclarationBindings, String> {
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 {

21
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<Scope>,
}
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<T> From<T> for Block
@ -44,8 +57,12 @@ where
T: Into<StatementList>,
{
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,
}
}
}

37
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<Statement>,
pub(crate) initializer: IterableLoopInitializer,
pub(crate) target: Expression,
pub(crate) body: Box<Statement>,
pub(crate) target_contains_direct_eval: bool,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) target_scope: Option<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
}
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 {

75
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<InnerForLoop>,
pub(crate) inner: Box<InnerForLoop>,
}
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<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
pub(crate) struct InnerForLoop {
pub(crate) init: Option<ForLoopInitializer>,
pub(crate) condition: Option<Expression>,
pub(crate) final_expr: Option<Expression>,
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<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
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<Expression> for ForLoopInitializer {
impl From<LexicalDeclaration> 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),
}
}
}

37
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<Statement>,
pub(crate) init: IterableLoopInitializer,
pub(crate) iterable: Expression,
pub(crate) body: Box<Statement>,
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<Scope>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
}
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 {

2
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,

28
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<Scope>,
}
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 {

30
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<Binding>,
block: Block,
pub(crate) parameter: Option<Binding>,
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<Binding>, block: Block) -> Self {
Self { parameter, block }
pub fn new(parameter: Option<Binding>, block: Block) -> Self {
let mut contains_direct_eval = contains(&block, ContainsSymbol::DirectEval);
if let Some(param) = &parameter {
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 {

15
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<Statement>,
pub(crate) expression: Expression,
pub(crate) statement: Box<Statement>,
#[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<With> for Statement {

2
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,
}

7
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),

2
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"]

70
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<JsValue> {
// 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<Scope>,
mut strict: bool,
context: &mut Context,
) -> JsResult<JsValue> {
@ -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!("<main>"),
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());

1
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<Option<u32>>,
environment: Gc<DeclarativeEnvironment>,
}

43
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(
&parameters,
&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())

7
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!("<main>"),
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(),

101
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);
}

656
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<Identifier>,
_script: &Script,
_env: &Rc<CompileTimeEnvironment>,
_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<Identifier>,
script: &Script,
env: &Rc<CompileTimeEnvironment>,
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<Identifier>,
body: &Script,
#[allow(unused)] strict: bool,
#[allow(unused)] var_env: &Rc<CompileTimeEnvironment>,
#[allow(unused)] lex_env: &Rc<CompileTimeEnvironment>,
#[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<CompileTimeEnvironment>,
) {
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<CompileTimeEnvironment>,
) where
pub(crate) fn block_declaration_instantiation<'a, N>(&mut self, block: &'a N)
where
&'a N: Into<NodeRef<'a>>,
{
// 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<CompileTimeEnvironment>,
lex_env: &Rc<CompileTimeEnvironment>,
#[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 &parameter_names {
let param_name = param_name.to_js_string(self.interner());
// a. Let alreadyDeclared be ! env.HasBinding(paramName).
let already_declared = env.has_binding(&param_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.

25
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;
}
}

31
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 {

2
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);

8
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),
}

31
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 {

40
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<JsString>,
name_scope: Option<Scope>,
}
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<JsString>) -> Self {
self.binding_identifier = binding_identifier;
/// Provide the name scope of the function.
pub(crate) fn name_scope(mut self, name_scope: Option<Scope>) -> Self {
self.name_scope = name_scope;
self
}
@ -98,8 +98,9 @@ impl FunctionCompiler {
mut self,
parameters: &FormalParameterList,
body: &FunctionBody,
variable_environment: Rc<CompileTimeEnvironment>,
lexical_environment: Rc<CompileTimeEnvironment>,
variable_environment: Scope,
lexical_environment: Scope,
scopes: &FunctionScopes,
interner: &mut Interner,
) -> Gc<CodeBlock> {
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: <https://tc39.es/ecma262/#sec-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)
}
}

346
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<Identifier>,
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<BindingLocator>,
/// The current variable environment.
pub(crate) variable_environment: Rc<CompileTimeEnvironment>,
pub(crate) local_binding_registers: FxHashMap<IdentifierReference, u32>,
/// The current variable scope.
pub(crate) variable_scope: Scope,
/// The current lexical environment.
pub(crate) lexical_environment: Rc<CompileTimeEnvironment>,
/// 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<Identifier>,
}
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<CompileTimeEnvironment>,
lexical_environment: Rc<CompileTimeEnvironment>,
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(),

2
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() {

22
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);
}
}
}

191
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<Sym>,
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);
}

22
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);
}
}
}

17
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);
}

8
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);
}
}

219
core/engine/src/environments/compile.rs

@ -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<Rc<Self>>,
environment_index: u32,
bindings: RefCell<FxHashMap<JsString, CompileTimeBinding>>,
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<Self>, 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<BindingLocator> {
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<BindingLocator, BindingLocatorError> {
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<BindingLocator, BindingLocatorError> {
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<Rc<Self>> {
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
}
}

10
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)]

19
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<FunctionSlots>,
// 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

35
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<CompileTimeEnvironment>,
}
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<CompileTimeEnvironment>,
) -> Self {
Self { kind, compile }
}
/// Gets the compile time environment of this environment.
pub(crate) fn compile_env(&self) -> Rc<CompileTimeEnvironment> {
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<Vec<Option<JsValue>>>,
#[unsafe_ignore_trace]
poisoned: Cell<bool>,
#[unsafe_ignore_trace]
with: Cell<bool>,
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.

18
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<Vec<BindingType>>,
// 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");

263
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<DeclarativeEnvironment> {
pub(crate) fn outer_function_environment(&self) -> Option<(Gc<DeclarativeEnvironment>, 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<CompileTimeEnvironment>) -> 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<CompileTimeEnvironment>,
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<CompileTimeEnvironment>) {
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<CompileTimeEnvironment> {
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<bool> {
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<Option<JsValue>> {
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<bool> {
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();

6
core/engine/src/module/mod.rs

@ -163,15 +163,17 @@ impl Module {
) -> JsResult<Self> {
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(),

10
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");

84
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!("<main>"),
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(),
);

37
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!("<main>"),
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::<Vec<_>>();
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(),
);

20
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<DeclarativeEnvironment>,
/// 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<FxHashMap<u64, JsObject>>,
@ -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()

14
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!("<main>"),
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());

11
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<IteratorRecord>,
// The stack of bindings being updated.
// SAFETY: Nothing in `BindingLocator` requires tracing, so this is safe.
#[unsafe_ignore_trace]
pub(crate) binding_stack: Vec<BindingLocator>,
// 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,

58
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<CodeBlock>),
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<CompileTimeEnvironment>),
/// 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<Handler>,
@ -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<CompileTimeEnvironment> {
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()
)?;
}

10
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"),
}
}

66
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<CompletionType> {
fn operation(
context: &mut Context,
argument_count: usize,
scope_index: usize,
) -> JsResult<CompletionType> {
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<CompletionType> {
let argument_count = context.vm.read::<u8>();
Self::operation(context, argument_count as usize)
let scope_index = context.vm.read::<u8>();
Self::operation(context, argument_count as usize, scope_index as usize)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u16>() as usize;
Self::operation(context, argument_count)
let scope_index = context.vm.read::<u16>();
Self::operation(context, argument_count, scope_index as usize)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u32>();
Self::operation(context, argument_count as usize)
let scope_index = context.vm.read::<u32>();
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<CompletionType> {
impl CallEvalSpread {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
// 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<CompletionType> {
let index = context.vm.read::<u8>();
Self::operation(context, index as usize)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>();
Self::operation(context, index as usize)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
Self::operation(context, index as usize)
}
}
/// `Call` implements the Opcode Operation for `Opcode::Call`
///
/// Operation:

4
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,
);

89
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<CompletionType> {
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<CompletionType> {
let dst = u32::from(context.vm.read::<u8>());
Self::operation(dst, context)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = u32::from(context.vm.read::<u16>());
Self::operation(dst, context)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = context.vm.read::<u32>();
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<CompletionType> {
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<CompletionType> {
let dst = u32::from(context.vm.read::<u8>());
Self::operation(dst, context)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = u32::from(context.vm.read::<u16>());
Self::operation(dst, context)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let dst = context.vm.read::<u32>();
Self::operation(dst, context)
}
}

31
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.

37
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<CompletionType> {
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<CompletionType> {
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<CompletionType> {
let compile_environments_index = context.vm.read::<u8>() as usize;
Self::operation(context, compile_environments_index)
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let compile_environments_index = context.vm.read::<u16>() as usize;
Self::operation(context, compile_environments_index)
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let compile_environments_index = context.vm.read::<u32>() as usize;
Self::operation(context, compile_environments_index)
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}

14
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()

2
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,
}
}
}

60
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<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
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)
}

36
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<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
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)
}

18
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,
)

22
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,
)

27
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,
)

12
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,
)

13
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<R> TokenParser<R> for GeneratorMethod
where
R: ReadChar,
{
type Output = (ClassElementNameNode, FormalParameterList, Script);
type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("GeneratorMethod", "Parsing");
@ -845,7 +848,7 @@ impl<R> TokenParser<R> for AsyncGeneratorMethod
where
R: ReadChar,
{
type Output = (ClassElementNameNode, FormalParameterList, Script);
type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("AsyncGeneratorMethod", "Parsing");
@ -955,7 +958,7 @@ impl<R> TokenParser<R> for AsyncMethod
where
R: ReadChar,
{
type Output = (ClassElementNameNode, FormalParameterList, Script);
type Output = (ClassElementNameNode, FormalParameterList, FunctionBodyAst);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("AsyncMethod", "Parsing");

6
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(),
],

8
core/parser/src/parser/function/mod.rs

@ -455,7 +455,7 @@ where
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
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())
}
}

102
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(),

31
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<boa_ast::Script> {
pub fn parse_script(
&mut self,
scope: &Scope,
interner: &mut Interner,
) -> ParseResult<boa_ast::Script> {
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<boa_ast::Module>
pub fn parse_module(
&mut self,
scope: &Scope,
interner: &mut Interner,
) -> ParseResult<boa_ast::Module>
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]

12
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(),

101
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",

19
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,
);

61
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

3
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 {

17
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<L>(js: &str, expr: L, interner: &mut Interner)
where
L: Into<Box<[StatementListItem]>>,
{
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()],

3
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();

3
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();

8
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<dyn Error>> {
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<dyn Error>> {
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();

Loading…
Cancel
Save