use crate::{
Context, JsResult, JsString, JsSymbol, JsValue,
object::{JsObject, PrivateName},
};
use boa_ast::scope::{BindingLocator, BindingLocatorScope, Scope};
use boa_gc::{Finalize, Gc, Trace};
use thin_vec::ThinVec;
mod declarative;
mod private;
use self::declarative::ModuleEnvironment;
pub(crate) use self::{
declarative::{
DeclarativeEnvironment, DeclarativeEnvironmentKind, FunctionEnvironment, FunctionSlots,
LexicalEnvironment, ThisBindingStatus,
},
private::PrivateEnvironment,
};
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct EnvironmentNode {
env: Environment,
parent: Option<Gc<EnvironmentNode>>,
}
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct EnvironmentStack {
tip: Option<Gc<EnvironmentNode>>,
#[unsafe_ignore_trace]
depth: u32,
private_stack: ThinVec<Gc<PrivateEnvironment>>,
}
pub(crate) struct SavedEnvironments {
tip: Option<Gc<EnvironmentNode>>,
depth: u32,
}
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum Environment {
Declarative(Gc<DeclarativeEnvironment>),
Object(JsObject),
}
impl Environment {
pub(crate) const fn as_declarative(&self) -> Option<&Gc<DeclarativeEnvironment>> {
match self {
Self::Declarative(env) => Some(env),
Self::Object(_) => None,
}
}
}
impl EnvironmentStack {
pub(crate) fn new() -> Self {
Self {
tip: None,
depth: 0,
private_stack: ThinVec::new(),
}
}
pub(crate) fn outer_function_environment(&self) -> Option<(Gc<DeclarativeEnvironment>, Scope)> {
for (env, _) in self.iter_from_tip() {
if let Some(decl) = env.as_declarative()
&& let Some(function_env) = decl.kind().as_function()
{
return Some((decl.clone(), function_env.compile().clone()));
}
}
None
}
pub(crate) fn pop_to_global(&mut self) -> SavedEnvironments {
SavedEnvironments {
tip: self.tip.take(),
depth: std::mem::replace(&mut self.depth, 0),
}
}
pub(crate) fn restore_from_saved(&mut self, saved: SavedEnvironments) {
self.tip = saved.tip;
self.depth = saved.depth;
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.depth as usize
}
#[inline]
pub(crate) fn get(&self, index: usize) -> Option<&Environment> {
let depth = self.depth as usize;
if index >= depth {
return None;
}
let steps = depth - 1 - index;
let mut current = self.tip.as_deref()?;
for _ in 0..steps {
current = current.parent.as_deref()?;
}
Some(¤t.env)
}
fn iter_from_tip(&self) -> EnvironmentChainIter<'_> {
EnvironmentChainIter {
current: self.tip.as_deref(),
index: self.depth,
}
}
#[inline]
fn last(&self) -> Option<&Environment> {
self.tip.as_deref().map(|node| &node.env)
}
pub(crate) fn truncate(&mut self, len: usize) {
while self.depth as usize > len {
let node = self.tip.as_ref().expect("depth > 0 implies tip is Some");
self.tip = node.parent.clone();
self.depth -= 1;
}
}
pub(crate) fn get_this_environment<'a>(
&'a self,
global: &'a Gc<DeclarativeEnvironment>,
) -> &'a DeclarativeEnvironmentKind {
for (env, _) in self.iter_from_tip() {
if let Some(decl) = env.as_declarative().filter(|decl| decl.has_this_binding()) {
return decl.kind();
}
}
global.kind()
}
pub(crate) fn get_this_binding(&self) -> JsResult<Option<JsValue>> {
for (env, _) in self.iter_from_tip() {
if let Environment::Declarative(decl) = env
&& let Some(this) = decl.get_this_binding()?
{
return Ok(Some(this));
}
}
Ok(None)
}
pub(crate) fn push_object(&mut self, object: JsObject) {
self.push_env(Environment::Object(object));
}
pub(crate) fn push_lexical(
&mut self,
bindings_count: u32,
global: &Gc<DeclarativeEnvironment>,
) -> u32 {
let (poisoned, with) = self.compute_poisoned_with(global);
let index = self.depth;
self.push_env(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new(
DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new(bindings_count)),
poisoned,
with,
),
)));
index
}
pub(crate) fn push_function(
&mut self,
scope: Scope,
function_slots: FunctionSlots,
global: &Gc<DeclarativeEnvironment>,
) {
let num_bindings = scope.num_bindings_non_local();
let (poisoned, with) = self.compute_poisoned_with(global);
self.push_env(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new(
DeclarativeEnvironmentKind::Function(FunctionEnvironment::new(
num_bindings,
function_slots,
scope,
)),
poisoned,
with,
),
)));
}
pub(crate) fn push_module(&mut self, scope: Scope) {
let num_bindings = scope.num_bindings_non_local();
self.push_env(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new(
DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)),
false,
false,
),
)));
}
#[track_caller]
pub(crate) fn pop(&mut self) {
let node = self
.tip
.as_ref()
.expect("cannot pop empty environment chain");
self.tip = node.parent.clone();
self.depth -= 1;
}
pub(crate) fn current_declarative_ref<'a>(
&'a self,
global: &'a Gc<DeclarativeEnvironment>,
) -> Option<&'a Gc<DeclarativeEnvironment>> {
if let Some(env) = self.last() {
env.as_declarative()
} else {
Some(global)
}
}
pub(crate) fn poison_until_last_function(&mut self, global: &Gc<DeclarativeEnvironment>) {
for (env, _) in self.iter_from_tip() {
if let Some(decl) = env.as_declarative() {
decl.poison();
if decl.is_function() {
return;
}
}
}
global.poison();
}
#[track_caller]
pub(crate) fn put_lexical_value(
&mut self,
environment: BindingLocatorScope,
binding_index: u32,
value: JsValue,
global: &Gc<DeclarativeEnvironment>,
) {
let env = match environment {
BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => global,
BindingLocatorScope::Stack(index) => self
.get(index as usize)
.and_then(Environment::as_declarative)
.expect("must be declarative environment"),
};
env.set(binding_index, value);
}
#[track_caller]
pub(crate) fn put_value_if_uninitialized(
&mut self,
environment: BindingLocatorScope,
binding_index: u32,
value: JsValue,
global: &Gc<DeclarativeEnvironment>,
) {
let env = match environment {
BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => global,
BindingLocatorScope::Stack(index) => self
.get(index as usize)
.and_then(Environment::as_declarative)
.expect("must be declarative environment"),
};
if env.get(binding_index).is_none() {
env.set(binding_index, value);
}
}
pub(crate) fn push_private(&mut self, environment: Gc<PrivateEnvironment>) {
self.private_stack.push(environment);
}
pub(crate) fn pop_private(&mut self) {
self.private_stack.pop();
}
pub(crate) fn resolve_private_identifier(&self, identifier: JsString) -> Option<PrivateName> {
for environment in self.private_stack.iter().rev() {
if environment.descriptions().contains(&identifier) {
return Some(PrivateName::new(identifier, environment.id()));
}
}
None
}
pub(crate) fn private_name_descriptions(&self) -> Vec<&JsString> {
let mut names = Vec::new();
for environment in self.private_stack.iter().rev() {
for name in environment.descriptions() {
if !names.contains(&name) {
names.push(name);
}
}
}
names
}
pub(crate) fn has_object_environment(&self) -> bool {
self.iter_from_tip()
.any(|(env, _)| matches!(env, Environment::Object(_)))
}
pub(crate) fn snapshot_for_closure(&self) -> EnvironmentStack {
self.clone()
}
fn push_env(&mut self, env: Environment) {
self.tip = Some(Gc::new(EnvironmentNode {
env,
parent: self.tip.take(),
}));
self.depth += 1;
}
fn compute_poisoned_with(&self, global: &Gc<DeclarativeEnvironment>) -> (bool, bool) {
let with = if let Some(env) = self.last() {
env.as_declarative().is_none()
} else {
false
};
let environment = self
.iter_from_tip()
.find_map(|(env, _)| env.as_declarative())
.unwrap_or(global);
(environment.poisoned(), with || environment.with())
}
}
struct EnvironmentChainIter<'a> {
current: Option<&'a EnvironmentNode>,
index: u32,
}
impl<'a> Iterator for EnvironmentChainIter<'a> {
type Item = (&'a Environment, u32);
fn next(&mut self) -> Option<Self::Item> {
let node = self.current?;
self.index = self
.index
.checked_sub(1)
.expect("iterator advanced past root");
self.current = node.parent.as_deref();
Some((&node.env, self.index))
}
}
impl Context {
pub(crate) fn find_runtime_binding(&mut self, locator: &mut BindingLocator) -> JsResult<()> {
let deleted_binding = self.is_deleted_binding(locator);
let global = self.vm.frame().realm.environment();
if let Some(env) = self.vm.frame().environments.current_declarative_ref(global)
&& !env.with()
&& !env.poisoned()
&& !deleted_binding
{
return Ok(());
}
let (global, min_index) = match locator.scope() {
BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => (true, 0),
BindingLocatorScope::Stack(_) if deleted_binding => (false, 0),
BindingLocatorScope::Stack(index) => (false, index),
};
let max_index = self.vm.frame().environments.len() as u32;
for index in (min_index..max_index).rev() {
match self.environment_expect(index) {
Environment::Declarative(env) => {
if env.poisoned() || deleted_binding {
if let Some(env) = env.kind().as_function()
&& let Some(b) = env.compile().get_binding(locator.name())
{
if env.is_deleted_binding(b.binding_index()) {
continue;
}
locator.set_scope(b.scope());
locator.set_binding_index(b.binding_index());
return Ok(());
}
} else if !env.with() {
return Ok(());
}
}
Environment::Object(o) => {
let o = o.clone();
let key = locator.name().clone();
if o.has_property(key.clone(), self)? {
if let Some(unscopables) = o.get(JsSymbol::unscopables(), self)?.as_object()
&& unscopables.get(key.clone(), self)?.to_boolean()
{
continue;
}
locator.set_scope(BindingLocatorScope::Stack(index));
return Ok(());
}
}
}
}
if deleted_binding
&& let BindingLocatorScope::Stack(index) = locator.scope()
&& let Environment::Declarative(env) = self.environment_expect(index)
&& let Some(function_env) = env.kind().as_function()
{
let mut scope = function_env.compile().outer();
while let Some(current_scope) = scope {
if let Some(binding) = current_scope.get_binding(locator.name()) {
locator.set_scope(binding.scope());
locator.set_binding_index(binding.binding_index());
return Ok(());
}
scope = current_scope.outer();
}
locator.set_scope(BindingLocatorScope::GlobalObject);
locator.set_binding_index(0);
return Ok(());
}
if global
&& self.realm().environment().poisoned()
&& let Some(b) = self.realm().scope().get_binding(locator.name())
{
locator.set_scope(b.scope());
locator.set_binding_index(b.binding_index());
}
Ok(())
}
pub(crate) fn this_from_object_environment_binding(
&mut self,
locator: &BindingLocator,
) -> JsResult<Option<JsObject>> {
let global = self.vm.frame().realm.environment();
if let Some(env) = self.vm.frame().environments.current_declarative_ref(global)
&& !env.with()
{
return Ok(None);
}
let min_index = match locator.scope() {
BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => 0,
BindingLocatorScope::Stack(index) => index,
};
let max_index = self.vm.frame().environments.len() as u32;
for index in (min_index..max_index).rev() {
match self.environment_expect(index) {
Environment::Declarative(env) => {
if env.poisoned() {
if let Some(env) = env.kind().as_function()
&& env.compile().get_binding(locator.name()).is_some()
{
break;
}
} else if !env.with() {
break;
}
}
Environment::Object(o) => {
let o = o.clone();
let key = locator.name().clone();
if o.has_property(key.clone(), self)? {
if let Some(unscopables) = o.get(JsSymbol::unscopables(), self)?.as_object()
&& unscopables.get(key.clone(), self)?.to_boolean()
{
continue;
}
return Ok(Some(o));
}
}
}
}
Ok(None)
}
pub(crate) fn is_initialized_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> {
match locator.scope() {
BindingLocatorScope::GlobalObject => {
let key = locator.name().clone();
let obj = self.global_object();
obj.has_property(key, self)
}
BindingLocatorScope::GlobalDeclarative => {
let env = self.vm.frame().realm.environment();
Ok(env.get(locator.binding_index()).is_some())
}
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();
let obj = obj.clone();
obj.has_property(key, self)
}
},
}
}
pub(crate) fn is_deleted_binding(&self, locator: &BindingLocator) -> bool {
match locator.scope() {
BindingLocatorScope::Stack(index) => matches!(
self.environment_expect(index),
Environment::Declarative(env) if env.is_deleted_binding(locator.binding_index())
),
BindingLocatorScope::GlobalObject | BindingLocatorScope::GlobalDeclarative => false,
}
}
pub(crate) fn restore_deleted_binding(&self, locator: &BindingLocator) {
if let BindingLocatorScope::Stack(index) = locator.scope()
&& let Environment::Declarative(env) = self.environment_expect(index)
{
env.restore_deleted_binding(locator.binding_index());
}
}
#[track_caller]
pub(crate) fn get_binding(&mut self, locator: &BindingLocator) -> JsResult<Option<JsValue>> {
match locator.scope() {
BindingLocatorScope::GlobalObject => {
let key = locator.name().clone();
let obj = self.global_object();
obj.try_get(key, self)
}
BindingLocatorScope::GlobalDeclarative => {
let env = self.vm.frame().realm.environment();
Ok(env.get(locator.binding_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();
let obj = obj.clone();
obj.get(key, self).map(Some)
}
},
}
}
#[track_caller]
pub(crate) fn set_binding(
&mut self,
locator: &BindingLocator,
value: JsValue,
strict: bool,
) -> JsResult<()> {
match locator.scope() {
BindingLocatorScope::GlobalObject => {
let key = locator.name().clone();
let obj = self.global_object();
obj.set(key, value, strict, self)?;
}
BindingLocatorScope::GlobalDeclarative => {
let env = self.vm.frame().realm.environment();
env.set(locator.binding_index(), value);
}
BindingLocatorScope::Stack(index) => match self.environment_expect(index) {
Environment::Declarative(decl) => {
decl.set(locator.binding_index(), value);
}
Environment::Object(obj) => {
let key = locator.name().clone();
let obj = obj.clone();
obj.set(key, value, strict, self)?;
}
},
}
Ok(())
}
pub(crate) fn delete_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> {
match locator.scope() {
BindingLocatorScope::GlobalObject => {
let key = locator.name().clone();
let obj = self.global_object();
obj.__delete__(&key.into(), &mut self.into())
}
BindingLocatorScope::GlobalDeclarative => Ok(false),
BindingLocatorScope::Stack(index) => match self.environment_expect(index) {
Environment::Declarative(env) => Ok(env.delete_binding(locator.binding_index())),
Environment::Object(obj) => {
let key = locator.name().clone();
let obj = obj.clone();
obj.__delete__(&key.into(), &mut self.into())
}
},
}
}
pub(crate) fn environment_expect(&self, index: u32) -> &Environment {
self.vm
.frame()
.environments
.get(index as usize)
.expect("environment index must be in range")
}
}