mod hooks;
#[cfg(feature = "intl")]
pub(crate) mod icu;
pub mod intrinsics;
mod maybe_shared;
pub use hooks::{DefaultHooks, HostHooks};
#[cfg(feature = "intl")]
pub use icu::{BoaProvider, IcuError};
use intrinsics::Intrinsics;
pub use maybe_shared::MaybeShared;
#[cfg(not(feature = "intl"))]
pub use std::marker::PhantomData;
use std::{io::Read, path::Path, rc::Rc};
use crate::{
builtins,
class::{Class, ClassBuilder},
job::{JobQueue, NativeJob, SimpleJobQueue},
module::{IdleModuleLoader, ModuleLoader, SimpleModuleLoader},
native_function::NativeFunction,
object::{shape::RootShape, FunctionObjectBuilder, JsObject},
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
script::Script,
vm::{CallFrame, Vm},
JsResult, JsValue, Source,
};
use boa_ast::{expression::Identifier, StatementList};
use boa_interner::Interner;
use boa_profiler::Profiler;
use crate::vm::RuntimeLimits;
pub struct Context<'host> {
realm: Realm,
interner: Interner,
strict: bool,
#[cfg(feature = "fuzz")]
pub(crate) instructions_remaining: usize,
pub(crate) vm: Vm,
pub(crate) kept_alive: Vec<JsObject>,
#[cfg(feature = "intl")]
icu: icu::Icu<'host>,
host_hooks: MaybeShared<'host, dyn HostHooks>,
job_queue: MaybeShared<'host, dyn JobQueue>,
module_loader: MaybeShared<'host, dyn ModuleLoader>,
optimizer_options: OptimizerOptions,
root_shape: RootShape,
parser_identifier: u32,
}
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.realm)
.field("interner", &self.interner)
.field("vm", &self.vm)
.field("strict", &self.strict)
.field("promise_job_queue", &"JobQueue")
.field("hooks", &"HostHooks")
.field("module_loader", &"ModuleLoader")
.field("optimizer_options", &self.optimizer_options);
#[cfg(feature = "intl")]
debug.field("icu", &self.icu);
debug.finish()
}
}
impl Default for Context<'_> {
fn default() -> Self {
ContextBuilder::default()
.build()
.expect("Building the default context should not fail")
}
}
impl<'host> Context<'host> {
#[must_use]
pub fn builder() -> ContextBuilder<'static, 'static, 'static, 'static> {
ContextBuilder::default()
}
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
let main_timer = Profiler::global().start_event("Script evaluation", "Main");
let result = Script::parse(src, None, self)?.evaluate(self);
drop(main_timer);
Profiler::global().drop();
result
}
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: &str,
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self, body)
.name(name)
.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: &str,
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self, body)
.name(name)
.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<T>(&mut self) -> JsResult<()>
where
T: Class,
{
let mut class_builder = ClassBuilder::new::<T>(self);
T::init(&mut class_builder)?;
let class = class_builder.build();
let property = PropertyDescriptor::builder()
.value(class)
.writable(T::ATTRIBUTES.writable())
.enumerable(T::ATTRIBUTES.enumerable())
.configurable(T::ATTRIBUTES.configurable());
self.global_object()
.define_property_or_throw(T::NAME, property, self)?;
Ok(())
}
#[inline]
pub const fn interner(&self) -> &Interner {
&self.interner
}
#[inline]
pub fn interner_mut(&mut self) -> &mut Interner {
&mut self.interner
}
#[inline]
pub fn global_object(&self) -> JsObject {
self.realm.global_object().clone()
}
#[inline]
pub fn intrinsics(&self) -> &Intrinsics {
self.realm.intrinsics()
}
#[inline]
pub const fn realm(&self) -> &Realm {
&self.realm
}
#[cfg(feature = "trace")]
#[inline]
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
#[inline]
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: NativeJob) {
self.job_queue().enqueue_promise_job(job, self);
}
#[inline]
pub fn run_jobs(&mut self) {
self.job_queue().run_jobs(self);
self.clear_kept_objects();
}
#[allow(clippy::future_not_send)]
pub async fn run_jobs_async(&mut self) {
self.job_queue().run_jobs_async(self).await;
self.clear_kept_objects();
}
#[inline]
pub fn clear_kept_objects(&mut self) {
self.kept_alive.clear();
}
#[inline]
pub fn stack_trace(&self) -> impl Iterator<Item = &CallFrame> {
self.vm.frames.iter().rev()
}
#[inline]
pub fn enter_realm(&mut self, realm: Realm) -> Realm {
self.vm
.environments
.replace_global(realm.environment().clone());
std::mem::replace(&mut self.realm, realm)
}
#[inline]
pub const fn root_shape(&self) -> &RootShape {
&self.root_shape
}
#[inline]
pub fn host_hooks(&self) -> MaybeShared<'host, dyn HostHooks> {
self.host_hooks.clone()
}
#[inline]
pub fn job_queue(&self) -> MaybeShared<'host, dyn JobQueue> {
self.job_queue.clone()
}
pub fn module_loader(&self) -> MaybeShared<'host, dyn ModuleLoader> {
self.module_loader.clone()
}
#[inline]
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
}
}
impl Context<'_> {
pub(crate) fn swap_realm(&mut self, realm: &mut Realm) {
std::mem::swap(&mut self.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: Identifier) -> JsResult<bool> {
let global_object = self.realm().global_object().clone();
let name = self.interner().resolve_expect(name.sym()).utf16().into();
let existing_prop = global_object.__get_own_property__(&name, self)?;
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: Identifier) -> JsResult<bool> {
let global_object = self.realm().global_object().clone();
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
let has_property = global_object.has_own_property(name, self)?;
if has_property {
return Ok(true);
}
global_object.is_extensible(self)
}
pub(crate) fn create_global_var_binding(
&mut self,
name: Identifier,
configurable: bool,
) -> JsResult<()> {
let global_object = self.realm().global_object().clone();
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
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: Identifier,
function: JsObject,
configurable: bool,
) -> JsResult<()> {
let global_object = self.realm().global_object().clone();
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
let existing_prop = global_object.__get_own_property__(&name, self)?;
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: Identifier) -> JsResult<bool> {
let global_object = self.realm().global_object().clone();
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
let existing_prop = global_object.__get_own_property__(&name, self)?;
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
}
}
impl<'host> Context<'host> {
pub(crate) fn guard<F>(&mut self, cleanup: F) -> ContextCleanupGuard<'_, 'host, F>
where
F: FnOnce(&mut Context<'_>) + 'static,
{
ContextCleanupGuard::new(self, cleanup)
}
#[cfg(feature = "intl")]
pub(crate) const fn icu(&self) -> &icu::Icu<'host> {
&self.icu
}
}
#[cfg_attr(
feature = "intl",
doc = "The required data in a valid provider is specified in [`BoaProvider`]"
)]
#[derive(Default)]
pub struct ContextBuilder<'icu, 'hooks, 'queue, 'module> {
interner: Option<Interner>,
host_hooks: Option<MaybeShared<'hooks, dyn HostHooks>>,
job_queue: Option<MaybeShared<'queue, dyn JobQueue>>,
module_loader: Option<MaybeShared<'module, dyn ModuleLoader>>,
#[cfg(feature = "intl")]
icu: Option<icu::Icu<'icu>>,
#[cfg(not(feature = "intl"))]
icu: PhantomData<&'icu ()>,
#[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 JobQueue;
#[derive(Clone, Copy, Debug)]
struct HostHooks;
#[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("job_queue", &self.job_queue.as_ref().map(|_| JobQueue))
.field(
"module_loader",
&self.module_loader.as_ref().map(|_| ModuleLoader),
);
#[cfg(feature = "intl")]
out.field("icu", &self.icu);
#[cfg(feature = "fuzz")]
out.field("instructions_remaining", &self.instructions_remaining);
out.finish()
}
}
impl<'icu, 'hooks, 'queue, 'module> ContextBuilder<'icu, 'hooks, 'queue, 'module> {
#[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_provider(
self,
provider: BoaProvider<'_>,
) -> Result<ContextBuilder<'_, 'hooks, 'queue, 'module>, IcuError> {
Ok(ContextBuilder {
icu: Some(icu::Icu::new(provider)?),
..self
})
}
#[must_use]
pub fn host_hooks<'new_hooks, H>(
self,
host_hooks: H,
) -> ContextBuilder<'icu, 'new_hooks, 'queue, 'module>
where
H: Into<MaybeShared<'new_hooks, dyn HostHooks>>,
{
ContextBuilder {
host_hooks: Some(host_hooks.into()),
..self
}
}
#[must_use]
pub fn job_queue<'new_queue, Q>(
self,
job_queue: Q,
) -> ContextBuilder<'icu, 'hooks, 'new_queue, 'module>
where
Q: Into<MaybeShared<'new_queue, dyn JobQueue>>,
{
ContextBuilder {
job_queue: Some(job_queue.into()),
..self
}
}
#[must_use]
pub fn module_loader<'new_module, M>(
self,
module_loader: M,
) -> ContextBuilder<'icu, 'hooks, 'queue, 'new_module>
where
M: Into<MaybeShared<'new_module, dyn ModuleLoader>>,
{
ContextBuilder {
module_loader: Some(module_loader.into()),
..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<'host>(self) -> JsResult<Context<'host>>
where
'icu: 'host,
'hooks: 'host,
'queue: 'host,
'module: 'host,
{
let root_shape = RootShape::default();
let host_hooks = self.host_hooks.unwrap_or_else(|| {
let hooks: &dyn HostHooks = &DefaultHooks;
hooks.into()
});
let realm = Realm::create(&*host_hooks, &root_shape);
let vm = Vm::new(realm.environment().clone());
let module_loader = if let Some(loader) = self.module_loader {
loader
} else {
SimpleModuleLoader::new(Path::new(".")).map_or_else(
|_| {
let loader: &dyn ModuleLoader = &IdleModuleLoader;
loader.into()
},
|loader| {
let loader: Rc<dyn ModuleLoader> = Rc::new(loader);
loader.into()
},
)
};
let mut context = Context {
realm,
interner: self.interner.unwrap_or_default(),
vm,
strict: false,
#[cfg(feature = "intl")]
icu: self.icu.unwrap_or_else(|| {
let buffer: &dyn icu_provider::BufferProvider = boa_icu_provider::buffer();
let provider = BoaProvider::Buffer(buffer);
icu::Icu::new(provider).expect("Failed to initialize default icu data.")
}),
#[cfg(feature = "fuzz")]
instructions_remaining: self.instructions_remaining,
kept_alive: Vec::new(),
host_hooks,
job_queue: self.job_queue.unwrap_or_else(|| {
let queue: Rc<dyn JobQueue> = Rc::new(SimpleJobQueue::new());
queue.into()
}),
module_loader,
optimizer_options: OptimizerOptions::OPTIMIZE_ALL,
root_shape,
parser_identifier: 0,
};
builtins::set_default_global_bindings(&mut context)?;
Ok(context)
}
}
#[derive(Debug)]
pub(crate) struct ContextCleanupGuard<'a, 'host, F>
where
F: FnOnce(&mut Context<'_>) + 'static,
{
context: &'a mut Context<'host>,
cleanup: Option<F>,
}
impl<'a, 'host, F> ContextCleanupGuard<'a, 'host, F>
where
F: FnOnce(&mut Context<'_>) + 'static,
{
pub(crate) fn new(context: &'a mut Context<'host>, cleanup: F) -> Self {
Self {
context,
cleanup: Some(cleanup),
}
}
}
impl<'host, F> std::ops::Deref for ContextCleanupGuard<'_, 'host, F>
where
F: FnOnce(&mut Context<'_>) + 'static,
{
type Target = Context<'host>;
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);
}
}
}