Skip to content

Commit

Permalink
feat(jstz_engine): add skeletons for Context and Compartment
Browse files Browse the repository at this point in the history
  • Loading branch information
johnyob committed Nov 27, 2024
1 parent 5fbe97d commit b0fa870
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 2 deletions.
26 changes: 26 additions & 0 deletions crates/jstz_engine/src/compartment.rs
Original file line number Diff line number Diff line change
@@ -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> {}
104 changes: 104 additions & 0 deletions crates/jstz_engine/src/context.rs
Original file line number Diff line number Diff line change
@@ -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<S> {
raw_cx: NonNull<JSContext>,
marker: PhantomData<S>,
}

/// 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<C: Compartment> {}
impl<'a, C: Compartment, S> InCompartment<C> for Entered<'a, C, S> {}

impl Context<Owned> {
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<S> AsRawPtr for Context<S> {
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() })
}
}
11 changes: 9 additions & 2 deletions crates/jstz_engine/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down

0 comments on commit b0fa870

Please sign in to comment.