Skip to main content

script_bindings/
root.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
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::UnsafeCell;
6use std::hash::{Hash, Hasher};
7use std::ops::Deref;
8use std::rc::Rc;
9use std::{fmt, mem, ptr};
10
11use js::gc::{Handle, Traceable as JSTraceable};
12use js::jsapi::{Heap, JSObject, JSTracer};
13use js::rust::GCMethods;
14use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
15
16use crate::assert::assert_in_script;
17use crate::conversions::DerivedFrom;
18use crate::inheritance::Castable;
19use crate::reflector::{DomObject, MutDomObject, Reflector};
20use crate::trace::trace_reflector;
21
22/// A rooted value.
23#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]
24pub struct Root<T: StableTraceObject> {
25    /// The value to root.
26    value: T,
27    /// List that ensures correct dynamic root ordering
28    root_list: *const RootCollection,
29}
30
31impl<T> Root<T>
32where
33    T: StableTraceObject + 'static,
34{
35    /// Create a new stack-bounded root for the provided value.
36    /// It gives out references which cannot outlive this new `Root`.
37    ///
38    /// # Safety
39    /// It must not outlive its associated `RootCollection`.
40    pub unsafe fn new(value: T) -> Self {
41        unsafe fn add_to_root_list(object: *const dyn JSTraceable) -> *const RootCollection {
42            assert_in_script();
43            STACK_ROOTS.with(|root_list| {
44                unsafe { root_list.root(object) };
45                root_list as *const _
46            })
47        }
48
49        let root_list = unsafe { add_to_root_list(value.stable_trace_object()) };
50        Root { value, root_list }
51    }
52}
53
54/// `StableTraceObject` represents values that can be rooted through a stable address that will
55/// not change for their whole lifetime.
56/// It is an unsafe trait that requires implementors to ensure certain safety guarantees.
57///
58/// # Safety
59///
60/// Implementors of this trait must ensure that the `trace` method correctly accounts for all
61/// owned and referenced objects, so that the garbage collector can accurately determine which
62/// objects are still in use. Failing to adhere to this contract may result in undefined behavior,
63/// such as use-after-free errors.
64pub unsafe trait StableTraceObject {
65    /// Returns a stable trace object which address won't change for the whole
66    /// lifetime of the value.
67    fn stable_trace_object(&self) -> *const dyn JSTraceable;
68}
69
70unsafe impl<T> StableTraceObject for Dom<T>
71where
72    T: DomObject,
73{
74    fn stable_trace_object(&self) -> *const dyn JSTraceable {
75        // The JSTraceable impl for Reflector doesn't actually do anything,
76        // so we need this shenanigan to actually trace the reflector of the
77        // T pointer in Dom<T>.
78        #[cfg_attr(crown, expect(crown::unrooted_must_root))]
79        struct ReflectorStackRoot<T>(Reflector<T>);
80        unsafe impl<T> JSTraceable for ReflectorStackRoot<T> {
81            unsafe fn trace(&self, tracer: *mut JSTracer) {
82                unsafe { trace_reflector(tracer, "on stack", &self.0) };
83            }
84        }
85        unsafe {
86            &*(self.reflector() as *const Reflector<T::ReflectorType>
87                as *const ReflectorStackRoot<T::ReflectorType>)
88        }
89    }
90}
91
92unsafe impl<T> StableTraceObject for MaybeUnreflectedDom<T>
93where
94    T: DomObject,
95{
96    fn stable_trace_object(&self) -> *const dyn JSTraceable {
97        // The JSTraceable impl for Reflector doesn't actually do anything,
98        // so we need this shenanigan to actually trace the reflector of the
99        // T pointer in Dom<T>.
100        struct MaybeUnreflectedStackRoot<T>(T);
101        unsafe impl<T> JSTraceable for MaybeUnreflectedStackRoot<T>
102        where
103            T: DomObject,
104        {
105            unsafe fn trace(&self, tracer: *mut JSTracer) {
106                if self.0.reflector().get_jsobject().is_null() {
107                    unsafe { self.0.trace(tracer) };
108                } else {
109                    unsafe { trace_reflector(tracer, "on stack", self.0.reflector()) };
110                }
111            }
112        }
113        unsafe { &*(self.ptr.as_ptr() as *const T as *const MaybeUnreflectedStackRoot<T>) }
114    }
115}
116
117impl<T> Deref for Root<T>
118where
119    T: Deref + StableTraceObject,
120{
121    type Target = <T as Deref>::Target;
122
123    fn deref(&self) -> &Self::Target {
124        assert_in_script();
125        &self.value
126    }
127}
128
129impl<T> Drop for Root<T>
130where
131    T: StableTraceObject,
132{
133    fn drop(&mut self) {
134        unsafe {
135            (*self.root_list).unroot(self.value.stable_trace_object());
136        }
137    }
138}
139
140impl<T: fmt::Debug + StableTraceObject> fmt::Debug for Root<T> {
141    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142        self.value.fmt(f)
143    }
144}
145
146impl<T: fmt::Debug + DomObject> fmt::Debug for Dom<T> {
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        (**self).fmt(f)
149    }
150}
151
152/// A traced reference to a DOM object
153///
154/// This type is critical to making garbage collection work with the DOM,
155/// but it is very dangerous; if garbage collection happens with a `Dom<T>`
156/// on the stack, the `Dom<T>` can point to freed memory.
157///
158/// This should only be used as a field in other DOM objects.
159#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
160#[repr(transparent)]
161pub struct Dom<T> {
162    ptr: ptr::NonNull<T>,
163}
164
165// Dom<T> is similar to Rc<T>, in that it's not always clear how to avoid double-counting.
166// For now, we choose not to follow any such pointers.
167impl<T> MallocSizeOf for Dom<T> {
168    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
169        0
170    }
171}
172
173/// Compare by pointer address
174impl<T> PartialEq for Dom<T> {
175    fn eq(&self, other: &Dom<T>) -> bool {
176        self.ptr.as_ptr() == other.ptr.as_ptr()
177    }
178}
179
180/// Compare by pointer address
181impl<'a, T: DomObject> PartialEq<&'a T> for Dom<T> {
182    fn eq(&self, other: &&'a T) -> bool {
183        *self == Dom::from_ref(*other)
184    }
185}
186
187impl<T> Eq for Dom<T> {}
188
189/// Hashes the pointer address
190impl<T> Hash for Dom<T> {
191    fn hash<H: Hasher>(&self, state: &mut H) {
192        self.ptr.as_ptr().hash(state)
193    }
194}
195
196impl<T> Clone for Dom<T> {
197    #[inline]
198    fn clone(&self) -> Self {
199        assert_in_script();
200        Dom { ptr: self.ptr }
201    }
202}
203
204impl<T: DomObject> Dom<T> {
205    /// Create a `Dom<T>` from a `&T`
206    pub fn from_ref(obj: &T) -> Dom<T> {
207        assert_in_script();
208        Dom {
209            ptr: ptr::NonNull::from(obj),
210        }
211    }
212
213    /// Return a rooted version of this DOM object ([`DomRoot<T>`]) suitable for use on the stack.
214    pub fn as_rooted(&self) -> DomRoot<T> {
215        DomRoot::from_ref(self)
216    }
217
218    pub fn as_ptr(&self) -> *const T {
219        self.ptr.as_ptr()
220    }
221}
222
223impl<T: DomObject> Deref for Dom<T> {
224    type Target = T;
225
226    fn deref(&self) -> &T {
227        assert_in_script();
228        // We can only have &Dom<T> from a rooted thing, so it's safe to deref
229        // it to &T.
230        unsafe { &*self.ptr.as_ptr() }
231    }
232}
233
234unsafe impl<T: DomObject> JSTraceable for Dom<T> {
235    unsafe fn trace(&self, tracer: *mut JSTracer) {
236        let trace_info = if cfg!(debug_assertions) {
237            std::any::type_name::<T>()
238        } else {
239            "DOM object on heap"
240        };
241        unsafe {
242            trace_reflector(tracer, trace_info, (*self.ptr.as_ptr()).reflector());
243        }
244    }
245}
246
247/// A traced reference to a DOM object that may not be reflected yet.
248#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
249pub struct MaybeUnreflectedDom<T> {
250    ptr: ptr::NonNull<T>,
251}
252
253impl<T> MaybeUnreflectedDom<T>
254where
255    T: DomObject,
256{
257    /// Create a new MaybeUnreflectedDom value from the given boxed DOM object.
258    ///
259    /// # Safety
260    /// TODO: unclear why this is marked unsafe.
261    pub unsafe fn from_box(value: Box<T>) -> Self {
262        Self {
263            ptr: Box::leak(value).into(),
264        }
265    }
266
267    /// Create a new MaybeUnreflectedDom value from the given RCed DOM object.
268    ///
269    /// # Safety
270    /// TODO: unclear why this is marked unsafe.
271    pub unsafe fn from_rc(value: Rc<T>) -> Self {
272        Self {
273            ptr: ptr::NonNull::new(Rc::into_raw(value) as *mut T).unwrap(),
274        }
275    }
276}
277
278impl<T> Root<MaybeUnreflectedDom<T>>
279where
280    T: DomObject,
281{
282    pub fn as_ptr(&self) -> *const T {
283        self.value.ptr.as_ptr()
284    }
285}
286
287impl<T> Root<MaybeUnreflectedDom<T>>
288where
289    T: MutDomObject,
290{
291    /// Treat the given JS object as the reflector of this unreflected object.
292    ///
293    /// # Safety
294    /// obj must point to a valid, non-null JS object.
295    pub unsafe fn reflect_with(self, obj: *mut JSObject) -> DomRoot<T> {
296        let ptr = self.as_ptr();
297        drop(self);
298        let root = DomRoot::from_ref(unsafe { &*ptr });
299        unsafe { root.init_reflector::<T>(obj) };
300        root
301    }
302}
303
304/// A rooted reference to a DOM object.
305pub type DomRoot<T> = Root<Dom<T>>;
306
307impl<T: Castable> DomRoot<T> {
308    /// Cast a DOM object root upwards to one of the interfaces it derives from.
309    pub fn upcast<U>(root: DomRoot<T>) -> DomRoot<U>
310    where
311        U: Castable,
312        T: DerivedFrom<U>,
313    {
314        unsafe { mem::transmute::<DomRoot<T>, DomRoot<U>>(root) }
315    }
316
317    /// Cast a DOM object root downwards to one of the interfaces it might implement.
318    pub fn downcast<U>(root: DomRoot<T>) -> Option<DomRoot<U>>
319    where
320        U: DerivedFrom<T>,
321    {
322        if root.is::<U>() {
323            Some(unsafe { mem::transmute::<DomRoot<T>, DomRoot<U>>(root) })
324        } else {
325            None
326        }
327    }
328}
329
330impl<T: DomObject> DomRoot<T> {
331    /// Generate a new root from a reference
332    pub fn from_ref(unrooted: &T) -> DomRoot<T> {
333        unsafe { DomRoot::new(Dom::from_ref(unrooted)) }
334    }
335
336    /// Create a traced version of this rooted object.
337    ///
338    /// # Safety
339    ///
340    /// This should never be used to create on-stack values. Instead these values should always
341    /// end up as members of other DOM objects.
342    pub fn as_traced(&self) -> Dom<T> {
343        Dom::from_ref(self)
344    }
345}
346
347impl<T> MallocSizeOf for DomRoot<T>
348where
349    T: DomObject + MallocSizeOf,
350{
351    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
352        0
353    }
354}
355
356impl<T> PartialEq for DomRoot<T>
357where
358    T: DomObject,
359{
360    fn eq(&self, other: &Self) -> bool {
361        self.value == other.value
362    }
363}
364
365impl<T: DomObject> Eq for DomRoot<T> {}
366
367impl<T: DomObject> Hash for DomRoot<T> {
368    fn hash<H: Hasher>(&self, state: &mut H) {
369        self.value.hash(state);
370    }
371}
372
373impl<T> Clone for DomRoot<T>
374where
375    T: DomObject,
376{
377    fn clone(&self) -> DomRoot<T> {
378        DomRoot::from_ref(self)
379    }
380}
381
382unsafe impl<T> JSTraceable for DomRoot<T>
383where
384    T: DomObject,
385{
386    unsafe fn trace(&self, _: *mut JSTracer) {
387        // Already traced.
388    }
389}
390
391/// A rooting mechanism for reflectors on the stack.
392/// LIFO is not required.
393///
394/// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*][cstack].
395///
396/// [cstack]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting
397pub struct RootCollection {
398    roots: UnsafeCell<Vec<*const dyn JSTraceable>>,
399}
400
401impl RootCollection {
402    /// Create an empty collection of roots
403    #[expect(clippy::new_without_default)]
404    pub const fn new() -> RootCollection {
405        RootCollection {
406            roots: UnsafeCell::new(vec![]),
407        }
408    }
409
410    /// Starts tracking a trace object.
411    unsafe fn root(&self, object: *const dyn JSTraceable) {
412        assert_in_script();
413        unsafe { (*self.roots.get()).push(object) };
414    }
415
416    /// Stops tracking a trace object, asserting if it isn't found.
417    unsafe fn unroot(&self, object: *const dyn JSTraceable) {
418        assert_in_script();
419        let roots = unsafe { &mut *self.roots.get() };
420        match roots
421            .iter()
422            .rposition(|r| std::ptr::addr_eq(*r as *const (), object as *const ()))
423        {
424            Some(idx) => {
425                // Partial inlining of `Vec::swap_remove` to avoid having to read and return the value since
426                // we don't care about it, doing less work in our case.
427                // SAFETY: the copy source and destination are derived from valid positions in the vector.
428                unsafe {
429                    let len = roots.len() - 1;
430                    if len != idx {
431                        let base_ptr = roots.as_mut_ptr();
432                        ptr::copy_nonoverlapping(base_ptr.add(len), base_ptr.add(idx), 1);
433                    }
434                    roots.set_len(len);
435                }
436            },
437            None => panic!("Can't remove a root that was never rooted!"),
438        }
439    }
440}
441
442thread_local!(pub static STACK_ROOTS: RootCollection = const { RootCollection::new() });
443
444/// SM Callback that traces the rooted reflectors
445///
446/// # Safety
447/// tracer must point to a valid, non-null JS tracer object.
448pub unsafe fn trace_roots(tracer: *mut JSTracer) {
449    trace!("tracing stack roots");
450    STACK_ROOTS.with(|collection| {
451        let collection = unsafe { &*collection.roots.get() };
452        for root in collection {
453            unsafe {
454                (**root).trace(tracer);
455            }
456        }
457    });
458}
459
460/// Get a slice of references to DOM objects.
461pub trait DomSlice<T>
462where
463    T: JSTraceable + DomObject,
464{
465    /// Returns the slice of `T` references.
466    fn r(&self) -> &[&T];
467}
468
469impl<T> DomSlice<T> for [Dom<T>]
470where
471    T: JSTraceable + DomObject,
472{
473    #[inline]
474    fn r(&self) -> &[&T] {
475        let _ = mem::transmute::<Dom<T>, &T>;
476        unsafe { &*(self as *const [Dom<T>] as *const [&T]) }
477    }
478}
479
480/// Returns a handle to a Heap member of a reflected DOM object.
481/// The provided callback acts as a projection of the rooted-ness of
482/// the provided DOM object; it must return a reference to a Heap
483/// member of the DOM object.
484pub fn rooted_heap_handle<'a, T: DomObject, U: GCMethods + Copy>(
485    object: &'a T,
486    f: impl Fn(&'a T) -> &'a Heap<U>,
487) -> Handle<'a, U> {
488    // SAFETY: Heap::handle is safe to call when the Heap is a member
489    //   of a rooted object. Our safety invariants for DOM objects
490    //   ensure that a &T is obtained via a root of T.
491    unsafe { Handle::from_raw(f(object).handle()) }
492}