use std::{cell::Cell, path::Path, rc::Rc};
use boa_ast::StatementList;
use boa_interner::Interner;
use boa_parser::source::ReadChar;
pub use hooks::{DefaultHooks, HostHooks};
#[cfg(feature = "intl")]
pub use icu::IcuError;
use intrinsics::Intrinsics;
#[cfg(feature = "temporal")]
use temporal_rs::provider::TimeZoneProvider;
#[cfg(feature = "temporal")]
use timezone_provider::tzif::CompiledTzdbProvider;
use crate::job::Job;
use crate::module::DynModuleLoader;
use crate::vm::RuntimeLimits;
use crate::{
HostDefined, JsNativeError, JsResult, JsString, JsValue, NativeObject, Source, builtins,
class::{Class, ClassBuilder},
job::{JobExecutor, SimpleJobExecutor},
js_string,
module::{IdleModuleLoader, ModuleLoader, SimpleModuleLoader},
native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject, shape::RootShape},
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
script::Script,
vm::{ActiveRunnable, CallFrame, Vm},
};
use self::intrinsics::StandardConstructor;
pub mod time;
use crate::context::time::StdClock;
pub use time::Clock;
mod hooks;
#[cfg(feature = "intl")]
pub(crate) mod icu;
pub mod intrinsics;
thread_local! {
static CANNOT_BLOCK_COUNTER: Cell<u64> = const { Cell::new(0) };
}
pub struct Context {
interner: Interner,
strict: bool,
#[cfg(feature = "fuzz")]
pub(crate) instructions_remaining: usize,
pub(crate) vm: Vm,
pub(crate) kept_alive: Vec<JsObject>,
can_block: bool,
#[cfg(feature = "temporal")]
tz_provider: CompiledTzdbProvider,
#[cfg(feature = "intl")]
intl_provider: icu::IntlProvider,
host_hooks: Rc<dyn HostHooks>,
clock: Rc<dyn Clock>,
job_executor: Rc<dyn JobExecutor>,
module_loader: Rc<dyn DynModuleLoader>,
optimizer_options: OptimizerOptions,
root_shape: RootShape,
parser_identifier: u32,
data: HostDefined,
}
impl std::fmt::Debug for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("Context");
debug
.field("realm", &self.vm.realm)
.field("interner", &self.interner)
.field("vm", &self.vm)
.field("strict", &self.strict)
.field("job_executor", &"JobExecutor")
.field("hooks", &"HostHooks")
.field("clock", &"Clock")
.field("module_loader", &"ModuleLoader")
.field("optimizer_options", &self.optimizer_options);
#[cfg(feature = "intl")]
debug.field("intl_provider", &self.intl_provider);
debug.finish_non_exhaustive()
}
}
impl Drop for Context {
fn drop(&mut self) {
if !self.can_block {
CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() - 1);
}
}
}
impl Default for Context {
fn default() -> Self {
ContextBuilder::default()
.build()
.expect("Building the default context should not fail")
}
}
impl Context {
#[must_use]
pub fn builder() -> ContextBuilder {
ContextBuilder::default()
}
#[allow(clippy::unit_arg, dropping_copy_types)]
pub fn eval<R: ReadChar>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
Script::parse(src, None, self)?.evaluate(self)
}
pub fn optimize_statement_list(
&mut self,
statement_list: &mut StatementList,
) -> OptimizerStatistics {
let mut optimizer = Optimizer::new(self);
optimizer.apply(statement_list)
}
pub fn register_global_property<K, V>(
&mut self,
key: K,
value: V,
attribute: Attribute,
) -> JsResult<()>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.global_object().define_property_or_throw(
key,
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable()),
self,
)?;
Ok(())
}
pub fn register_global_callable(
&mut self,
name: JsString,
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self.realm(), body)
.name(name.clone())
.length(length)
.constructor(true)
.build();
self.global_object().define_property_or_throw(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
self,
)?;
Ok(())
}
pub fn register_global_builtin_callable(
&mut self,
name: JsString,
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self.realm(), body)
.name(name.clone())
.length(length)
.constructor(false)
.build();
self.global_object().define_property_or_throw(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
self,
)?;
Ok(())
}
pub fn register_global_class<C: Class>(&mut self) -> JsResult<()> {
if self.realm().has_class::<C>() {
return Err(JsNativeError::typ()
.with_message("cannot register a class twice")
.into());
}
let mut class_builder = ClassBuilder::new::<C>(self);
C::init(&mut class_builder)?;
let class = class_builder.build();
let property = PropertyDescriptor::builder()
.value(class.constructor())
.writable(C::ATTRIBUTES.writable())
.enumerable(C::ATTRIBUTES.enumerable())
.configurable(C::ATTRIBUTES.configurable());
self.global_object()
.define_property_or_throw(js_string!(C::NAME), property, self)?;
self.realm().register_class::<C>(class);
Ok(())
}
pub fn unregister_global_class<C: Class>(&mut self) -> JsResult<Option<StandardConstructor>> {
self.global_object()
.delete_property_or_throw(js_string!(C::NAME), self)?;
Ok(self.realm().unregister_class::<C>())
}
#[must_use]
pub fn has_global_class<C: Class>(&self) -> bool {
self.realm().has_class::<C>()
}
#[must_use]
pub fn get_global_class<C: Class>(&self) -> Option<StandardConstructor> {
self.realm().get_class::<C>()
}
#[inline]
#[must_use]
pub const fn interner(&self) -> &Interner {
&self.interner
}
#[inline]
pub fn interner_mut(&mut self) -> &mut Interner {
&mut self.interner
}
#[inline]
#[must_use]
pub fn global_object(&self) -> JsObject {
self.vm.realm.global_object().clone()
}
#[inline]
#[must_use]
pub fn intrinsics(&self) -> &Intrinsics {
self.vm.realm.intrinsics()
}
#[inline]
#[must_use]
pub const fn realm(&self) -> &Realm {
&self.vm.realm
}
#[cfg(feature = "trace")]
#[inline]
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
#[inline]
#[must_use]
pub const fn optimizer_options(&self) -> OptimizerOptions {
self.optimizer_options
}
#[inline]
pub fn set_optimizer_options(&mut self, optimizer_options: OptimizerOptions) {
self.optimizer_options = optimizer_options;
}
#[inline]
pub fn strict(&mut self, strict: bool) {
self.strict = strict;
}
#[inline]
pub fn enqueue_job(&mut self, job: Job) {
self.job_executor().enqueue_job(job, self);
}
#[inline]
pub fn run_jobs(&mut self) -> JsResult<()> {
self.job_executor().run_jobs(self)
}
#[inline]
pub fn clear_kept_objects(&mut self) {
self.kept_alive.clear();
}
#[inline]
pub fn stack_trace(&self) -> impl Iterator<Item = &CallFrame> {
let frames: Vec<_> = self
.vm
.frames
.iter()
.chain(std::iter::once(&self.vm.frame))
.collect();
frames.into_iter().skip(1).rev()
}
#[inline]
pub fn enter_realm(&mut self, realm: Realm) -> Realm {
self.vm
.environments
.replace_global(realm.environment().clone());
std::mem::replace(&mut self.vm.realm, realm)
}
pub fn create_realm(&mut self) -> JsResult<Realm> {
let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape)?;
let old_realm = self.enter_realm(realm);
builtins::set_default_global_bindings(self)?;
Ok(self.enter_realm(old_realm))
}
#[inline]
#[must_use]
pub const fn root_shape(&self) -> &RootShape {
&self.root_shape
}
#[inline]
#[must_use]
pub fn host_hooks(&self) -> Rc<dyn HostHooks> {
self.host_hooks.clone()
}
#[inline]
#[must_use]
pub fn clock(&self) -> &dyn Clock {
self.clock.as_ref()
}
#[inline]
#[must_use]
pub fn downcast_job_executor<T: 'static>(&self) -> Option<Rc<T>> {
Rc::downcast(self.job_executor.clone()).ok()
}
#[must_use]
pub fn downcast_module_loader<T: 'static>(&self) -> Option<Rc<T>> {
Rc::downcast(self.module_loader.clone()).ok()
}
#[inline]
#[must_use]
pub const fn runtime_limits(&self) -> RuntimeLimits {
self.vm.runtime_limits
}
#[inline]
pub fn set_runtime_limits(&mut self, runtime_limits: RuntimeLimits) {
self.vm.runtime_limits = runtime_limits;
}
#[inline]
pub fn runtime_limits_mut(&mut self) -> &mut RuntimeLimits {
&mut self.vm.runtime_limits
}
#[inline]
#[must_use]
pub fn can_block(&self) -> bool {
self.can_block
}
#[inline]
pub fn insert_data<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
self.data.insert(value)
}
#[inline]
#[must_use]
pub fn has_data<T: NativeObject>(&self) -> bool {
self.data.has::<T>()
}
#[inline]
pub fn remove_data<T: NativeObject>(&mut self) -> Option<Box<T>> {
self.data.remove::<T>()
}
#[inline]
#[must_use]
pub fn get_data<T: NativeObject>(&self) -> Option<&T> {
self.data.get::<T>()
}
}
impl Context {
pub(crate) fn job_executor(&self) -> Rc<dyn JobExecutor> {
self.job_executor.clone()
}
pub(crate) fn module_loader(&self) -> Rc<dyn DynModuleLoader> {
self.module_loader.clone()
}
pub(crate) fn swap_realm(&mut self, realm: &mut Realm) {
std::mem::swap(&mut self.vm.realm, realm);
}
pub(crate) fn next_parser_identifier(&mut self) -> u32 {
self.parser_identifier += 1;
self.parser_identifier
}
pub(crate) fn can_declare_global_function(&mut self, name: &JsString) -> JsResult<bool> {
let global_object = self.realm().global_object().clone();
let name = name.clone().into();
let existing_prop = global_object.__get_own_property__(&name, &mut self.into())?;
let Some(existing_prop) = existing_prop else {
return global_object.is_extensible(self);
};
if existing_prop.configurable() == Some(true) {
return Ok(true);
}
if existing_prop.is_data_descriptor()
&& existing_prop.writable() == Some(true)
&& existing_prop.enumerable() == Some(true)
{
return Ok(true);
}
Ok(false)
}
pub(crate) fn can_declare_global_var(&mut self, name: &JsString) -> JsResult<bool> {
let global_object = self.realm().global_object().clone();
let has_property = global_object.has_own_property(name.clone(), self)?;
if has_property {
return Ok(true);
}
global_object.is_extensible(self)
}
pub(crate) fn create_global_var_binding(
&mut self,
name: JsString,
configurable: bool,
) -> JsResult<()> {
let global_object = self.realm().global_object().clone();
let has_property = global_object.has_own_property(name.clone(), self)?;
let extensible = global_object.is_extensible(self)?;
if !has_property && extensible {
global_object.define_property_or_throw(
name,
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(configurable)
.build(),
self,
)?;
}
Ok(())
}
pub(crate) fn create_global_function_binding(
&mut self,
name: JsString,
function: JsObject,
configurable: bool,
) -> JsResult<()> {
let global_object = self.realm().global_object().clone();
let existing_prop =
global_object.__get_own_property__(&name.clone().into(), &mut self.into())?;
let desc = if existing_prop.is_none()
|| existing_prop.and_then(|p| p.configurable()) == Some(true)
{
PropertyDescriptor::builder()
.value(function.clone())
.writable(true)
.enumerable(true)
.configurable(configurable)
.build()
}
else {
PropertyDescriptor::builder()
.value(function.clone())
.build()
};
global_object.define_property_or_throw(name.clone(), desc, self)?;
global_object.set(name, function, false, self)?;
Ok(())
}
pub(crate) fn has_restricted_global_property(&mut self, name: &JsString) -> JsResult<bool> {
let global_object = self.realm().global_object().clone();
let name = name.clone().into();
let existing_prop = global_object.__get_own_property__(&name, &mut self.into())?;
let Some(existing_prop) = existing_prop else {
return Ok(false);
};
if existing_prop.configurable() == Some(true) {
return Ok(false);
}
Ok(true)
}
pub(crate) const fn is_strict(&self) -> bool {
self.strict
}
pub(crate) fn get_active_script_or_module(&self) -> Option<ActiveRunnable> {
if let Some(active_runnable) = &self.vm.frame.active_runnable {
return Some(active_runnable.clone());
}
self.vm
.frames
.iter()
.rev()
.find_map(|frame| frame.active_runnable.clone())
}
pub(crate) fn active_function_object(&self) -> Option<JsObject> {
if self.vm.native_active_function.is_some() {
return self.vm.native_active_function.clone();
}
self.vm.stack.get_function(self.vm.frame())
}
}
impl Context {
pub(crate) fn guard<F>(&mut self, cleanup: F) -> ContextCleanupGuard<'_, F>
where
F: FnOnce(&mut Context) + 'static,
{
ContextCleanupGuard::new(self, cleanup)
}
#[cfg(feature = "intl")]
pub(crate) const fn intl_provider(&self) -> &icu::IntlProvider {
&self.intl_provider
}
#[cfg(feature = "temporal")]
pub(crate) fn tz_provider(&self) -> &impl TimeZoneProvider {
&self.tz_provider
}
}
#[derive(Default)]
pub struct ContextBuilder {
interner: Option<Interner>,
host_hooks: Option<Rc<dyn HostHooks>>,
clock: Option<Rc<dyn Clock>>,
job_executor: Option<Rc<dyn JobExecutor>>,
module_loader: Option<Rc<dyn DynModuleLoader>>,
can_block: bool,
#[cfg(feature = "intl")]
icu: Option<icu::IntlProvider>,
#[cfg(feature = "fuzz")]
instructions_remaining: usize,
}
impl std::fmt::Debug for ContextBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[derive(Clone, Copy, Debug)]
struct JobExecutor;
#[derive(Clone, Copy, Debug)]
struct HostHooks;
#[derive(Clone, Copy, Debug)]
struct Clock;
#[derive(Clone, Copy, Debug)]
struct ModuleLoader;
let mut out = f.debug_struct("ContextBuilder");
out.field("interner", &self.interner)
.field("host_hooks", &self.host_hooks.as_ref().map(|_| HostHooks))
.field("clock", &self.clock.as_ref().map(|_| Clock))
.field(
"job_executor",
&self.job_executor.as_ref().map(|_| JobExecutor),
)
.field(
"module_loader",
&self.module_loader.as_ref().map(|_| ModuleLoader),
)
.field("can_block", &self.can_block);
#[cfg(feature = "intl")]
out.field("icu", &self.icu);
#[cfg(feature = "fuzz")]
out.field("instructions_remaining", &self.instructions_remaining);
out.finish()
}
}
impl ContextBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn interner(mut self, interner: Interner) -> Self {
self.interner = Some(interner);
self
}
#[cfg(feature = "intl")]
pub fn icu_buffer_provider<
T: icu_provider::prelude::DynamicDryDataProvider<icu_provider::prelude::BufferMarker>
+ 'static,
>(
mut self,
provider: T,
) -> Result<Self, IcuError> {
self.icu = Some(icu::IntlProvider::try_new_buffer(provider));
Ok(self)
}
#[must_use]
pub fn host_hooks<H: HostHooks + 'static>(mut self, host_hooks: Rc<H>) -> Self {
self.host_hooks = Some(host_hooks);
self
}
#[must_use]
pub fn clock<C: Clock + 'static>(mut self, clock: Rc<C>) -> Self {
self.clock = Some(clock);
self
}
#[must_use]
pub fn job_executor<Q: JobExecutor + 'static>(mut self, job_executor: Rc<Q>) -> Self {
self.job_executor = Some(job_executor);
self
}
#[must_use]
pub fn module_loader<M: ModuleLoader + 'static>(mut self, module_loader: Rc<M>) -> Self {
self.module_loader = Some(module_loader);
self
}
#[must_use]
pub const fn can_block(mut self, can_block: bool) -> Self {
self.can_block = can_block;
self
}
#[cfg(feature = "fuzz")]
#[must_use]
pub const fn instructions_remaining(mut self, instructions_remaining: usize) -> Self {
self.instructions_remaining = instructions_remaining;
self
}
pub fn build(self) -> JsResult<Context> {
if self.can_block {
if CANNOT_BLOCK_COUNTER.get() > 0 {
return Err(JsNativeError::typ()
.with_message(
"a context that can block must be the only active context in its current thread",
)
.into());
}
} else {
CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() + 1);
}
let root_shape = RootShape::default();
let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks));
let clock = self.clock.unwrap_or_else(|| Rc::new(StdClock));
let realm = Realm::create(host_hooks.as_ref(), &root_shape)?;
let vm = Vm::new(realm);
let module_loader: Rc<dyn DynModuleLoader> = if let Some(loader) = self.module_loader {
loader
} else if let Ok(loader) = SimpleModuleLoader::new(Path::new(".")) {
Rc::new(loader)
} else {
Rc::new(IdleModuleLoader)
};
let job_executor = self
.job_executor
.unwrap_or_else(|| Rc::new(SimpleJobExecutor::new()));
let mut context = Context {
interner: self.interner.unwrap_or_default(),
vm,
strict: false,
#[cfg(feature = "temporal")]
tz_provider: CompiledTzdbProvider::default(),
#[cfg(feature = "intl")]
intl_provider: if let Some(icu) = self.icu {
icu
} else {
cfg_if::cfg_if! {
if #[cfg(feature = "intl_bundled")] {
icu::IntlProvider::try_new_buffer(boa_icu_provider::buffer())
} else {
return Err(JsNativeError::typ()
.with_message("missing Intl provider for context")
.into()
);
}
}
},
#[cfg(feature = "fuzz")]
instructions_remaining: self.instructions_remaining,
kept_alive: Vec::new(),
host_hooks,
clock,
job_executor,
module_loader,
optimizer_options: OptimizerOptions::OPTIMIZE_ALL,
root_shape,
parser_identifier: 0,
can_block: self.can_block,
data: HostDefined::default(),
};
builtins::set_default_global_bindings(&mut context)?;
Ok(context)
}
}
#[derive(Debug)]
pub(crate) struct ContextCleanupGuard<'a, F>
where
F: FnOnce(&mut Context) + 'static,
{
context: &'a mut Context,
cleanup: Option<F>,
}
impl<'a, F> ContextCleanupGuard<'a, F>
where
F: FnOnce(&mut Context) + 'static,
{
pub(crate) fn new(context: &'a mut Context, cleanup: F) -> Self {
Self {
context,
cleanup: Some(cleanup),
}
}
}
impl<F> std::ops::Deref for ContextCleanupGuard<'_, F>
where
F: FnOnce(&mut Context) + 'static,
{
type Target = Context;
fn deref(&self) -> &Self::Target {
self.context
}
}
impl<F> std::ops::DerefMut for ContextCleanupGuard<'_, F>
where
F: FnOnce(&mut Context) + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.context
}
}
impl<F> Drop for ContextCleanupGuard<'_, F>
where
F: FnOnce(&mut Context) + 'static,
{
fn drop(&mut self) {
if let Some(cleanup) = self.cleanup.take() {
cleanup(self.context);
}
}
}