Skip to content

Commit 7f6380f

Browse files
committed
let's write compile tests in docs
Signed-off-by: sagudev <[email protected]>
1 parent 35bb4cc commit 7f6380f

File tree

4 files changed

+124
-81
lines changed

4 files changed

+124
-81
lines changed

mozjs/Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ cc.workspace = true
4040
bindgen.workspace = true
4141

4242

43-
[dev-dependencies]
44-
trybuild = "1"
45-
46-
4743
[[bench]]
4844
name = "latin1_string_conversion"
4945
harness = false

mozjs/src/context.rs

Lines changed: 124 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,102 @@ use std::marker::PhantomData;
66
use std::ops::Deref;
77
use std::ptr::NonNull;
88

9-
/// A wrapper for raw JSContext pointers that are strongly associated with
10-
/// the [Runtime] type.
9+
pub use crate::jsapi::JSContext as RawJSContext;
10+
11+
/// A wrapper for raw JSContext pointers that are strongly associated with the [Runtime] type.
12+
///
13+
/// This type is fundamental for safe SpiderMonkey usage.
14+
/// Each (SpiderMonkey) function which takes `&mut JSContext` as argument can trigger GC.
15+
/// SpiderMonkey functions require take `&JSContext` are guaranteed to not trigger GC.
16+
/// We must not hold any unrooted or borrowed data while calling any functions that can trigger GC.
17+
/// That can causes panics or UB.
18+
/// Such types are derived from [NoGC] token which can be though of `&JSContext`,
19+
/// so they are bounded to [JSContext].
20+
///
21+
/// ```rust
22+
/// use std::marker::PhantomData;
23+
/// use mozjs::context::*;
24+
/// use mozjs::jsapi::JSContext as RawJSContext;
25+
///
26+
/// struct ShouldNotBeHoldAcrossGC<'a>(PhantomData<&'a ()>);
27+
///
28+
/// impl<'a> Drop for ShouldNotBeHoldAcrossGC<'a> {
29+
/// fn drop(&mut self) {}
30+
/// }
31+
///
32+
/// fn something_that_should_not_hold_across_gc<'a>(_no_gc: &NoGC<'a>) -> ShouldNotBeHoldAcrossGC<'a> {
33+
/// ShouldNotBeHoldAcrossGC(PhantomData)
34+
/// }
35+
///
36+
/// fn SM_function_that_can_trigger_gc(_cx: *mut RawJSContext) {}
37+
///
38+
/// // this lives in mozjs
39+
/// fn safe_wrapper_to_SM_function_that_can_trigger_gc(cx: &mut JSContext) {
40+
/// unsafe { SM_function_that_can_trigger_gc(cx.raw()) }
41+
/// }
42+
///
43+
/// fn can_cause_gc(cx: &mut JSContext) {
44+
/// safe_wrapper_to_SM_function_that_can_trigger_gc(cx);
45+
/// {
46+
/// let t = something_that_should_not_hold_across_gc(&cx.no_gc());
47+
/// // do something with it
48+
/// } // t get dropped
49+
/// safe_wrapper_to_SM_function_that_can_trigger_gc(cx); // we can call GC again
50+
/// }
51+
/// ```
52+
///
53+
/// One cannot call any GC function, while any [NoGC] token is alive,
54+
/// because [NoGC] token borrows [JSContext] (`&JSContext`)
55+
/// and thus prevents calling any function that triggers GC,
56+
/// because they require exclusive access to [JSContext] (`&mut JSContext`).
57+
///
58+
/// ```compile_fail
59+
/// use std::marker::PhantomData;
60+
/// use mozjs::context::*;
61+
/// use mozjs::jsapi::JSContext as RawJSContext;
62+
///
63+
/// struct ShouldNotBeHoldAcrossGC<'a>(PhantomData<&'a ()>);
64+
///
65+
/// impl<'a> Drop for ShouldNotBeHoldAcrossGC<'a> {
66+
/// fn drop(&mut self) {} // make type not trivial, or else compiler can shorten it's lifetime
67+
/// }
68+
///
69+
/// fn something_that_should_not_hold_across_gc<'a>(_no_gc: &'a NoGC<'a>) -> ShouldNotBeHoldAcrossGC<'a> {
70+
/// ShouldNotBeHoldAcrossGC(PhantomData)
71+
/// }
72+
///
73+
/// fn safe_wrapper_to_SM_function_that_can_trigger_gc(_cx: &mut JSContext) {
74+
/// }
75+
///
76+
/// fn can_cause_gc(cx: &mut JSContext) {
77+
/// safe_wrapper_to_SM_function_that_can_trigger_gc(cx);
78+
/// let t = something_that_should_not_hold_across_gc(&cx.no_gc());
79+
/// // this will create compile error, because we cannot hold NoGc across C triggering function.
80+
/// // more specifically we cannot borrow `JSContext` as mutable because it is also borrowed as immutable (NoGC).
81+
/// safe_wrapper_to_SM_function_that_can_trigger_gc(cx);
82+
/// }
83+
/// ```
1184
///
12-
/// Each function that can trigger GC accepts `&mut JSContext`
13-
/// and each value that should not be held across GC is created using [NoGC]
14-
/// which borrows [JSContext] and thus prevents calling any function that triggers GC
15-
/// (because &mut requires exclusive access).
85+
/// ### WIP
86+
///
87+
/// This model is still being incrementally introduced, so there are currently some escape hatches.
1688
#[derive(Copy, Clone)]
1789
pub struct JSContext<'rt> {
18-
pub(crate) ptr: NonNull<crate::jsapi::JSContext>,
90+
pub(crate) ptr: NonNull<RawJSContext>,
1991
pub(crate) runtime_anchor: PhantomData<&'rt ()>,
2092
}
2193

2294
impl<'rt> JSContext<'rt> {
23-
/// Wrap an existing raw JSContext pointer.
95+
/// Wrap an existing [RawJSContext] pointer.
2496
///
2597
/// SAFETY:
26-
/// - cx must be valid JSContext object.
27-
/// - the resulting lifetime must not exceed the actual lifetime of the
28-
/// associated JS runtime.
29-
/// - only one JSContext can be alive (it's safe to construct only one from ptr provided from callbacks, but you are not allowed to make more from thin air)
30-
pub unsafe fn from_ptr(cx: NonNull<mozjs_sys::jsapi::JSContext>) -> JSContext<'rt> {
98+
/// - `cx` must be valid [RawJSContext] object.
99+
/// - the resulting lifetime `'rt` must not exceed the actual lifetime of the
100+
/// associated [Runtime].
101+
/// - only one [JSContext] can be alive.
102+
/// This in turn means that [JSContext] always needs to be passed down as an argument,
103+
/// but for the SpiderMonkey callbacks which provide [RawJSContext] it's safe to construct **one** from provided [RawJSContext].
104+
pub unsafe fn from_ptr(cx: NonNull<RawJSContext>) -> JSContext<'rt> {
31105
JSContext {
32106
ptr: cx,
33107
runtime_anchor: PhantomData,
@@ -39,35 +113,50 @@ impl<'rt> JSContext<'rt> {
39113
/// can be called while this is alive.
40114
#[inline]
41115
#[must_use]
42-
pub fn no_gc<'cx: 'rt>(&'cx self) -> &'cx NoGC<'cx> {
116+
pub fn no_gc<'cx>(&'cx self) -> &'cx NoGC<'cx> {
43117
&NoGC(PhantomData)
44118
}
45-
}
46-
47-
// This will be eventually removed, because it currently breaks safety invariants
48-
impl<'rt> Deref for JSContext<'rt> {
49-
type Target = *mut crate::jsapi::JSContext;
50119

51-
fn deref(&self) -> &Self::Target {
52-
unsafe {
53-
std::mem::transmute::<&NonNull<crate::jsapi::JSContext>, &*mut crate::jsapi::JSContext>(
54-
&self.ptr,
55-
)
56-
}
120+
/// Obtain [RawJSContext] mutable pointer.
121+
///
122+
/// # Safety
123+
///
124+
/// No [NoGC] tokens should be constructed while returned pointer is available to user.
125+
/// In practices this means that one should use the result
126+
/// as direct argument to SpiderMonkey function and not store it in variable.
127+
///
128+
/// ```rust
129+
/// use mozjs::context::*;
130+
/// use mozjs::jsapi::JSContext as RawJSContext;
131+
///
132+
/// fn SM_function_that_can_trigger_gc(_cx: *mut RawJSContext) {}
133+
///
134+
/// fn can_trigger_gc(cx: &mut JSContext) {
135+
/// unsafe { SM_function_that_can_trigger_gc(cx.raw()) } // returned pointer is immediately used
136+
/// cx.no_gc(); // this is ok because no outstanding raw pointer is alive
137+
/// }
138+
/// ```
139+
pub unsafe fn raw(&mut self) -> *mut RawJSContext {
140+
self.ptr.as_ptr()
57141
}
58142
}
59143

60-
/// Token that ensures that no GC can happen while this is alive.
144+
/// Token that ensures that no GC can happen while it is alive.
61145
///
62-
/// Each GC triggering function require mutable access to [JSContext],
63-
/// which cannot be while this exists, because it's lifetime is bounded with [JSContext].
146+
/// Each function that trigger GC require mutable access to [JSContext],
147+
/// so one cannot call them because [NoGC] lifetime is bounded to [JSContext].
148+
///
149+
/// For more info and examples see [JSContext].
64150
pub struct NoGC<'cx>(PhantomData<&'cx ()>);
65151

66-
/// Special case of [JSContext], which can be passed to functions which will not trigger GC, but still take `*mut JSContext`
67-
/// (they usually take `cx: *mut root::JSContext, nogc: *const root::JS::AutoRequireNoGC` like `JS_GetTwoByteStringCharsAndLength`).
68-
///
69-
/// Because they do not trigger GC, this can be alive while holding [NoGC].
70-
pub struct NoGcJSContext<'cx> {
71-
ptr: NonNull<crate::jsapi::JSContext>,
72-
no_gc: PhantomData<&'cx ()>,
152+
impl<'rt> Deref for JSContext<'rt> {
153+
type Target = *mut RawJSContext;
154+
155+
/// This exists to make migration to [JSContext] easier,
156+
/// but it will be eventually removed as it is **unsafe**.
157+
///
158+
/// Use [JSContext::raw] instead.
159+
fn deref(&'_ self) -> &'_ Self::Target {
160+
unsafe { std::mem::transmute::<&NonNull<RawJSContext>, &*mut RawJSContext>(&self.ptr) }
161+
}
73162
}

mozjs/tests/compile_tests.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.

mozjs/tests/compile_tests/pass/cx.rs

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)