use bitflags::bitflags;
use boa_string::JsString;
use std::{
cell::{Cell, RefCell},
fmt::Debug,
rc::Rc,
};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct BindingFlags: u8 {
const MUTABLE = 1 << 0;
const LEX = 1 << 1;
const STRICT = 1 << 2;
const ESCAPES = 1 << 3;
const ACCESSED = 1 << 4;
}
}
impl BindingFlags {
fn is_mutable(self) -> bool {
self.contains(BindingFlags::MUTABLE)
}
fn is_lex(self) -> bool {
self.contains(BindingFlags::LEX)
}
fn is_strict(self) -> bool {
self.contains(BindingFlags::STRICT)
}
fn escapes(self) -> bool {
self.contains(BindingFlags::ESCAPES)
}
fn is_accessed(self) -> bool {
self.contains(BindingFlags::ACCESSED)
}
}
#[derive(Clone, Debug, PartialEq)]
struct Binding {
name: JsString,
index: u32,
flags: BindingFlags,
}
impl Binding {
fn is_mutable(&self) -> bool {
self.flags.is_mutable()
}
fn is_lex(&self) -> bool {
self.flags.is_lex()
}
fn is_strict(&self) -> bool {
self.flags.is_strict()
}
fn escapes(&self) -> bool {
self.flags.escapes()
}
fn is_accessed(&self) -> bool {
self.flags.is_accessed()
}
}
#[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 {
unique_id: u32,
outer: Option<Scope>,
index: Cell<u32>,
bindings: RefCell<Vec<Binding>>,
function: bool,
this_escaped: Cell<bool>,
}
impl Scope {
#[must_use]
pub fn new_global() -> Self {
Self {
inner: Rc::new(Inner {
unique_id: 0,
outer: None,
index: Cell::default(),
bindings: RefCell::default(),
function: true,
this_escaped: Cell::new(false),
}),
}
}
#[must_use]
pub fn new(parent: Self, function: bool) -> Self {
let index = parent.inner.index.get() + 1;
Self {
inner: Rc::new(Inner {
unique_id: index,
outer: Some(parent),
index: Cell::new(index),
bindings: RefCell::default(),
function,
this_escaped: Cell::new(false),
}),
}
}
#[must_use]
pub fn all_bindings_local(&self) -> bool {
self.inner
.bindings
.borrow()
.iter()
.all(|binding| !binding.escapes())
}
pub fn escape_all_bindings(&self) {
for binding in self.inner.bindings.borrow_mut().iter_mut() {
binding.flags.insert(BindingFlags::ESCAPES);
}
}
#[must_use]
pub fn escaped_this(&self) -> bool {
self.inner.this_escaped.get()
}
#[must_use]
pub fn has_lex_binding(&self, name: &JsString) -> bool {
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.is_some_and(Binding::is_lex)
}
#[must_use]
pub fn has_binding(&self, name: &JsString) -> bool {
self.inner.bindings.borrow().iter().any(|b| &b.name == name)
}
#[must_use]
pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference {
if let Some(binding) = self.inner.bindings.borrow().iter().find(|b| b.name == name) {
IdentifierReference::new(
BindingLocator::declarative(
name,
self.inner.index.get(),
binding.index,
self.inner.unique_id,
),
binding.is_lex(),
binding.escapes(),
)
} else if let Some(outer) = &self.inner.outer {
outer.get_identifier_reference(name)
} else {
IdentifierReference::new(BindingLocator::global(name), false, true)
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn num_bindings(&self) -> u32 {
self.inner.bindings.borrow().len() as u32
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn num_bindings_non_local(&self) -> u32 {
self.inner
.bindings
.borrow()
.iter()
.filter(|binding| binding.escapes())
.count() as u32
}
pub(crate) fn reorder_binding_indices(&self) {
let mut bindings = self.inner.bindings.borrow_mut();
let mut index = 0;
for binding in bindings.iter_mut() {
if !binding.escapes() {
binding.index = 0;
continue;
}
binding.index = index;
index += 1;
}
}
#[must_use]
pub fn scope_index(&self) -> u32 {
self.inner.index.get()
}
pub(crate) fn set_index(&self, index: u32) {
self.inner.index.set(index);
}
#[must_use]
pub fn is_function(&self) -> bool {
self.inner.function
}
#[must_use]
pub fn is_global(&self) -> bool {
self.inner.outer.is_none()
}
#[must_use]
pub fn get_binding(&self, name: &JsString) -> Option<BindingLocator> {
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.map(|binding| {
BindingLocator::declarative(
name.clone(),
self.inner.index.get(),
binding.index,
self.inner.unique_id,
)
})
}
#[must_use]
pub fn get_binding_reference(&self, name: &JsString) -> Option<IdentifierReference> {
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.map(|binding| {
IdentifierReference::new(
BindingLocator::declarative(
name.clone(),
self.inner.index.get(),
binding.index,
self.inner.unique_id,
),
binding.is_lex(),
binding.escapes(),
)
})
}
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()
.iter_mut()
.find(|b| &b.name == name)
{
binding.flags.insert(BindingFlags::ACCESSED);
if crossed_function_border || eval_or_with {
binding.flags.insert(BindingFlags::ESCAPES);
}
return;
}
if let Some(outer) = ¤t.inner.outer {
if current.inner.function {
crossed_function_border = true;
}
current = outer;
} else {
return;
}
}
}
pub fn escape_this_in_enclosing_function_scope(&self) {
let mut current = self;
let mut crossed_function_border = false;
loop {
if crossed_function_border && current.is_function() {
current.inner.this_escaped.set(true);
return;
}
if let Some(outer) = ¤t.inner.outer {
if current.is_function() {
crossed_function_border = true;
}
current = outer;
} else {
return;
}
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator {
let mut bindings = self.inner.bindings.borrow_mut();
let binding_index = bindings.len() as u32;
if let Some(binding) = bindings.iter().find(|b| b.name == name) {
return BindingLocator::declarative(
name,
self.inner.index.get(),
binding.index,
self.inner.unique_id,
);
}
let mut flags = BindingFlags::MUTABLE;
flags.set(BindingFlags::LEX, !function_scope);
flags.set(BindingFlags::ESCAPES, self.is_global());
bindings.push(Binding {
name: name.clone(),
index: binding_index,
flags,
});
BindingLocator::declarative(
name,
self.inner.index.get(),
binding_index,
self.inner.unique_id,
)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) {
let mut bindings = self.inner.bindings.borrow_mut();
if bindings.iter().any(|b| b.name == name) {
return;
}
let binding_index = bindings.len() as u32;
let mut flags = BindingFlags::LEX;
flags.set(BindingFlags::STRICT, strict);
flags.set(BindingFlags::ESCAPES, self.is_global());
bindings.push(Binding {
name,
index: binding_index,
flags,
});
}
pub fn set_mutable_binding(
&self,
name: JsString,
) -> Result<IdentifierReference, BindingLocatorError> {
Ok(
match self.inner.bindings.borrow().iter().find(|b| b.name == name) {
Some(binding) if binding.is_mutable() => IdentifierReference::new(
BindingLocator::declarative(
name,
self.inner.index.get(),
binding.index,
self.inner.unique_id,
),
binding.is_lex(),
binding.escapes(),
),
Some(binding) if binding.is_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")]
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().iter().find(|b| b.name == name) {
Some(binding) if binding.is_mutable() => IdentifierReference::new(
BindingLocator::declarative(
name,
self.inner.index.get(),
binding.index,
self.inner.unique_id,
),
binding.is_lex(),
binding.escapes(),
),
Some(binding) if binding.is_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()),
)?,
},
)
}
#[must_use]
pub fn outer(&self) -> Option<Self> {
self.inner.outer.clone()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IdentifierReference {
locator: BindingLocator,
lexical: bool,
escapes: bool,
}
impl IdentifierReference {
pub(crate) fn new(locator: BindingLocator, lexical: bool, escapes: bool) -> Self {
Self {
locator,
lexical,
escapes,
}
}
#[must_use]
pub fn locator(&self) -> BindingLocator {
self.locator.clone()
}
#[must_use]
pub fn local(&self) -> bool {
self.locator.scope > 0 && !self.escapes
}
#[must_use]
pub fn is_global_object(&self) -> bool {
self.locator.scope == 0
}
#[must_use]
pub fn is_lexical(&self) -> bool {
self.lexical
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct BindingLocator {
name: JsString,
scope: u32,
binding_index: u32,
unique_scope_id: u32,
}
impl BindingLocator {
pub(crate) const fn declarative(
name: JsString,
scope_index: u32,
binding_index: u32,
unique_scope_id: u32,
) -> Self {
Self {
name,
scope: scope_index + 1,
binding_index,
unique_scope_id,
}
}
pub(super) const fn global(name: JsString) -> Self {
Self {
name,
scope: 0,
binding_index: 0,
unique_scope_id: 0,
}
}
#[must_use]
pub const fn name(&self) -> &JsString {
&self.name
}
#[must_use]
pub const fn is_global(&self) -> bool {
self.scope == 0
}
#[must_use]
pub fn scope(&self) -> BindingLocatorScope {
match self.scope {
0 => BindingLocatorScope::GlobalObject,
1 => BindingLocatorScope::GlobalDeclarative,
n => BindingLocatorScope::Stack(n - 2),
}
}
pub fn set_scope(&mut self, scope: BindingLocatorScope) {
self.scope = match scope {
BindingLocatorScope::GlobalObject => 0,
BindingLocatorScope::GlobalDeclarative => 1,
BindingLocatorScope::Stack(index) => index + 2,
};
}
#[must_use]
pub const fn binding_index(&self) -> u32 {
self.binding_index
}
pub fn set_binding_index(&mut self, index: u32) {
self.binding_index = index;
}
}
#[derive(Copy, Clone, Debug)]
pub enum BindingLocatorError {
MutateImmutable,
Silent,
}
#[derive(Clone, Copy, Debug)]
pub enum BindingLocatorScope {
GlobalObject,
GlobalDeclarative,
Stack(u32),
}
#[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>,
pub(crate) mapped_arguments_object: bool,
pub(crate) requires_function_scope: bool,
}
impl FunctionScopes {
#[must_use]
pub fn function_scope(&self) -> &Scope {
&self.function_scope
}
#[must_use]
pub fn arguments_object_accessed(&self) -> bool {
if self
.function_scope
.inner
.bindings
.borrow()
.first()
.filter(|b| b.name == "arguments" && b.is_accessed())
.is_some()
{
return true;
}
if let Some(scope) = &self.parameters_eval_scope
&& scope
.inner
.bindings
.borrow()
.first()
.filter(|b| b.name == "arguments" && b.is_accessed())
.is_some()
{
return true;
}
false
}
#[must_use]
pub fn requires_function_scope(&self) -> bool {
self.requires_function_scope
}
#[must_use]
pub fn parameters_eval_scope(&self) -> Option<&Scope> {
self.parameters_eval_scope.as_ref()
}
#[must_use]
pub fn parameters_scope(&self) -> Option<&Scope> {
self.parameters_scope.as_ref()
}
#[must_use]
pub fn lexical_scope(&self) -> Option<&Scope> {
self.lexical_scope.as_ref()
}
#[must_use]
pub fn parameter_scope(&self) -> Scope {
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
return parameters_eval_scope.clone();
}
self.function_scope.clone()
}
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()
}
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();
}
}
pub(crate) fn reorder_binding_indices(&self) {
self.function_scope.reorder_binding_indices();
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
parameters_eval_scope.reorder_binding_indices();
}
if let Some(parameters_scope) = &self.parameters_scope {
parameters_scope.reorder_binding_indices();
}
if let Some(lexical_scope) = &self.lexical_scope {
lexical_scope.reorder_binding_indices();
}
}
}
#[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,
mapped_arguments_object: false,
requires_function_scope: false,
})
}
}