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