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> AsMut<JSContext> for AutoRealm<'cx> {
175 fn as_mut(&mut self) -> &mut JSContext {
176 &mut self.cx
177 }
178}
179
180impl<'cx> Deref for AutoRealm<'cx> {
181 type Target = JSContext;
182
183 fn deref(&'_ self) -> &'_ Self::Target {
184 &self.cx
185 }
186}
187
188impl<'cx> DerefMut for AutoRealm<'cx> {
189 fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
190 &mut self.cx
191 }
192}
193
194impl<'cx> Drop for AutoRealm<'cx> {
195 // If we do not implement this, Rust can end the borrow of [JSContext]
196 // early while [JSAutoRealm] is still active. Since [JSAutoRealm]
197 // internally holds a raw pointer to the context, we must ensure the
198 // cx borrow remains bounded until the guard is dropped to not
199 // use after free and ensure strict LIFO realm entry/exit.
200 // See <https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=af3fe88460cd3bd0a9db71f1e6ac376b>
201 fn drop(&mut self) {}
202}
203
204/// Represents the current realm of [JSContext] (top realm on realm stack).
205///
206/// Similarly to [AutoRealm], while you can access this type via `&mut`/`&mut`
207/// we know that this realm is current (on top of realm stack).
208///
209/// ```compile_fail
210/// use mozjs::context::JSContext;
211/// use mozjs::jsapi::JSObject;
212/// use mozjs::realm::{AutoRealm, CurrentRealm};
213/// use std::ptr::NonNull;
214///
215/// fn f(current_realm: &mut CurrentRealm, target: NonNull<JSObject>) {
216/// let mut realm = AutoRealm::new(current_realm, target);
217/// let cx: &mut JSContext = &mut *current_realm; // we cannot use current realm while it's not current
218/// }
219/// ```
220pub struct CurrentRealm<'cx> {
221 cx: &'cx mut JSContext,
222 realm: NonNull<Realm>,
223}
224
225impl<'cx> CurrentRealm<'cx> {
226 /// Asserts that the current realm is valid and returns it.
227 pub fn assert(cx: &'cx mut JSContext) -> CurrentRealm<'cx> {
228 let realm = unsafe { GetCurrentRealmOrNull(cx) };
229 CurrentRealm {
230 cx,
231 realm: NonNull::new(realm).unwrap(),
232 }
233 }
234
235 /// Obtain the handle to the global object of the this realm.
236 /// Because the handle is bounded with lifetime to realm, you cannot do this:
237 ///
238 /// ```compile_fail
239 /// use mozjs::context::JSContext;
240 /// use mozjs::jsapi::JSObject;
241 /// use mozjs::realm::CurrentRealm;
242 /// use std::ptr::NonNull;
243 /// use mozjs::rust::Handle;
244 ///
245 /// fn g(realm: &'_ mut CurrentRealm, global: Handle<'_, *mut JSObject>) {
246 /// }
247 ///
248 /// fn f(realm: &mut CurrentRealm) {
249 /// let global = realm.global();
250 /// g(realm, global);
251 /// }
252 /// ```
253 ///
254 /// instead use [CurrentRealm::global_and_reborrow].
255 pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
256 // SAFETY: object is rooted by realm
257 unsafe { Handle::from_marked_location(CurrentGlobal(self)) }
258 }
259
260 /// Obtain the handle to the global object of this realm and reborrow the realm.
261 ///
262 /// ```
263 /// use mozjs::context::JSContext;
264 /// use mozjs::jsapi::JSObject;
265 /// use mozjs::realm::CurrentRealm;
266 /// use std::ptr::NonNull;
267 /// use mozjs::rust::Handle;
268 ///
269 /// fn g(realm: &'_ mut CurrentRealm, global: Handle<'_, *mut JSObject>) {
270 /// }
271 ///
272 /// fn f(realm: &mut CurrentRealm) {
273 /// let (global, realm) = realm.global_and_reborrow();
274 /// g(realm, global);
275 /// }
276 /// ```
277 pub fn global_and_reborrow(&'_ mut self) -> (Handle<'_, *mut JSObject>, &'_ mut Self) {
278 // SAFETY: This is ok because the handle will still be bound to original lifetime.
279 (unsafe { std::mem::transmute(self.global()) }, self)
280 }
281
282 pub fn realm(&self) -> &NonNull<Realm> {
283 &self.realm
284 }
285}
286
287impl<'cx> AsMut<JSContext> for CurrentRealm<'cx> {
288 fn as_mut(&mut self) -> &mut JSContext {
289 &mut self.cx
290 }
291}
292
293impl<'cx> Deref for CurrentRealm<'cx> {
294 type Target = JSContext;
295
296 fn deref(&'_ self) -> &'_ Self::Target {
297 &self.cx
298 }
299}
300
301impl<'cx> DerefMut for CurrentRealm<'cx> {
302 fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
303 &mut self.cx
304 }
305}