mozjs/
context.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::marker::PhantomData;
6use std::ptr::NonNull;
7
8pub use crate::jsapi::JSContext as RawJSContext;
9
10/// A wrapper for raw JSContext pointers that are strongly associated with the [Runtime] type.
11///
12/// This type is fundamental for safe SpiderMonkey usage.
13/// Each (SpiderMonkey) function which takes `&mut JSContext` as argument can trigger GC.
14/// SpiderMonkey functions require take `&JSContext` are guaranteed to not trigger GC.
15/// We must not hold any unrooted or borrowed data while calling any functions that can trigger GC.
16/// That can causes panics or UB.
17/// Such types are derived from [NoGC] token which can be though of `&JSContext`,
18/// so they are bounded to [JSContext].
19///
20/// ```rust
21/// use std::marker::PhantomData;
22/// use mozjs::context::*;
23/// use mozjs::jsapi::JSContext as RawJSContext;
24///
25/// struct ShouldNotBeHoldAcrossGC<'a>(PhantomData<&'a ()>);
26///
27/// impl<'a> Drop for ShouldNotBeHoldAcrossGC<'a> {
28///     fn drop(&mut self) {}
29/// }
30///
31/// fn something_that_should_not_hold_across_gc<'a>(_no_gc: &NoGC<'a>) -> ShouldNotBeHoldAcrossGC<'a> {
32///     ShouldNotBeHoldAcrossGC(PhantomData)
33/// }
34///
35/// fn SM_function_that_can_trigger_gc(_cx: *mut RawJSContext) {}
36///
37/// // this lives in mozjs
38/// fn safe_wrapper_to_SM_function_that_can_trigger_gc(cx: &mut JSContext) {
39///     unsafe { SM_function_that_can_trigger_gc(cx.raw_cx()) }
40/// }
41///
42/// fn can_cause_gc(cx: &mut JSContext) {
43///     safe_wrapper_to_SM_function_that_can_trigger_gc(cx);
44///     {
45///         let t = something_that_should_not_hold_across_gc(&cx.no_gc());
46///         // do something with it
47///     } // t get dropped
48///     safe_wrapper_to_SM_function_that_can_trigger_gc(cx); // we can call GC again
49/// }
50/// ```
51///
52/// One cannot call any GC function, while any [NoGC] token is alive,
53/// because [NoGC] token borrows [JSContext] (`&JSContext`)
54/// and thus prevents calling any function that triggers GC,
55/// because they require exclusive access to [JSContext] (`&mut JSContext`).
56///
57/// ```compile_fail
58/// use std::marker::PhantomData;
59/// use mozjs::context::*;
60/// use mozjs::jsapi::JSContext as RawJSContext;
61///
62/// struct ShouldNotBeHoldAcrossGC<'a>(PhantomData<&'a ()>);
63///
64/// impl<'a> Drop for ShouldNotBeHoldAcrossGC<'a> {
65///     fn drop(&mut self) {} // make type not trivial, or else compiler can shorten it's lifetime
66/// }
67///
68/// fn something_that_should_not_hold_across_gc<'a>(_no_gc: &'a NoGC<'a>) -> ShouldNotBeHoldAcrossGC<'a> {
69///     ShouldNotBeHoldAcrossGC(PhantomData)
70/// }
71///
72/// fn safe_wrapper_to_SM_function_that_can_trigger_gc(_cx: &mut JSContext) {
73/// }
74///
75/// fn can_cause_gc(cx: &mut JSContext) {
76///     safe_wrapper_to_SM_function_that_can_trigger_gc(cx);
77///     let t = something_that_should_not_hold_across_gc(&cx.no_gc());
78///     // this will create compile error, because we cannot hold NoGc across C triggering function.
79///     // more specifically we cannot borrow `JSContext` as mutable because it is also borrowed as immutable (NoGC).
80///     safe_wrapper_to_SM_function_that_can_trigger_gc(cx);
81/// }
82/// ```
83///
84/// ### WIP
85///
86/// This model is still being incrementally introduced, so there are currently some escape hatches.
87pub struct JSContext {
88    pub(crate) ptr: NonNull<RawJSContext>,
89}
90
91impl JSContext {
92    /// Wrap an existing [RawJSContext] pointer.
93    ///
94    /// SAFETY:
95    /// - `cx` must be valid [RawJSContext] object.
96    /// - only one [JSContext] can be alive and it should not outlive [Runtime].
97    /// This in turn means that [JSContext] always needs to be passed down as an argument,
98    /// but for the SpiderMonkey callbacks which provide [RawJSContext] it's safe to construct **one** from provided [RawJSContext].
99    pub unsafe fn from_ptr(cx: NonNull<RawJSContext>) -> JSContext {
100        JSContext { ptr: cx }
101    }
102
103    /// Returns [NoGC] token bounded to this [JSContext].
104    /// No function that accepts `&mut JSContext` (read: triggers GC)
105    /// can be called while this is alive.
106    #[inline]
107    #[must_use]
108    pub fn no_gc<'cx>(&'cx self) -> &'cx NoGC<'cx> {
109        &NoGC(PhantomData)
110    }
111
112    /// Obtain [RawJSContext] mutable pointer.
113    ///
114    /// # Safety
115    ///
116    /// No [NoGC] tokens should be constructed while returned pointer is available to user.
117    /// In practices this means that one should use the result
118    /// as direct argument to SpiderMonkey function and not store it in variable.
119    ///
120    /// ```rust
121    /// use mozjs::context::*;
122    /// use mozjs::jsapi::JSContext as RawJSContext;
123    ///
124    /// fn SM_function_that_can_trigger_gc(_cx: *mut RawJSContext) {}
125    ///
126    /// fn can_trigger_gc(cx: &mut JSContext) {
127    ///     unsafe { SM_function_that_can_trigger_gc(cx.raw_cx()) } // returned pointer is immediately used
128    ///     cx.no_gc(); // this is ok because no outstanding raw pointer is alive
129    /// }
130    /// ```
131    pub unsafe fn raw_cx(&mut self) -> *mut RawJSContext {
132        self.ptr.as_ptr()
133    }
134
135    /// Obtain [RawJSContext] mutable pointer, that will not be used for GC.
136    ///
137    /// # Safety
138    ///
139    /// No &mut calls should be done on [JSContext] while returned pointer is available.
140    /// In practices this means that one should use the result
141    /// as direct argument to SpiderMonkey function and not store it in variable.
142    ///
143    /// ```rust
144    /// use mozjs::context::*;
145    /// use mozjs::jsapi::JSContext as RawJSContext;
146    ///
147    /// fn SM_function_that_cannot_trigger_gc(_cx: *mut RawJSContext) {}
148    ///
149    /// fn f(cx: &mut JSContext) {
150    ///     unsafe { SM_function_that_cannot_trigger_gc(cx.raw_cx_no_gc()) } // returned pointer is immediately used
151    /// }
152    /// ```
153    pub unsafe fn raw_cx_no_gc(&self) -> *mut RawJSContext {
154        self.ptr.as_ptr()
155    }
156}
157
158/// Token that ensures that no GC can happen while it is alive.
159///
160/// Each function that trigger GC require mutable access to [JSContext],
161/// so one cannot call them because [NoGC] lifetime is bounded to [JSContext].
162///
163/// For more info and examples see [JSContext].
164pub struct NoGC<'cx>(PhantomData<&'cx ()>);