mozjs/
realm.rs

1use std::marker::PhantomData;
2use std::ops::{Deref, DerefMut};
3use std::ptr::NonNull;
4
5use crate::jsapi::JS::Realm;
6use crate::jsapi::{JSAutoRealm, JSObject};
7
8use crate::context::JSContext;
9use crate::gc::Handle;
10use crate::rust::wrappers2::{CurrentGlobal, GetCurrentRealmOrNull};
11
12/// Safe wrapper around [JSAutoRealm].
13///
14/// On creation it enters the realm of the target object,
15/// realm becomes current (it's on top of the realm stack).
16/// Drop exits realm.
17///
18/// While creating [AutoRealm] will not trigger GC,
19/// it still takes `&mut JSContext`, because it can be used in place of [JSContext] (by [Deref]/[DerefMut]).
20/// with additional information of entered/current realm:
21/// ```compile_fail
22/// use mozjs::context::JSContext;
23/// use mozjs::jsapi::JSObject;
24/// use mozjs::realm::AutoRealm;
25/// use std::ptr::NonNull;
26///
27/// fn f(cx: &mut JSContext, target: NonNull<JSObject>) {
28///     let realm = AutoRealm::new(cx, target);
29///     f(cx, target); // one cannot use JSContext here,
30///                   // because that could allow out of order realm drops.
31/// }
32/// ```
33/// instead do this:
34/// ```
35/// use mozjs::context::JSContext;
36/// use mozjs::jsapi::JSObject;
37/// use mozjs::realm::AutoRealm;
38/// use std::ptr::NonNull;
39///
40/// fn f(cx: &mut JSContext, target: NonNull<JSObject>) {
41///     let mut realm = AutoRealm::new(cx, target);
42///     let cx = &mut realm; // this JSContext is bounded to AutoRealm
43///                              // which in turn is bounded to original JSContext
44///     f(cx, target);
45/// }
46/// ```
47///
48/// This also enforces LIFO entering/exiting realms, which is not enforced by [JSAutoRealm]:
49/// ```compile_fail
50/// use mozjs::context::JSContext;
51/// use mozjs::jsapi::JSObject;
52/// use mozjs::realm::AutoRealm;
53/// use std::ptr::NonNull;
54///
55/// fn f(cx: &mut JSContext, t1: NonNull<JSObject>, t2: NonNull<JSObject>) {
56///     let mut realm1 = AutoRealm::new(cx, t1);
57///     let cx = &mut realm1;
58///     let realm2 = AutoRealm::new(cx, t2);
59///     drop(realm1); // it's not possible to drop realm1 before realm2
60/// }
61/// ```
62pub struct AutoRealm<'cx> {
63    cx: JSContext,
64    realm: JSAutoRealm,
65    phantom: PhantomData<&'cx mut ()>,
66}
67
68impl<'cx> AutoRealm<'cx> {
69    /// Enters the realm of the given target object.
70    /// The realm becomes the current realm (it's on top of the realm stack).
71    /// The realm is exited when the [AutoRealm] is dropped.
72    ///
73    /// While this function will not trigger GC (it will in fact root the object)
74    /// but because [AutoRealm] can act as a [JSContext] we need to take `&mut JSContext`.
75    pub fn new(cx: &'cx mut JSContext, target: NonNull<JSObject>) -> AutoRealm<'cx> {
76        let realm = JSAutoRealm::new(unsafe { cx.raw_cx_no_gc() }, target.as_ptr());
77        AutoRealm {
78            cx: unsafe { JSContext::from_ptr(NonNull::new_unchecked(cx.raw_cx())) },
79            realm,
80            phantom: PhantomData,
81        }
82    }
83
84    /// Enters the realm of the given target object.
85    /// The realm becomes the current realm (it's on top of the realm stack).
86    /// The realm is exited when the [AutoRealm] is dropped.
87    ///
88    /// While this function will not trigger GC (it will in fact root the object)
89    /// but because [AutoRealm] can act as a [JSContext] we need to take `&mut JSContext`.
90    pub fn new_from_handle(
91        cx: &'cx mut JSContext,
92        target: Handle<*mut JSObject>,
93    ) -> AutoRealm<'cx> {
94        Self::new(cx, NonNull::new(target.get()).unwrap())
95    }
96
97    /// If we can get &mut AutoRealm then we are current realm,
98    /// because if there existed other current realm, we couldn't get &mut AutoRealm.
99    /// ```
100    /// use mozjs::context::JSContext;
101    /// use mozjs::jsapi::JSObject;
102    /// use mozjs::realm::AutoRealm;
103    /// use std::ptr::NonNull;
104    ///
105    /// fn f(cx: &mut JSContext, t: NonNull<JSObject>) {
106    ///     let mut realm = AutoRealm::new(cx, t);
107    ///     let mut current_realm = realm.current_realm();
108    /// }
109    /// ```
110    pub fn current_realm(&'_ mut self) -> CurrentRealm<'_> {
111        CurrentRealm::assert(self)
112    }
113
114    /// Obtain the handle to the global object of the this realm.
115    /// Because the handle is bounded with lifetime to realm, you cannot do this:
116    ///
117    /// ```compile_fail
118    /// use mozjs::context::JSContext;
119    /// use mozjs::jsapi::JSObject;
120    /// use mozjs::realm::AutoRealm;
121    /// use std::ptr::NonNull;
122    /// use mozjs::rust::Handle;
123    ///
124    /// fn g(realm: &'_ mut AutoRealm, global: Handle<'_, *mut JSObject>) {
125    /// }
126    ///
127    /// fn f(realm: &mut AutoRealm) {
128    ///     let global = realm.global();
129    ///     g(realm, global);
130    /// }
131    /// ```
132    ///
133    /// instead use [AutoRealm::global_and_reborrow].
134    pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
135        // SAFETY: object is rooted by realm
136        unsafe { Handle::from_marked_location(CurrentGlobal(self)) }
137    }
138
139    /// Obtain the handle to the global object of the this realm and reborrow the realm.
140    ///
141    /// ```
142    /// use mozjs::context::JSContext;
143    /// use mozjs::jsapi::JSObject;
144    /// use mozjs::realm::AutoRealm;
145    /// use std::ptr::NonNull;
146    /// use mozjs::rust::Handle;
147    ///
148    /// fn g(realm: &'_ mut AutoRealm, global: Handle<'_, *mut JSObject>) {
149    /// }
150    ///
151    /// fn f(realm: &mut AutoRealm) {
152    ///     let (global, realm) = realm.global_and_reborrow();
153    ///     g(realm, global);
154    /// }
155    /// ```
156    pub fn global_and_reborrow(&'_ mut self) -> (Handle<'_, *mut JSObject>, &'_ mut Self) {
157        // SAFETY: This is ok because the handle will still be bound to original lifetime.
158        (unsafe { std::mem::transmute(self.global()) }, self)
159    }
160
161    /// Erase the lifetime of this [AutoRealm].
162    ///
163    /// # Safety
164    /// - The caller must ensure that the [AutoRealm] does not outlive the [JSContext] it was created with.
165    pub unsafe fn erase_lifetime(self) -> AutoRealm<'static> {
166        std::mem::transmute(self)
167    }
168
169    pub fn realm(&self) -> &JSAutoRealm {
170        &self.realm
171    }
172}
173
174impl<'cx> Deref for AutoRealm<'cx> {
175    type Target = JSContext;
176
177    fn deref(&'_ self) -> &'_ Self::Target {
178        &self.cx
179    }
180}
181
182impl<'cx> DerefMut for AutoRealm<'cx> {
183    fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
184        &mut self.cx
185    }
186}
187
188impl<'cx> Drop for AutoRealm<'cx> {
189    // If we do not implement this, Rust can end the borrow of [JSContext]
190    // early while [JSAutoRealm] is still active. Since [JSAutoRealm]
191    // internally holds a raw pointer to the context, we must ensure the
192    // cx borrow remains bounded until the guard is dropped to not
193    // use after free and ensure strict LIFO realm entry/exit.
194    // See <https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=af3fe88460cd3bd0a9db71f1e6ac376b>
195    fn drop(&mut self) {}
196}
197
198/// Represents the current realm of [JSContext] (top realm on realm stack).
199///
200/// Similarly to [AutoRealm], while you can access this type via `&mut`/`&mut`
201/// we know that this realm is current (on top of realm stack).
202///
203/// ```compile_fail
204/// use mozjs::context::JSContext;
205/// use mozjs::jsapi::JSObject;
206/// use mozjs::realm::{AutoRealm, CurrentRealm};
207/// use std::ptr::NonNull;
208///
209/// fn f(current_realm: &mut CurrentRealm, target: NonNull<JSObject>) {
210///     let mut realm = AutoRealm::new(current_realm, target);
211///     let cx: &mut JSContext = &mut *current_realm; // we cannot use current realm while it's not current
212/// }
213/// ```
214pub struct CurrentRealm<'cx> {
215    cx: &'cx mut JSContext,
216    realm: NonNull<Realm>,
217}
218
219impl<'cx> CurrentRealm<'cx> {
220    /// Asserts that the current realm is valid and returns it.
221    pub fn assert(cx: &'cx mut JSContext) -> CurrentRealm<'cx> {
222        let realm = unsafe { GetCurrentRealmOrNull(cx) };
223        CurrentRealm {
224            cx,
225            realm: NonNull::new(realm).unwrap(),
226        }
227    }
228
229    /// Obtain the handle to the global object of the this realm.
230    /// Because the handle is bounded with lifetime to realm, you cannot do this:
231    ///
232    /// ```compile_fail
233    /// use mozjs::context::JSContext;
234    /// use mozjs::jsapi::JSObject;
235    /// use mozjs::realm::CurrentRealm;
236    /// use std::ptr::NonNull;
237    /// use mozjs::rust::Handle;
238    ///
239    /// fn g(realm: &'_ mut CurrentRealm, global: Handle<'_, *mut JSObject>) {
240    /// }
241    ///
242    /// fn f(realm: &mut CurrentRealm) {
243    ///     let global = realm.global();
244    ///     g(realm, global);
245    /// }
246    /// ```
247    ///
248    /// instead use [CurrentRealm::global_and_reborrow].
249    pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
250        // SAFETY: object is rooted by realm
251        unsafe { Handle::from_marked_location(CurrentGlobal(self)) }
252    }
253
254    /// Obtain the handle to the global object of this realm and reborrow the realm.
255    ///
256    /// ```
257    /// use mozjs::context::JSContext;
258    /// use mozjs::jsapi::JSObject;
259    /// use mozjs::realm::CurrentRealm;
260    /// use std::ptr::NonNull;
261    /// use mozjs::rust::Handle;
262    ///
263    /// fn g(realm: &'_ mut CurrentRealm, global: Handle<'_, *mut JSObject>) {
264    /// }
265    ///
266    /// fn f(realm: &mut CurrentRealm) {
267    ///     let (global, realm) = realm.global_and_reborrow();
268    ///     g(realm, global);
269    /// }
270    /// ```
271    pub fn global_and_reborrow(&'_ mut self) -> (Handle<'_, *mut JSObject>, &'_ mut Self) {
272        // SAFETY: This is ok because the handle will still be bound to original lifetime.
273        (unsafe { std::mem::transmute(self.global()) }, self)
274    }
275
276    pub fn realm(&self) -> &NonNull<Realm> {
277        &self.realm
278    }
279}
280
281impl<'cx> Deref for CurrentRealm<'cx> {
282    type Target = JSContext;
283
284    fn deref(&'_ self) -> &'_ Self::Target {
285        &self.cx
286    }
287}
288
289impl<'cx> DerefMut for CurrentRealm<'cx> {
290    fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
291        &mut self.cx
292    }
293}