Skip to content

Commit e6f40eb

Browse files
authored
Add safe JSContext wrapper with strict safety rules (#638)
* squash Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> * run doc test on linux Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
1 parent 3d9402d commit e6f40eb

33 files changed

+1481
-312
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
- uses: actions/checkout@v4
3939
- uses: actions/setup-python@v6
4040
with:
41-
python-version: '3.13'
41+
python-version: "3.13"
4242
- name: Install deps
4343
run: |
4444
brew install llvm yasm
@@ -90,9 +90,11 @@ jobs:
9090
- name: Run sccache-cache
9191
uses: mozilla-actions/sccache-action@v0.0.9
9292
- name: Build
93+
# doc tests on mozjs_sys fail because they include stuff from SpiderMonkey
9394
run: |
9495
cargo +${{ steps.toolchain.outputs.name }} build --verbose --features ${{ matrix.features }}
9596
cargo +${{ steps.toolchain.outputs.name }} test --tests --examples --verbose --features ${{ matrix.features }}
97+
cargo +${{ steps.toolchain.outputs.name }} test --doc -p mozjs --verbose --features ${{ matrix.features }}
9698
- name: Check wrappers integrity
9799
# we generate wrappers only without debugmozjs
98100
if: ${{ matrix.features != 'debugmozjs' }}

mozjs/benches/latin1_string_conversion.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@ use criterion::measurement::WallTime;
22
use criterion::{
33
criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
44
};
5+
use mozjs::context::JSContext;
56
use mozjs::conversions::jsstr_to_string;
67
use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps};
7-
use mozjs::jsapi::{
8-
JSAutoRealm, JS_NewExternalStringLatin1, JS_NewGlobalObject, OnNewGlobalHookOption,
9-
};
8+
use mozjs::jsapi::{JSAutoRealm, OnNewGlobalHookOption};
109
use mozjs::rooted;
10+
use mozjs::rust::wrappers2::{JS_NewExternalStringLatin1, JS_NewGlobalObject};
1111
use mozjs::rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS};
12-
use mozjs_sys::jsapi::JSContext;
1312
use std::ffi::c_void;
13+
use std::ptr::NonNull;
1414
use std::{iter, ptr};
1515

1616
// TODO: Create a trait for creating a latin1 string of a required length, so that we can
1717
// try different kinds of content.
1818
fn bench_str_repetition(
1919
group: &mut BenchmarkGroup<WallTime>,
20-
context: *mut JSContext,
20+
context: &mut JSContext,
2121
variant_name: &str,
2222
latin1str_16_bytes: &[u8],
2323
) {
@@ -39,7 +39,7 @@ fn bench_str_repetition(
3939
str_len as *mut c_void,
4040
)
4141
};
42-
rooted!(in(context) let latin1_jsstr = unsafe { JS_NewExternalStringLatin1(
42+
rooted!(&in(context) let latin1_jsstr = unsafe { JS_NewExternalStringLatin1(
4343
context,
4444
latin1_chars,
4545
str_len,
@@ -51,26 +51,28 @@ fn bench_str_repetition(
5151
&latin1_jsstr,
5252
|b, js_str| {
5353
b.iter(|| {
54-
unsafe { jsstr_to_string(context, js_str.get()) };
54+
unsafe {
55+
jsstr_to_string(context.raw_cx(), NonNull::new(js_str.get()).unwrap())
56+
};
5557
})
5658
},
5759
);
5860
}
5961
}
6062
fn external_string(c: &mut Criterion) {
6163
let engine = JSEngine::init().unwrap();
62-
let runtime = Runtime::new(engine.handle());
64+
let mut runtime = Runtime::new(engine.handle());
6365
let context = runtime.cx();
6466
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
6567
let c_option = RealmOptions::default();
66-
rooted!(in(context) let global = unsafe { JS_NewGlobalObject(
68+
rooted!(&in(context) let global = unsafe { JS_NewGlobalObject(
6769
context,
6870
&SIMPLE_GLOBAL_CLASS,
6971
ptr::null_mut(),
7072
h_option,
7173
&*c_option,
7274
)});
73-
let _ac = JSAutoRealm::new(context, global.get());
75+
let _ac = JSAutoRealm::new(unsafe { context.raw_cx() }, global.get());
7476

7577
let mut group = c.benchmark_group("Latin1 conversion");
7678

mozjs/examples/eval.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@
1515
1616
use ::std::ptr;
1717

18-
use mozjs::jsapi::*;
18+
use mozjs::jsapi::OnNewGlobalHookOption;
1919
use mozjs::jsval::UndefinedValue;
2020
use mozjs::rooted;
21+
use mozjs::rust::wrappers2::*;
2122
use mozjs::rust::SIMPLE_GLOBAL_CLASS;
2223
use mozjs::rust::{JSEngine, RealmOptions, Runtime};
2324

24-
fn run(rt: Runtime) {
25+
fn run(mut rt: Runtime) {
2526
let options = RealmOptions::default();
26-
rooted!(in(rt.cx()) let global = unsafe {
27+
rooted!(&in(rt.cx()) let global = unsafe {
2728
JS_NewGlobalObject(rt.cx(), &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
2829
OnNewGlobalHookOption::FireOnNewGlobalHook,
2930
&*options)
@@ -37,7 +38,7 @@ fn run(rt: Runtime) {
3738
* The return value comes back here. If it could be a GC thing, you must add it to the
3839
* GC's "root set" with the rooted! macro.
3940
*/
40-
rooted!(in(rt.cx()) let mut rval = UndefinedValue());
41+
rooted!(&in(rt.cx()) let mut rval = UndefinedValue());
4142

4243
/*
4344
* Some example source in a string. This is equivalent to JS_EvaluateScript in C++.
@@ -57,7 +58,6 @@ fn run(rt: Runtime) {
5758
fn main() {
5859
let engine = JSEngine::init().expect("failed to initalize JS engine");
5960
let runtime = Runtime::new(engine.handle());
60-
assert!(!runtime.cx().is_null(), "failed to create JSContext");
6161
run(runtime);
6262
}
6363

mozjs/examples/minimal.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ::std::ptr;
1717

1818
use mozjs::rooted;
19+
use mozjs::rust::wrappers2::JS_NewGlobalObject;
1920
use mozjs::rust::SIMPLE_GLOBAL_CLASS;
2021
use mozjs::{jsapi::*, rust::JSEngine, rust::RealmOptions, rust::Runtime};
2122

@@ -25,22 +26,21 @@ fn main() {
2526

2627
// Create a Runtime -- wraps a JSContext in the C++ API.
2728
let runtime = Runtime::new(engine.handle());
28-
assert!(!runtime.cx().is_null(), "failed to create JSContext");
2929

3030
run(runtime);
3131

3232
// There is no need for the shut down block in the C++, because rust destructors and Arc
3333
// reference counts will clean up everything.
3434
}
3535

36-
fn run(rt: Runtime) {
36+
fn run(mut rt: Runtime) {
3737
let cx = rt.cx();
3838
// In addition to what the C++ interface requires, define a global scope for the code.
3939
//
4040
// This demonstrates the way Rust uses the C++ garbage collector: using the rooted! macro to
4141
// indicate when the GC can collect them.
4242
let options = RealmOptions::default();
43-
rooted!(in(cx) let _global = unsafe {
43+
rooted!(&in(cx) let _global = unsafe {
4444
JS_NewGlobalObject(cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
4545
OnNewGlobalHookOption::FireOnNewGlobalHook,
4646
&*options)

mozjs/examples/wasm.rs

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ use mozjs::jsapi::*;
1616
use mozjs::jsval::ObjectValue;
1717
use mozjs::jsval::UndefinedValue;
1818
use mozjs::rooted;
19-
use mozjs::rust::wrappers::{Construct1, JS_GetProperty, JS_SetProperty};
19+
use mozjs::rust::wrappers2::{
20+
Call, Construct1, JS_DefineFunction, JS_GetProperty, JS_NewGlobalObject, JS_NewPlainObject,
21+
JS_SetProperty, NewArrayBufferWithUserOwnedContents,
22+
};
2023
use mozjs::rust::SIMPLE_GLOBAL_CLASS;
21-
use mozjs::rust::{IntoHandle, JSEngine, RealmOptions, Runtime};
24+
use mozjs::rust::{HandleValue, IntoHandle, JSEngine, RealmOptions, Runtime};
2225
use mozjs_sys::jsgc::ValueArray;
2326

2427
#[repr(align(8))]
@@ -47,36 +50,37 @@ unsafe extern "C" fn bar(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool
4750
true
4851
}
4952

50-
fn run(rt: Runtime) {
53+
fn run(mut rt: Runtime) {
5154
let options = RealmOptions::default();
52-
rooted!(in(rt.cx()) let global = unsafe {
53-
JS_NewGlobalObject(rt.cx(), &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
55+
let cx = rt.cx();
56+
rooted!(&in(cx) let global = unsafe {
57+
JS_NewGlobalObject(cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
5458
OnNewGlobalHookOption::FireOnNewGlobalHook,
5559
&*options)
5660
});
57-
let _ac = JSAutoRealm::new(rt.cx(), global.get());
61+
let _ac = JSAutoRealm::new(unsafe { cx.raw_cx() }, global.get());
5862

5963
// Get WebAssembly.Module and WebAssembly.Instance constructors.
60-
rooted!(in(rt.cx()) let mut wasm = UndefinedValue());
61-
rooted!(in(rt.cx()) let mut wasm_module = UndefinedValue());
62-
rooted!(in(rt.cx()) let mut wasm_instance = UndefinedValue());
64+
rooted!(&in(cx) let mut wasm = UndefinedValue());
65+
rooted!(&in(cx) let mut wasm_module = UndefinedValue());
66+
rooted!(&in(cx) let mut wasm_instance = UndefinedValue());
6367

6468
unsafe {
6569
assert!(JS_GetProperty(
66-
rt.cx(),
70+
cx,
6771
global.handle(),
6872
c"WebAssembly".as_ptr(),
6973
wasm.handle_mut()
7074
));
71-
rooted!(in(rt.cx()) let mut wasm_obj = wasm.to_object());
75+
rooted!(&in(cx) let mut wasm_obj = wasm.to_object());
7276
assert!(JS_GetProperty(
73-
rt.cx(),
77+
cx,
7478
wasm_obj.handle(),
7579
c"Module".as_ptr(),
7680
wasm_module.handle_mut()
7781
));
7882
assert!(JS_GetProperty(
79-
rt.cx(),
83+
cx,
8084
wasm_obj.handle(),
8185
c"Instance".as_ptr(),
8286
wasm_instance.handle_mut()
@@ -86,86 +90,83 @@ fn run(rt: Runtime) {
8690
assert!(HI_WASM.0.as_ptr() as usize % 8 == 0);
8791

8892
// Construct Wasm module from bytes.
89-
rooted!(in(rt.cx()) let mut module = null_mut::<JSObject>());
93+
rooted!(&in(cx) let mut module = null_mut::<JSObject>());
9094
{
91-
let array_buffer = JS::NewArrayBufferWithUserOwnedContents(
92-
rt.cx(),
93-
HI_WASM.0.len(),
94-
HI_WASM.0.as_ptr() as _,
95-
);
95+
let array_buffer =
96+
NewArrayBufferWithUserOwnedContents(cx, HI_WASM.0.len(), HI_WASM.0.as_ptr() as _);
9697
assert!(!array_buffer.is_null());
9798

98-
rooted!(in(rt.cx()) let val = ObjectValue(array_buffer));
99+
rooted!(&in(cx) let val = ObjectValue(array_buffer));
99100
let args = HandleValueArray::from(val.handle().into_handle());
100101

101102
assert!(Construct1(
102-
rt.cx(),
103+
cx,
103104
wasm_module.handle(),
104105
&args,
105106
module.handle_mut()
106107
))
107108
}
108109

109110
// Construct Wasm module instance with required imports.
110-
rooted!(in(rt.cx()) let mut instance = null_mut::<JSObject>());
111+
rooted!(&in(cx) let mut instance = null_mut::<JSObject>());
111112
{
112113
// Build "env" imports object.
113-
rooted!(in(rt.cx()) let mut env_import_obj = JS_NewPlainObject(rt.cx()));
114+
rooted!(&in(cx) let mut env_import_obj = JS_NewPlainObject(cx));
114115
assert!(!env_import_obj.is_null());
115116
let function = JS_DefineFunction(
116-
rt.cx(),
117+
cx,
117118
env_import_obj.handle().into(),
118119
c"bar".as_ptr(),
119120
Some(bar),
120121
1,
121122
0,
122123
);
123124
assert!(!function.is_null());
124-
rooted!(in(rt.cx()) let mut env_import = ObjectValue(env_import_obj.get()));
125+
rooted!(&in(cx) let mut env_import = ObjectValue(env_import_obj.get()));
125126
// Build imports bag.
126-
rooted!(in(rt.cx()) let mut imports = JS_NewPlainObject(rt.cx()));
127+
rooted!(&in(cx) let mut imports = JS_NewPlainObject(cx));
127128
assert!(!imports.is_null());
128129
assert!(JS_SetProperty(
129-
rt.cx(),
130+
cx,
130131
imports.handle(),
131132
c"env".as_ptr(),
132133
env_import.handle()
133134
));
134135

135-
rooted!(in(rt.cx()) let mut args = ValueArray::new([ObjectValue(module.get()), ObjectValue(imports.get())]));
136+
rooted!(&in(cx) let mut args = ValueArray::new([ObjectValue(module.get()), ObjectValue(imports.get())]));
136137

137138
assert!(Construct1(
138-
rt.cx(),
139+
cx,
139140
wasm_instance.handle(),
140141
&HandleValueArray::from(&args),
141142
instance.handle_mut()
142143
));
143144
}
144145

145146
// Find `foo` method in exports.
146-
rooted!(in(rt.cx()) let mut exports = UndefinedValue());
147+
rooted!(&in(cx) let mut exports = UndefinedValue());
147148

148149
assert!(JS_GetProperty(
149-
rt.cx(),
150+
cx,
150151
instance.handle(),
151152
c"exports".as_ptr(),
152153
exports.handle_mut()
153154
));
154155

155-
rooted!(in(rt.cx()) let mut exports_obj = exports.to_object());
156-
rooted!(in(rt.cx()) let mut foo = UndefinedValue());
156+
rooted!(&in(cx) let mut exports_obj = exports.to_object());
157+
rooted!(&in(cx) let mut foo = UndefinedValue());
157158
assert!(JS_GetProperty(
158-
rt.cx(),
159+
cx,
159160
exports_obj.handle(),
160161
c"foo".as_ptr(),
161162
foo.handle_mut()
162163
));
163164

164165
// call foo and get its result
165-
rooted!(in(rt.cx()) let mut rval = UndefinedValue());
166+
rooted!(&in(cx) let mut rval = UndefinedValue());
166167
assert!(Call(
167-
rt.cx(),
168-
JS::UndefinedHandleValue,
168+
cx,
169+
HandleValue::undefined(),
169170
foo.handle().into(),
170171
&HandleValueArray::empty(),
171172
rval.handle_mut().into()
@@ -180,7 +181,6 @@ fn run(rt: Runtime) {
180181
fn main() {
181182
let engine = JSEngine::init().expect("failed to initalize JS engine");
182183
let runtime = Runtime::new(engine.handle());
183-
assert!(!runtime.cx().is_null(), "failed to create JSContext");
184184
run(runtime);
185185
}
186186

0 commit comments

Comments
 (0)