use ahash::AHashMap;
#[cfg(test)]
use crate::ecmascript::GlobalEnvironment;
#[cfg(feature = "shared-array-buffer")]
use crate::ecmascript::SharedArrayBuffer;
#[cfg(feature = "atomics")]
use crate::ecmascript::WaitAsyncJob;
#[cfg(feature = "weak-refs")]
use crate::ecmascript::{FinalizationRegistryCleanupJob, clear_kept_objects};
use crate::{
ecmascript::{
AbstractModuleMethods, Environment, ErrorHeapData, ExecutionContext, Function,
GraphLoadingStateRecord, HostDefined, ModuleRequest, Object, OrdinaryObject,
PrivateEnvironment, PrivateName, Promise, PromiseReactionJob, PromiseResolveThenableJob,
PropertyKey, PropertyLookupCache, Realm, RealmRecord, Reference, Referrer, ScriptOrModule,
SourceCode, SourceTextModule, String, Symbol, Value, ValueRootRepr,
get_identifier_reference, initialize_default_realm, initialize_host_defined_realm,
parse_script, script_evaluation, to_string, try_get_identifier_reference,
},
engine::{
Bindable, GcScope, Global, HeapRootCollection, HeapRootData, HeapRootRef, NoGcScope,
Rootable, Vm, bindable_handle,
},
heap::{
ArenaAccess, CompactionLists, CreateHeapData, Heap, HeapIndexHandle, HeapMarkAndSweep,
PrimitiveHeapAccess, WorkQueues, heap_gc,
},
ndt,
};
use core::{any::Any, cell::RefCell, ops::ControlFlow, ptr::NonNull};
use std::collections::TryReserveError;
#[derive(Debug, Default)]
pub struct AgentOptions {
pub disable_gc: bool,
pub print_internals: bool,
pub no_block: bool,
}
pub type JsResult<'a, T> = core::result::Result<T, JsError<'a>>;
impl<'a, T: 'a> From<JsError<'a>> for JsResult<'a, T> {
fn from(value: JsError<'a>) -> Self {
JsResult::Err(value)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(transparent)]
pub struct JsError<'a>(Value<'a>);
bindable_handle!(JsError);
impl<'a> JsError<'a> {
pub(crate) fn new(value: Value<'a>) -> Self {
Self(value)
}
pub fn value(self) -> Value<'a> {
self.0
}
pub fn to_string<'gc>(self, agent: &mut Agent, gc: GcScope<'gc, '_>) -> String<'gc> {
to_string(agent, self.0, gc).unwrap()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(transparent)]
pub(crate) struct JsErrorRootRepr(ValueRootRepr);
impl Rootable for JsError<'_> {
type RootRepr = JsErrorRootRepr;
fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
Value::to_root_repr(value.value()).map(JsErrorRootRepr)
}
fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
Value::from_root_repr(&value.0).map(JsError)
}
fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
JsErrorRootRepr(Value::from_heap_ref(heap_ref))
}
fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
Value::from_heap_data(heap_data).map(JsError)
}
}
impl HeapMarkAndSweep for JsError<'static> {
fn mark_values(&self, queues: &mut crate::heap::WorkQueues) {
self.0.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) {
self.0.sweep_values(compactions);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TryError<'a> {
Err(JsError<'a>),
GcError,
}
bindable_handle!(TryError);
pub(crate) fn option_into_try<'a, T: 'a>(value: Option<T>) -> TryResult<'a, T> {
match value {
Some(value) => TryResult::Continue(value),
None => TryError::GcError.into(),
}
}
pub fn js_result_into_try<'a, T: 'a>(value: JsResult<'a, T>) -> TryResult<'a, T> {
match value {
Ok(value) => TryResult::Continue(value),
Err(err) => TryResult::Break(TryError::Err(err)),
}
}
pub fn try_result_into_js<'a, T: 'a>(value: TryResult<'a, T>) -> JsResult<'a, Option<T>> {
match value {
TryResult::Continue(value) => JsResult::Ok(Some(value)),
TryResult::Break(TryError::GcError) => JsResult::Ok(None),
TryResult::Break(TryError::Err(err)) => JsResult::Err(err),
}
}
pub fn try_result_into_option_js<'a, T: 'a>(value: TryResult<'a, T>) -> Option<JsResult<'a, T>> {
match value {
TryResult::Continue(value) => Some(JsResult::Ok(value)),
TryResult::Break(TryError::GcError) => None,
TryResult::Break(TryError::Err(err)) => Some(JsResult::Err(err)),
}
}
impl<'a, T: 'a> From<JsError<'a>> for TryResult<'a, T> {
fn from(value: JsError<'a>) -> Self {
TryResult::Break(TryError::Err(value))
}
}
impl<'a, T: 'a> From<TryError<'a>> for TryResult<'a, T> {
fn from(value: TryError<'a>) -> Self {
TryResult::Break(value)
}
}
macro_rules! try_result_ok {
($self:ident) => {
impl<'a> core::convert::From<$self<'a>> for TryResult<'a, $self<'a>> {
fn from(value: $self<'a>) -> Self {
TryResult::Continue(value)
}
}
};
}
pub(crate) use try_result_ok;
pub type TryResult<'a, T> = ControlFlow<TryError<'a>, T>;
#[inline]
pub fn unwrap_try<'a, T: 'a>(try_result: TryResult<'a, T>) -> T {
match try_result {
TryResult::Continue(t) => t,
TryResult::Break(_) => unreachable!(),
}
}
pub(crate) enum InnerJob {
PromiseResolveThenable(PromiseResolveThenableJob),
PromiseReaction(PromiseReactionJob),
#[cfg(feature = "atomics")]
WaitAsync(WaitAsyncJob),
#[cfg(feature = "weak-refs")]
FinalizationRegistry(FinalizationRegistryCleanupJob),
}
pub struct Job {
pub(crate) realm: Option<Global<Realm<'static>>>,
pub(crate) inner: InnerJob,
}
impl Job {
pub fn is_finished(&self) -> bool {
match &self.inner {
#[cfg(feature = "atomics")]
InnerJob::WaitAsync(job) => job.is_finished(),
_ => true,
}
}
pub fn run<'a>(self, agent: &mut Agent, gc: GcScope<'a, '_>) -> JsResult<'a, ()> {
let mut id = 0;
ndt::job_evaluation_start!(|| {
id = core::ptr::from_ref(&self).addr() as u64;
id
});
let mut pushed_context = false;
if let Some(realm) = self.realm.map(|r| r.take(agent))
&& agent.current_realm(gc.nogc()) != realm
{
agent.push_execution_context(ExecutionContext {
ecmascript_code: None,
function: None,
realm,
script_or_module: None,
});
pushed_context = true;
}
let result = match self.inner {
InnerJob::PromiseResolveThenable(job) => job.run(agent, gc),
InnerJob::PromiseReaction(job) => job.run(agent, gc),
#[cfg(feature = "atomics")]
InnerJob::WaitAsync(job) => job.run(agent, gc),
#[cfg(feature = "weak-refs")]
InnerJob::FinalizationRegistry(job) => {
job.run(agent, gc);
Ok(())
}
};
if pushed_context {
agent.execution_context_stack.pop();
}
ndt::job_evaluation_done!(|| id);
result
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum PromiseRejectionTrackerOperation {
Reject,
Handle,
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[cfg(feature = "shared-array-buffer")]
pub enum GrowSharedArrayBufferResult {
#[default]
Unhandled = 0,
Handled = 1,
}
pub trait HostHooks: core::fmt::Debug {
#[allow(unused_variables)]
fn ensure_can_compile_strings<'a>(
&self,
callee_realm: Realm<'a>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
Ok(())
}
#[allow(unused_variables)]
fn has_source_text_available(&self, func: Function) -> bool {
true
}
fn enqueue_generic_job(&self, job: Job);
fn enqueue_promise_job(&self, job: Job);
fn enqueue_timeout_job(&self, timeout_job: Job, milliseconds: u64);
#[allow(unused_variables)]
#[cfg(feature = "weak-refs")]
fn enqueue_finalization_registry_cleanup_job(&self, job: Job) {
}
#[allow(unused_variables)]
fn promise_rejection_tracker(
&self,
promise: Promise,
operation: PromiseRejectionTrackerOperation,
) {
}
#[allow(unused_variables)]
fn load_imported_module<'gc>(
&self,
agent: &mut Agent,
referrer: Referrer<'gc>,
module_request: ModuleRequest<'gc>,
host_defined: Option<HostDefined>,
payload: &mut GraphLoadingStateRecord<'gc>,
gc: NoGcScope<'gc, '_>,
) {
unimplemented!();
}
fn get_supported_import_attributes(&self) -> &[&'static str] {
&[]
}
#[allow(unused_variables)]
fn get_import_meta_properties<'gc>(
&self,
agent: &mut Agent,
module_record: SourceTextModule,
gc: NoGcScope<'gc, '_>,
) -> Vec<(PropertyKey<'gc>, Value<'gc>)> {
Default::default()
}
#[allow(unused_variables)]
fn finalize_import_meta(
&self,
agent: &mut Agent,
import_meta: OrdinaryObject,
module_record: SourceTextModule,
gc: NoGcScope,
) {
}
#[allow(unused_variables)]
#[inline(always)]
#[cfg(feature = "shared-array-buffer")]
fn grow_shared_array_buffer<'gc>(
&self,
agent: &Agent,
buffer: SharedArrayBuffer,
new_byte_length: u64,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, GrowSharedArrayBufferResult> {
Ok(GrowSharedArrayBufferResult::Unhandled)
}
fn get_host_data(&self) -> &dyn Any {
unimplemented!()
}
}
pub struct GcAgent {
agent: Agent,
realm_roots: Vec<Option<Realm<'static>>>,
}
#[must_use]
#[repr(transparent)]
pub struct RealmRoot {
index: u8,
}
impl RealmRoot {
pub fn initialize_host_defined(&self, agent: &mut GcAgent, host_defined: HostDefined) {
let realm = agent.get_realm_by_root(self);
realm.initialize_host_defined(&mut agent.agent, host_defined);
}
}
impl GcAgent {
pub fn new(options: AgentOptions, host_hooks: &'static dyn HostHooks) -> Self {
Self {
agent: Agent::new(options, host_hooks),
realm_roots: Vec::with_capacity(1),
}
}
fn root_realm(&mut self, identifier: Realm<'static>) -> RealmRoot {
let index = if let Some((index, deleted_entry)) = self
.realm_roots
.iter_mut()
.enumerate()
.find(|(_, entry)| entry.is_none())
{
*deleted_entry = Some(identifier);
index
} else {
self.realm_roots.push(Some(identifier));
self.realm_roots.len() - 1
};
assert!(self.agent.execution_context_stack.is_empty());
RealmRoot {
index: u8::try_from(index).expect("Only up to 256 simultaneous Realms are supported"),
}
}
pub fn create_realm(
&mut self,
create_global_object: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
create_global_this_value: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
initialize_global_object: Option<impl FnOnce(&mut Agent, Object, GcScope)>,
) -> RealmRoot {
let realm = self.agent.create_realm_internal(
create_global_object,
create_global_this_value,
initialize_global_object,
);
self.root_realm(realm.unbind())
}
pub fn create_default_realm(&mut self) -> RealmRoot {
let realm = self.agent.create_default_realm();
self.root_realm(realm)
}
pub fn remove_realm(&mut self, realm: RealmRoot) {
let RealmRoot { index } = realm;
let error_message = "Cannot remove a non-existing Realm";
let _ = self
.realm_roots
.get_mut(index as usize)
.expect(error_message)
.take()
.expect(error_message);
while let Some(r) = self.realm_roots.last()
&& r.is_none()
{
let _ = self.realm_roots.pop();
}
}
pub fn run_in_realm<F, R>(&mut self, realm: &RealmRoot, func: F) -> R
where
F: for<'agent, 'gc, 'scope> FnOnce(&'agent mut Agent, GcScope<'gc, 'scope>) -> R,
{
let realm = self.get_realm_by_root(realm);
assert!(self.agent.execution_context_stack.is_empty());
let result = self.agent.run_in_realm(realm, func);
#[cfg(feature = "weak-refs")]
clear_kept_objects(&mut self.agent);
assert!(self.agent.execution_context_stack.is_empty());
assert!(self.agent.vm_stack.is_empty());
self.agent.stack_refs.borrow_mut().clear();
result
}
pub fn run_job<F, R>(&mut self, job: Job, then: F) -> R
where
F: for<'agent, 'gc, 'scope> FnOnce(
&'agent mut Agent,
JsResult<'_, ()>,
GcScope<'gc, 'scope>,
) -> R,
{
assert!(self.agent.execution_context_stack.is_empty());
let result = self.agent.run_job(job, then);
#[cfg(feature = "weak-refs")]
clear_kept_objects(&mut self.agent);
assert!(self.agent.execution_context_stack.is_empty());
assert!(self.agent.vm_stack.is_empty());
self.agent.stack_refs.borrow_mut().clear();
result
}
fn get_realm_by_root(&self, realm_root: &RealmRoot) -> Realm<'static> {
let index = realm_root.index;
let error_message = "Couldn't find Realm by RealmRoot";
*self
.realm_roots
.get(index as usize)
.expect(error_message)
.as_ref()
.expect(error_message)
}
pub fn gc(&mut self) {
if self.agent.options.disable_gc {
return;
}
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let gc = GcScope::new(&mut gc, &mut scope);
let Self {
agent, realm_roots, ..
} = self;
heap_gc(agent, realm_roots, gc);
}
}
pub struct Agent {
pub(crate) heap: Heap,
pub(crate) options: AgentOptions,
#[expect(dead_code)]
symbol_id: usize,
pub(crate) global_symbol_registry: AHashMap<String<'static>, Symbol<'static>>,
pub(crate) host_hooks: &'static dyn HostHooks,
execution_context_stack: Vec<ExecutionContext>,
pub(crate) stack_refs: RefCell<Vec<HeapRootData>>,
pub(crate) stack_ref_collections: RefCell<Vec<HeapRootCollection>>,
pub(crate) vm_stack: Vec<NonNull<Vm>>,
#[cfg(feature = "weak-refs")]
pub(super) kept_alive: bool,
private_names_counter: u32,
module_async_evaluation_count: u32,
}
impl Agent {
pub(crate) fn new(options: AgentOptions, host_hooks: &'static dyn HostHooks) -> Self {
Self {
heap: Heap::new(),
options,
symbol_id: 0,
global_symbol_registry: AHashMap::default(),
host_hooks,
execution_context_stack: Vec::new(),
stack_refs: RefCell::new(Vec::with_capacity(64)),
stack_ref_collections: RefCell::new(Vec::with_capacity(32)),
vm_stack: Vec::with_capacity(16),
#[cfg(feature = "weak-refs")]
kept_alive: false,
private_names_counter: 0,
module_async_evaluation_count: 0,
}
}
pub fn can_suspend(&self) -> bool {
!self.options.no_block
}
pub fn gc(&mut self, gc: GcScope) {
let mut root_realms = self
.heap
.realms
.iter()
.enumerate()
.map(|(i, _)| Some(Realm::from_index(i)))
.collect::<Vec<_>>();
heap_gc(self, &mut root_realms, gc);
}
pub(crate) fn check_gc(&mut self) -> bool {
const ALLOC_COUNTER_LIMIT: usize = 1024 * 1024 * 2;
self.heap.alloc_counter > ALLOC_COUNTER_LIMIT
}
fn get_created_realm_root(&mut self) -> Realm<'static> {
assert!(!self.execution_context_stack.is_empty());
let identifier = self.current_realm_id_internal();
let _ = self.pop_execution_context();
identifier.unbind()
}
pub fn create_realm<'gc>(
&mut self,
create_global_object: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
create_global_this_value: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
initialize_global_object: Option<impl FnOnce(&mut Agent, Object, GcScope)>,
gc: GcScope<'gc, '_>,
) -> Realm<'gc> {
initialize_host_defined_realm(
self,
create_global_object,
create_global_this_value,
initialize_global_object,
gc,
);
self.get_created_realm_root()
}
fn create_realm_internal(
&mut self,
create_global_object: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
create_global_this_value: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
initialize_global_object: Option<impl FnOnce(&mut Agent, Object, GcScope)>,
) -> Realm<'static> {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let gc = GcScope::new(&mut gc, &mut scope);
initialize_host_defined_realm(
self,
create_global_object,
create_global_this_value,
initialize_global_object,
gc,
);
self.get_created_realm_root()
}
fn create_default_realm(&mut self) -> Realm<'static> {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let gc = GcScope::new(&mut gc, &mut scope);
initialize_default_realm(self, gc);
self.get_created_realm_root()
}
fn run_in_realm<F, R>(&mut self, realm: Realm, func: F) -> R
where
F: for<'agent, 'gc, 'scope> FnOnce(&'agent mut Agent, GcScope<'gc, 'scope>) -> R,
{
let execution_stack_depth_before_call = self.execution_context_stack.len();
self.push_execution_context(ExecutionContext {
ecmascript_code: None,
function: None,
realm: realm.unbind(),
script_or_module: None,
});
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let gc = GcScope::new(&mut gc, &mut scope);
let result = func(self, gc);
assert_eq!(
self.execution_context_stack.len(),
execution_stack_depth_before_call + 1
);
self.pop_execution_context();
result
}
fn run_job<F, R>(&mut self, mut job: Job, then: F) -> R
where
F: for<'agent, 'gc, 'scope> FnOnce(
&'agent mut Agent,
JsResult<'_, ()>,
GcScope<'gc, 'scope>,
) -> R,
{
let realm = job.realm.take().unwrap().take(self);
let execution_stack_depth_before_call = self.execution_context_stack.len();
self.push_execution_context(ExecutionContext {
ecmascript_code: None,
function: None,
realm,
script_or_module: None,
});
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let result = job.run(self, gc.reborrow()).unbind().bind(gc.nogc());
let result = then(self, result.unbind(), gc);
assert_eq!(
self.execution_context_stack.len(),
execution_stack_depth_before_call + 1
);
self.pop_execution_context();
result
}
#[cfg(test)]
pub(crate) fn current_global_env<'a>(&self, gc: NoGcScope<'a, '_>) -> GlobalEnvironment<'a> {
let realm = self.current_realm(gc);
let Some(e) = realm.get(self).global_env else {
panic_corrupted_agent()
};
e
}
pub fn current_global_object<'a>(&self, gc: NoGcScope<'a, '_>) -> Object<'a> {
self.current_realm(gc).get(self).global_object
}
pub fn current_realm<'a>(&self, gc: NoGcScope<'a, '_>) -> Realm<'a> {
self.current_realm_id_internal().bind(gc)
}
pub(crate) fn set_current_realm(&mut self, realm: Realm) {
let Some(ctx) = self.execution_context_stack.last_mut() else {
panic_corrupted_agent()
};
ctx.realm = realm.unbind();
}
#[inline]
pub(crate) fn current_realm_id_internal(&self) -> Realm<'static> {
let Some(r) = self.execution_context_stack.last().map(|ctx| ctx.realm) else {
panic_corrupted_agent()
};
r
}
pub(crate) fn current_realm_record(&self) -> &RealmRecord<'static> {
self.get_realm_record_by_id(self.current_realm_id_internal())
}
pub(crate) fn get_realm_record_by_id<'r>(&self, id: Realm<'r>) -> &RealmRecord<'r> {
id.get(self)
}
#[must_use]
pub fn create_exception_with_static_message<'a>(
&mut self,
kind: ExceptionType,
message: &'static str,
gc: NoGcScope<'a, '_>,
) -> Value<'a> {
let message = String::from_static_str(self, message, gc).unbind();
self.heap
.create(ErrorHeapData::new(kind, Some(message), None))
.into()
}
#[must_use]
pub(crate) fn todo<'a>(&mut self, feature: &'static str, gc: NoGcScope<'a, '_>) -> JsError<'a> {
self.throw_exception(
ExceptionType::Error,
format!("{feature} not implemented"),
gc,
)
}
#[must_use]
pub fn throw_exception_with_static_message<'a>(
&mut self,
kind: ExceptionType,
message: &'static str,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
JsError(
self.create_exception_with_static_message(kind, message, gc)
.unbind(),
)
}
#[must_use]
pub fn throw_exception<'a>(
&mut self,
kind: ExceptionType,
message: std::string::String,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
let message = String::from_string(self, message, gc).unbind();
JsError(
self.heap
.create(ErrorHeapData::new(kind, Some(message), None))
.into(),
)
}
#[must_use]
pub fn throw_exception_with_message<'a>(
&mut self,
kind: ExceptionType,
message: String,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
JsError(
self.heap
.create(ErrorHeapData::new(kind, Some(message.unbind()), None))
.bind(gc)
.into(),
)
}
#[must_use]
pub(crate) fn throw_allocation_exception<'a>(
&mut self,
error: TryReserveError,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
self.throw_exception(ExceptionType::RangeError, error.to_string(), gc)
}
pub(crate) fn running_execution_context(&self) -> &ExecutionContext {
let Some(ctx) = self.execution_context_stack.last() else {
panic_corrupted_agent()
};
ctx
}
pub(crate) fn is_evaluating_strict_code(&self) -> bool {
let Some(strict) = self
.running_execution_context()
.ecmascript_code
.map(|e| e.is_strict_mode)
else {
panic_corrupted_agent()
};
strict
}
pub(crate) fn check_call_depth<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> JsResult<'gc, ()> {
if self.execution_context_stack.len() > 3500 {
Err(self.throw_exception_with_static_message(
ExceptionType::RangeError,
"Maximum call stack size exceeded",
gc,
))
} else {
Ok(())
}
}
pub(crate) fn get_previous_context_realm<'a>(&self, gc: NoGcScope<'a, '_>) -> Realm<'a> {
assert!(self.execution_context_stack.len() >= 2);
let previous_context =
&self.execution_context_stack[self.execution_context_stack.len() - 2];
previous_context.realm.bind(gc)
}
pub(crate) fn push_execution_context(&mut self, context: ExecutionContext) {
self.execution_context_stack.push(context);
}
pub(crate) fn pop_execution_context(&mut self) -> Option<ExecutionContext> {
self.execution_context_stack.pop()
}
pub(crate) fn current_source_code<'a>(&self, gc: NoGcScope<'a, '_>) -> SourceCode<'a> {
let Some(s) = self
.execution_context_stack
.last()
.and_then(|s| s.ecmascript_code.as_ref())
.map(|e| e.source_code.bind(gc))
else {
panic_corrupted_agent()
};
s
}
pub(crate) fn current_lexical_environment<'a>(&self, gc: NoGcScope<'a, '_>) -> Environment<'a> {
let Some(e) = self
.execution_context_stack
.last()
.and_then(|s| s.ecmascript_code.as_ref())
.map(|e| e.lexical_environment.bind(gc))
else {
panic_corrupted_agent()
};
e
}
pub(crate) fn current_variable_environment<'a>(
&self,
gc: NoGcScope<'a, '_>,
) -> Environment<'a> {
let Some(e) = self
.execution_context_stack
.last()
.and_then(|s| s.ecmascript_code.as_ref())
.map(|e| e.variable_environment.bind(gc))
else {
panic_corrupted_agent()
};
e
}
pub(crate) fn current_private_environment<'a>(
&self,
gc: NoGcScope<'a, '_>,
) -> Option<PrivateEnvironment<'a>> {
let Some(e) = self
.execution_context_stack
.last()
.and_then(|s| s.ecmascript_code.as_ref())
.map(|e| e.private_environment.bind(gc))
else {
panic_corrupted_agent()
};
e
}
pub(crate) fn set_current_lexical_environment(&mut self, env: Environment) {
let Some(_) = self
.execution_context_stack
.last_mut()
.and_then(|s| s.ecmascript_code.as_mut())
.map(|e| {
e.lexical_environment = env.unbind();
})
else {
panic_corrupted_agent()
};
}
pub(crate) fn set_current_variable_environment(&mut self, env: Environment) {
let Some(_) = self
.execution_context_stack
.last_mut()
.and_then(|s| s.ecmascript_code.as_mut())
.map(|e| {
e.variable_environment = env.unbind();
})
else {
panic_corrupted_agent()
};
}
pub(crate) fn set_current_private_environment(&mut self, env: Option<PrivateEnvironment>) {
let Some(_) = self
.execution_context_stack
.last_mut()
.and_then(|s| s.ecmascript_code.as_mut())
.map(|e| {
e.private_environment = env.unbind();
})
else {
panic_corrupted_agent()
};
}
pub(crate) fn create_private_names(&mut self, count: usize) -> PrivateName {
let count = u32::try_from(count).expect("Unreasonable amount of PrivateNames");
let first = self.private_names_counter;
let next_free_name = first
.checked_add(count)
.expect("PrivateName counter overflowed");
self.private_names_counter = next_free_name;
PrivateName::from_u32(first)
}
pub(crate) fn increment_module_async_evaluation_count(&mut self) -> u32 {
let count = self.module_async_evaluation_count;
self.module_async_evaluation_count += 1;
count
}
pub(crate) fn active_function_object<'a>(&self, gc: NoGcScope<'a, '_>) -> Function<'a> {
let Some(f) = self
.execution_context_stack
.last()
.and_then(|s| s.function.bind(gc))
else {
panic_corrupted_agent()
};
f
}
pub(crate) fn get_active_script_or_module<'a>(
&self,
gc: NoGcScope<'a, '_>,
) -> Option<ScriptOrModule<'a>> {
let Some(s) = self
.execution_context_stack
.last()
.map(|s| s.script_or_module.bind(gc))
else {
panic_corrupted_agent()
};
s
}
pub fn get_host_data(&self) -> &dyn Any {
self.host_hooks.get_host_data()
}
pub fn run_script<'gc>(
&mut self,
source_text: String,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let realm = self.current_realm(gc.nogc());
let script = match parse_script(self, source_text, realm, false, None, gc.nogc()) {
Ok(script) => script,
Err(err) => {
let gc = gc.into_nogc();
let message =
String::from_string(self, err.first().unwrap().message.to_string(), gc);
return Err(self.throw_exception_with_message(
ExceptionType::SyntaxError,
message,
gc,
));
}
};
script_evaluation(self, script.unbind(), gc)
}
pub fn run_module<'gc>(
&mut self,
module: SourceTextModule,
host_defined: Option<HostDefined>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let module = module.bind(gc.nogc());
let Some(result) = module
.load_requested_modules(self, host_defined, gc.nogc())
.try_get_result(self, gc.nogc())
else {
return Err(self.throw_exception_with_static_message(
ExceptionType::Error,
"module was not sync",
gc.into_nogc(),
));
};
result.unbind()?;
module.link(self, gc.nogc()).unbind()?;
if let Some(result) = module
.unbind()
.evaluate(self, gc.reborrow())
.unbind()
.try_get_result(self, gc.into_nogc())
{
result
} else {
Ok(Value::Undefined)
}
}
}
pub(crate) fn get_active_script_or_module<'a>(
agent: &mut Agent,
_: NoGcScope<'a, '_>,
) -> Option<ScriptOrModule<'a>> {
if agent.execution_context_stack.is_empty() {
return None;
}
agent
.execution_context_stack
.iter()
.rev()
.find_map(|context| context.script_or_module)
}
pub(crate) fn try_resolve_binding<'a>(
agent: &mut Agent,
name: String<'a>,
cache: Option<PropertyLookupCache<'a>>,
gc: NoGcScope<'a, '_>,
) -> TryResult<'a, Reference<'a>> {
let env = agent.current_lexical_environment(gc);
let strict = agent.is_evaluating_strict_code();
try_get_identifier_reference(agent, env, name, cache, strict, gc)
}
pub(crate) fn resolve_binding<'a, 'b>(
agent: &mut Agent,
name: String<'b>,
cache: Option<PropertyLookupCache<'a>>,
env: Option<Environment>,
gc: GcScope<'a, 'b>,
) -> JsResult<'a, Reference<'a>> {
let name = name.bind(gc.nogc());
let env = env
.unwrap_or_else(|| {
agent.current_lexical_environment(gc.nogc())
})
.bind(gc.nogc());
let cache = cache.bind(gc.nogc());
let strict = agent.is_evaluating_strict_code();
get_identifier_reference(
agent,
Some(env.unbind()),
name.unbind(),
cache.unbind(),
strict,
gc,
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExceptionType {
Error,
AggregateError,
EvalError,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
UriError,
}
impl TryFrom<u16> for ExceptionType {
type Error = ();
fn try_from(value: u16) -> Result<Self, ()> {
match value {
0 => Ok(Self::Error),
1 => Ok(Self::AggregateError),
2 => Ok(Self::EvalError),
3 => Ok(Self::RangeError),
4 => Ok(Self::ReferenceError),
5 => Ok(Self::SyntaxError),
6 => Ok(Self::TypeError),
7 => Ok(Self::UriError),
_ => Err(()),
}
}
}
impl PrimitiveHeapAccess for Agent {}
impl HeapMarkAndSweep for Agent {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
heap,
execution_context_stack,
stack_refs,
stack_ref_collections,
vm_stack,
options: _,
symbol_id: _,
global_symbol_registry,
host_hooks: _,
#[cfg(feature = "weak-refs")]
kept_alive: _,
private_names_counter: _,
module_async_evaluation_count: _,
} = self;
execution_context_stack.iter().for_each(|ctx| {
ctx.mark_values(queues);
});
stack_refs
.borrow()
.iter()
.for_each(|value| value.mark_values(queues));
stack_ref_collections
.borrow()
.iter()
.for_each(|collection| collection.mark_values(queues));
vm_stack.iter().for_each(|vm_ptr| {
unsafe { vm_ptr.as_ref() }.mark_values(queues);
});
global_symbol_registry.mark_values(queues);
let mut last_filled_global_value = None;
heap.globals
.borrow()
.iter()
.enumerate()
.for_each(|(i, &value)| {
if value != HeapRootData::Empty {
value.mark_values(queues);
last_filled_global_value = Some(i);
}
});
if let Some(last_filled_global_value) = last_filled_global_value {
heap.globals
.borrow_mut()
.truncate(last_filled_global_value + 1);
}
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Agent {
heap: _,
execution_context_stack,
stack_refs,
stack_ref_collections,
vm_stack,
options: _,
symbol_id: _,
global_symbol_registry,
host_hooks: _,
#[cfg(feature = "weak-refs")]
kept_alive: _,
private_names_counter: _,
module_async_evaluation_count: _,
} = self;
execution_context_stack
.iter_mut()
.for_each(|entry| entry.sweep_values(compactions));
stack_refs
.borrow_mut()
.iter_mut()
.for_each(|entry| entry.sweep_values(compactions));
stack_ref_collections
.borrow_mut()
.iter_mut()
.for_each(|entry| entry.sweep_values(compactions));
vm_stack
.iter_mut()
.for_each(|entry| unsafe { entry.as_mut().sweep_values(compactions) });
global_symbol_registry.sweep_values(compactions);
}
}
#[cold]
#[inline(never)]
fn panic_corrupted_agent() -> ! {
panic!("Agent is corrupted")
}