From b0fa870e5edbf757f9391b6c7332618ec40e9e69 Mon Sep 17 00:00:00 2001 From: Alistair Date: Thu, 14 Nov 2024 12:57:39 +0000 Subject: [PATCH] feat(jstz_engine): add skeletons for `Context` and `Compartment` --- crates/jstz_engine/src/compartment.rs | 26 +++++++ crates/jstz_engine/src/context.rs | 104 ++++++++++++++++++++++++++ crates/jstz_engine/src/lib.rs | 11 ++- 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 crates/jstz_engine/src/compartment.rs create mode 100644 crates/jstz_engine/src/context.rs diff --git a/crates/jstz_engine/src/compartment.rs b/crates/jstz_engine/src/compartment.rs new file mode 100644 index 000000000..a60c7a61a --- /dev/null +++ b/crates/jstz_engine/src/compartment.rs @@ -0,0 +1,26 @@ +//! SpiderMonkey partitions the heap into compartments. Some key properties of +//! compartments are: +//! - Every GC object (cell) belongs to one compartment +//! - A GC object cannot hold a pointer to an object in another compartment. +//! This is fundamental invariant for security checks and garbage collection. +//! - Garbage collection is done a per-compartment basis -- this is an optimisation +//! to avoid marking the entire heap. +//! +//! INVARIANT: no references between compartments. +//! +//! This module defines the `Compartment` trait, which is implemented by types that represent compartments +//! at Rust's type level. This allows the type system to enforce the compartment invariant. + +use std::{fmt::Debug, hash::Hash, marker::PhantomData}; + +pub trait Compartment: Copy + Debug + Eq + Hash {} + +/// A wildcard compartment with an erased lifetime +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Any; +impl Compartment for Any {} + +/// A compartment that is alive for a given lifetime +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Ref<'a>(PhantomData<&'a mut ()>); +impl<'a> Compartment for Ref<'a> {} diff --git a/crates/jstz_engine/src/context.rs b/crates/jstz_engine/src/context.rs new file mode 100644 index 000000000..b33db6df0 --- /dev/null +++ b/crates/jstz_engine/src/context.rs @@ -0,0 +1,104 @@ +//! In order to run any JavaScript code in SpiderMonkey, an application must +//! have three key elements: a `Runtime``, a `Context`, and a global object. +//! This module implements a memory-safe wrapper for contexts. +//! +//! A `Runtime` contains the state for managing the garbage collector of +//! SpiderMonkey. All objects and contexts must be linked to a given runtime. +//! These objects cannot be shared across runtimes. Each thread must have a +//! unique `Runtime`. +//! +//! A `Context` contains the state for a virtual machine that executes and +//! manages JavaScript objects within a `Runtime`. It can compile and execute +//! scripts, get and set object properties, call JavaScript functions, convert +//! JavaScript data from one type to another, create objects, and so on. +//! +//! Global objects. Lastly, the global object contains all the classes, +//! functions, and variables that are available for JavaScript code to use. +//! Whenever JavaScript code does something like `window.open("http://jstz.dev/")`, +//! it is accessing a global property, in this case `window`. + +use std::{marker::PhantomData, ptr::NonNull}; + +use mozjs::{jsapi::JSContext, rust::Runtime}; + +use crate::{compartment::Compartment, AsRawPtr}; + +/// The context of a JavaScript runtime with a state `S`. +/// Ownership of a context represents the capability to manipulate data +/// managed by the engine. +#[allow(dead_code)] +pub struct Context { + raw_cx: NonNull, + marker: PhantomData, +} + +/// A context state for a JavaScript context owned by Rust. +pub struct Owned; + +/// A context state for a JavaScript context provided by callbacks from JavaScript. +#[allow(dead_code)] +pub struct Callback; + +/// A context state that has entered the compartment `C` with lifetime `'a`. +#[allow(dead_code)] +pub struct Entered<'a, C: Compartment, S> { + marker: PhantomData<(&'a (), C, S)>, +} + +// The following traits are 'marker' traits that are used to enforce +// type-level invariants on the context state. +#[allow(dead_code)] +pub trait CanAlloc {} +impl CanAlloc for Owned {} +impl CanAlloc for Callback {} +impl<'a, C: Compartment, S> CanAlloc for Entered<'a, C, S> {} + +#[allow(dead_code)] +pub trait CanAccess {} +impl CanAccess for Owned {} +impl CanAccess for Callback {} +impl<'a, C: Compartment, S> CanAccess for Entered<'a, C, S> {} + +#[allow(dead_code)] +pub trait InCompartment {} +impl<'a, C: Compartment, S> InCompartment for Entered<'a, C, S> {} + +impl Context { + pub fn from_runtime(rt: &Runtime) -> Self { + // SAFETY: `rt.cx()` cannot be `NULL`. + let raw_cx = unsafe { NonNull::new_unchecked(rt.cx()) }; + + Self { + raw_cx, + marker: PhantomData, + } + } +} + +impl AsRawPtr for Context { + type Ptr = *mut JSContext; + + unsafe fn as_raw_ptr(&self) -> Self::Ptr { + self.raw_cx.as_ptr() + } +} + +#[cfg(test)] +mod test { + use mozjs::rust::{JSEngine, Runtime}; + + use crate::AsRawPtr; + + use super::Context; + + #[test] + fn create_context_from_runtime() { + let engine = JSEngine::init().unwrap(); + let rt = Runtime::new(engine.handle()); + let raw_cx = rt.cx(); + + let cx = Context::from_runtime(&rt); + + assert_eq!(raw_cx, unsafe { cx.as_raw_ptr() }) + } +} diff --git a/crates/jstz_engine/src/lib.rs b/crates/jstz_engine/src/lib.rs index 0fba6f318..928e03213 100644 --- a/crates/jstz_engine/src/lib.rs +++ b/crates/jstz_engine/src/lib.rs @@ -1,5 +1,12 @@ -pub fn hello() { - println!("Hello, world!"); +mod compartment; +mod context; + +#[allow(dead_code)] +pub(crate) trait AsRawPtr { + type Ptr; + + /// Get the raw pointer to the underlying object. + unsafe fn as_raw_ptr(&self) -> Self::Ptr; } #[cfg(test)]