Skip to content

Commit

Permalink
Add JSContext wrapper for bindings with fewer raw pointers.
Browse files Browse the repository at this point in the history
Signed-off-by: Josh Matthews <[email protected]>
  • Loading branch information
jdm committed Mar 3, 2025
1 parent 87cabf4 commit 3289877
Show file tree
Hide file tree
Showing 25 changed files with 235 additions and 190 deletions.
7 changes: 4 additions & 3 deletions mozjs/examples/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ use mozjs::rust::{JSEngine, RealmOptions, Runtime};

fn run(rt: Runtime) {
let options = RealmOptions::default();
rooted!(in(rt.cx()) let global = unsafe {
JS_NewGlobalObject(rt.cx(), &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
let cx = rt.cx();
rooted!(in(*cx) let global = unsafe {
JS_NewGlobalObject(*cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
OnNewGlobalHookOption::FireOnNewGlobalHook,
&*options)
});
Expand All @@ -37,7 +38,7 @@ fn run(rt: Runtime) {
* The return value comes back here. If it could be a GC thing, you must add it to the
* GC's "root set" with the rooted! macro.
*/
rooted!(in(rt.cx()) let mut rval = UndefinedValue());
rooted!(in(*cx) let mut rval = UndefinedValue());

/*
* Some example source in a string. This is equivalent to JS_EvaluateScript in C++.
Expand Down
4 changes: 2 additions & 2 deletions mozjs/examples/minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ fn run(rt: Runtime) {
// This demonstrates the way Rust uses the C++ garbage collector: using the rooted! macro to
// indicate when the GC can collect them.
let options = RealmOptions::default();
rooted!(in(cx) let _global = unsafe {
JS_NewGlobalObject(cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
rooted!(in(*cx) let _global = unsafe {
JS_NewGlobalObject(*cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
OnNewGlobalHookOption::FireOnNewGlobalHook,
&*options)
});
Expand Down
59 changes: 30 additions & 29 deletions mozjs/examples/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,34 +47,35 @@ unsafe extern "C" fn bar(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool

fn run(rt: Runtime) {
let options = RealmOptions::default();
rooted!(in(rt.cx()) let global = unsafe {
JS_NewGlobalObject(rt.cx(), &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
let cx = rt.cx();
rooted!(in(*cx) let global = unsafe {
JS_NewGlobalObject(*cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
OnNewGlobalHookOption::FireOnNewGlobalHook,
&*options)
});
let _ac = JSAutoRealm::new(rt.cx(), global.get());
let _ac = JSAutoRealm::new(*cx, global.get());

// Get WebAssembly.Module and WebAssembly.Instance constructors.
rooted!(in(rt.cx()) let mut wasm = UndefinedValue());
rooted!(in(rt.cx()) let mut wasm_module = UndefinedValue());
rooted!(in(rt.cx()) let mut wasm_instance = UndefinedValue());
rooted!(in(*cx) let mut wasm = UndefinedValue());
rooted!(in(*cx) let mut wasm_module = UndefinedValue());
rooted!(in(*cx) let mut wasm_instance = UndefinedValue());

unsafe {
assert!(JS_GetProperty(
rt.cx(),
*cx,
global.handle(),
c"WebAssembly".as_ptr(),
&mut wasm.handle_mut()
));
rooted!(in(rt.cx()) let mut wasm_obj = wasm.to_object());
rooted!(in(*cx) let mut wasm_obj = wasm.to_object());
assert!(JS_GetProperty(
rt.cx(),
*cx,
wasm_obj.handle(),
c"Module".as_ptr(),
&mut wasm_module.handle_mut()
));
assert!(JS_GetProperty(
rt.cx(),
*cx,
wasm_obj.handle(),
c"Instance".as_ptr(),
&mut wasm_instance.handle_mut()
Expand All @@ -84,85 +85,85 @@ fn run(rt: Runtime) {
assert!(HI_WASM.0.as_ptr() as usize % 8 == 0);

// Construct Wasm module from bytes.
rooted!(in(rt.cx()) let mut module = null_mut::<JSObject>());
rooted!(in(*cx) let mut module = null_mut::<JSObject>());
{
let array_buffer = JS::NewArrayBufferWithUserOwnedContents(
rt.cx(),
*cx,
HI_WASM.0.len(),
HI_WASM.0.as_ptr() as _,
);
assert!(!array_buffer.is_null());

rooted!(in(rt.cx()) let val = ObjectValue(array_buffer));
rooted!(in(*cx) let val = ObjectValue(array_buffer));
let args = HandleValueArray::from(val.handle().into_handle());

assert!(Construct1(
rt.cx(),
*cx,
wasm_module.handle(),
&args,
&mut module.handle_mut()
))
}

// Construct Wasm module instance with required imports.
rooted!(in(rt.cx()) let mut instance = null_mut::<JSObject>());
rooted!(in(*cx) let mut instance = null_mut::<JSObject>());
{
// Build "env" imports object.
rooted!(in(rt.cx()) let mut env_import_obj = JS_NewPlainObject(rt.cx()));
rooted!(in(*cx) let mut env_import_obj = JS_NewPlainObject(*cx));
assert!(!env_import_obj.is_null());
let function = JS_DefineFunction(
rt.cx(),
*cx,
env_import_obj.handle().into(),
c"bar".as_ptr(),
Some(bar),
1,
0,
);
assert!(!function.is_null());
rooted!(in(rt.cx()) let mut env_import = ObjectValue(env_import_obj.get()));
rooted!(in(*cx) let mut env_import = ObjectValue(env_import_obj.get()));
// Build imports bag.
rooted!(in(rt.cx()) let mut imports = JS_NewPlainObject(rt.cx()));
rooted!(in(*cx) let mut imports = JS_NewPlainObject(*cx));
assert!(!imports.is_null());
assert!(JS_SetProperty(
rt.cx(),
*cx,
imports.handle(),
c"env".as_ptr(),
env_import.handle()
));

rooted!(in(rt.cx()) let mut args = ValueArray::new([ObjectValue(module.get()), ObjectValue(imports.get())]));
rooted!(in(*cx) let mut args = ValueArray::new([ObjectValue(module.get()), ObjectValue(imports.get())]));

assert!(Construct1(
rt.cx(),
*cx,
wasm_instance.handle(),
&HandleValueArray::from(&args),
&mut instance.handle_mut()
));
}

// Find `foo` method in exports.
rooted!(in(rt.cx()) let mut exports = UndefinedValue());
rooted!(in(*cx) let mut exports = UndefinedValue());

assert!(JS_GetProperty(
rt.cx(),
*cx,
instance.handle(),
c"exports".as_ptr(),
&mut exports.handle_mut()
));

rooted!(in(rt.cx()) let mut exports_obj = exports.to_object());
rooted!(in(rt.cx()) let mut foo = UndefinedValue());
rooted!(in(*cx) let mut exports_obj = exports.to_object());
rooted!(in(*cx) let mut foo = UndefinedValue());
assert!(JS_GetProperty(
rt.cx(),
*cx,
exports_obj.handle(),
c"foo".as_ptr(),
&mut foo.handle_mut()
));

// call foo and get its result
rooted!(in(rt.cx()) let mut rval = UndefinedValue());
rooted!(in(*cx) let mut rval = UndefinedValue());
assert!(Call(
rt.cx(),
*cx,
JS::UndefinedHandleValue,
foo.handle().into(),
&HandleValueArray::empty(),
Expand Down
37 changes: 37 additions & 0 deletions mozjs/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::marker::PhantomData;
use std::ops::Deref;

/// A wrapper for raw JSContext pointers that are strongly associated with
/// the [Runtime] type.
#[derive(Copy, Clone)]
pub struct JSContext<'a> {
pub(crate) raw: *mut crate::jsapi::JSContext,
pub(crate) anchor: PhantomData<&'a ()>,
}

impl<'a> JSContext<'a> {
/// Wrap an existing raw JSContext pointer.
///
/// SAFETY:
/// - cx must point to non-null, valid JSContext object.
/// - the resulting lifetime must not exceed the actual lifetime of the
/// associated JS runtime.
pub unsafe fn from_ptr(cx: *mut crate::jsapi::JSContext) -> JSContext<'a> {
JSContext {
raw: cx,
anchor: PhantomData,
}
}
}

impl<'a> Deref for JSContext<'a> {
type Target = *mut crate::jsapi::JSContext;

fn deref(&self) -> &Self::Target {
&self.raw
}
}
1 change: 1 addition & 0 deletions mozjs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod jsapi {
pub mod rust;

mod consts;
pub mod context;
pub mod conversions;
pub mod error;
pub mod gc;
Expand Down
14 changes: 9 additions & 5 deletions mozjs/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,11 @@ impl Runtime {
}

/// Returns the `JSContext` object.
pub fn cx(&self) -> *mut JSContext {
self.cx
pub fn cx<'a>(&self) -> crate::context::JSContext<'a> {
crate::context::JSContext {
raw: self.cx,
anchor: std::marker::PhantomData,
}
}

pub fn evaluate_script(
Expand All @@ -409,12 +412,13 @@ impl Runtime {
filename, script
);

let _ac = JSAutoRealm::new(self.cx(), glob.get());
let options = unsafe { CompileOptionsWrapper::new(self.cx(), filename, line_num) };
let cx = self.cx();
let _ac = JSAutoRealm::new(*cx, glob.get());
let options = unsafe { CompileOptionsWrapper::new(*cx, filename, line_num) };

unsafe {
let mut source = transform_str_to_source_text(&script);
if !Evaluate2(self.cx(), options.ptr, &mut source, rval.into()) {
if !Evaluate2(*cx, options.ptr, &mut source, rval.into()) {
debug!("...err!");
maybe_resume_unwind();
Err(())
Expand Down
12 changes: 6 additions & 6 deletions mozjs/tests/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ fn callback() {
let context = runtime.cx();
#[cfg(feature = "debugmozjs")]
unsafe {
mozjs::jsapi::SetGCZeal(context, 2, 1);
mozjs::jsapi::SetGCZeal(*context, 2, 1);
}
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
let c_option = RealmOptions::default();

unsafe {
rooted!(in(context) let global = JS_NewGlobalObject(
context,
rooted!(in(*context) let global = JS_NewGlobalObject(
*context,
&SIMPLE_GLOBAL_CLASS,
ptr::null_mut(),
h_option,
&*c_option,
));
let _ac = JSAutoRealm::new(context, global.get());
let _ac = JSAutoRealm::new(*context, global.get());

let function = JS_DefineFunction(
context,
*context,
global.handle().into(),
c"puts".as_ptr(),
Some(puts),
Expand All @@ -46,7 +46,7 @@ fn callback() {
assert!(!function.is_null());

let javascript = "puts('Test Iñtërnâtiônàlizætiøn ┬─┬ノ( º _ ºノ) ');";
rooted!(in(context) let mut rval = UndefinedValue());
rooted!(in(*context) let mut rval = UndefinedValue());
assert!(runtime
.evaluate_script(global.handle(), javascript, "test.js", 0, rval.handle_mut())
.is_ok());
Expand Down
12 changes: 6 additions & 6 deletions mozjs/tests/capture_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,23 @@ fn capture_stack() {
let context = runtime.cx();
#[cfg(feature = "debugmozjs")]
unsafe {
mozjs::jsapi::SetGCZeal(context, 2, 1);
mozjs::jsapi::SetGCZeal(*context, 2, 1);
}
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
let c_option = RealmOptions::default();

unsafe {
rooted!(in(context) let global = JS_NewGlobalObject(
context,
rooted!(in(*context) let global = JS_NewGlobalObject(
*context,
&SIMPLE_GLOBAL_CLASS,
ptr::null_mut(),
h_option,
&*c_option,
));
let _ac = JSAutoRealm::new(context, global.get());
let _ac = JSAutoRealm::new(*context, global.get());

let function = JS_DefineFunction(
context,
*context,
global.handle().into(),
c"print_stack".as_ptr(),
Some(print_stack),
Expand All @@ -71,7 +71,7 @@ fn capture_stack() {
foo(\"arg1-value\");
";
rooted!(in(context) let mut rval = UndefinedValue());
rooted!(in(*context) let mut rval = UndefinedValue());
assert!(runtime
.evaluate_script(global.handle(), javascript, "test.js", 0, rval.handle_mut())
.is_ok());
Expand Down
4 changes: 2 additions & 2 deletions mozjs/tests/custom_auto_rooter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ fn virtual_trace_called() {
let context = runtime.cx();

let mut rooter = CustomAutoRooter::new(TraceCheck::new());
let guard = rooter.root(context);
let guard = rooter.root(*context);

unsafe {
JS_GC(context, GCReason::API);
JS_GC(*context, GCReason::API);
}

assert!(guard.trace_was_called.get());
Expand Down
4 changes: 2 additions & 2 deletions mozjs/tests/custom_auto_rooter_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ fn custom_auto_rooter_macro() {
let runtime = Runtime::new(engine.handle());
let context = runtime.cx();

auto_root!(in(context) let vec = vec![TraceCheck::new(), TraceCheck::new()]);
auto_root!(in(*context) let vec = vec![TraceCheck::new(), TraceCheck::new()]);

unsafe {
JS_GC(context, GCReason::API);
JS_GC(*context, GCReason::API);
}

vec.iter()
Expand Down
Loading

0 comments on commit 3289877

Please sign in to comment.