mirror of https://github.com/boa-dev/boa.git
Browse Source
* 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 fuzzerpull/4003/head
raskad
2 months ago
committed by
GitHub
89 changed files with 5004 additions and 2053 deletions
@ -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) = ¤t.inner.outer { |
||||||
|
if current.inner.function { |
||||||
|
crossed_function_border = true; |
||||||
|
} |
||||||
|
current = outer; |
||||||
|
} else { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Creates a mutable binding.
|
||||||
|
#[must_use] |
||||||
|
#[allow(clippy::cast_possible_truncation)] |
||||||
|
pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator { |
||||||
|
let binding_index = self.inner.bindings.borrow().len() as u32; |
||||||
|
self.inner.bindings.borrow_mut().insert( |
||||||
|
name.clone(), |
||||||
|
Binding { |
||||||
|
index: binding_index, |
||||||
|
mutable: true, |
||||||
|
lex: !function_scope, |
||||||
|
strict: false, |
||||||
|
escapes: self.is_global(), |
||||||
|
}, |
||||||
|
); |
||||||
|
BindingLocator::declarative(name, self.inner.index, binding_index) |
||||||
|
} |
||||||
|
|
||||||
|
/// Crate an immutable binding.
|
||||||
|
#[allow(clippy::cast_possible_truncation)] |
||||||
|
pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) { |
||||||
|
let binding_index = self.inner.bindings.borrow().len() as u32; |
||||||
|
self.inner.bindings.borrow_mut().insert( |
||||||
|
name, |
||||||
|
Binding { |
||||||
|
index: binding_index, |
||||||
|
mutable: false, |
||||||
|
lex: true, |
||||||
|
strict, |
||||||
|
escapes: self.is_global(), |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// Return the binding locator for a mutable binding.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the binding is not mutable or does not exist.
|
||||||
|
pub fn set_mutable_binding( |
||||||
|
&self, |
||||||
|
name: JsString, |
||||||
|
) -> Result<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, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,28 @@ |
|||||||
|
use boa_ast::scope::Scope; |
||||||
|
|
||||||
use super::ByteCompiler; |
use super::ByteCompiler; |
||||||
use crate::environments::CompileTimeEnvironment; |
|
||||||
use std::rc::Rc; |
|
||||||
|
|
||||||
impl ByteCompiler<'_> { |
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] |
#[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; |
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; |
let index = self.constants.len() as u32; |
||||||
self.constants |
self.constants |
||||||
.push(crate::vm::Constant::CompileTimeEnvironment(env.clone())); |
.push(crate::vm::Constant::Scope(scope.clone())); |
||||||
|
|
||||||
if function_scope { |
if scope.is_function() { |
||||||
self.variable_environment = env.clone(); |
self.variable_scope = scope.clone(); |
||||||
} |
} |
||||||
|
|
||||||
self.lexical_environment = env; |
self.lexical_scope = scope.clone(); |
||||||
|
|
||||||
index |
index |
||||||
} |
} |
||||||
|
|
||||||
/// Pops the top compile time environment and returns its index in the compile time environments array.
|
/// Pops the top scope.
|
||||||
pub(crate) fn pop_compile_environment(&mut self) { |
pub(crate) fn pop_scope(&mut self) { |
||||||
self.current_open_environments_count -= 1; |
self.current_open_environments_count -= 1; |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -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 |
|
||||||
} |
|
||||||
} |
|
@ -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) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue