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
173impl<T> PartialEq for Dom<T> {
174    fn eq(&self, other: &Dom<T>) -> bool {
175        self.ptr.as_ptr() == other.ptr.as_ptr()
176    }
177}
178
179impl<'a, T: DomObject> PartialEq<&'a T> for Dom<T> {
180    fn eq(&self, other: &&'a T) -> bool {
181        *self == Dom::from_ref(*other)
182    }
183}
184
185impl<T> Eq for Dom<T> {}
186
187impl<T> Hash for Dom<T> {
188    fn hash<H: Hasher>(&self, state: &mut H) {
189        self.ptr.as_ptr().hash(state)
190    }
191}
192
193impl<T> Clone for Dom<T> {
194    #[inline]
195    fn clone(&self) -> Self {
196        assert_in_script();
197        Dom { ptr: self.ptr }
198    }
199}
200
201impl<T: DomObject> Dom<T> {
202    /// Create a `Dom<T>` from a `&T`
203    pub fn from_ref(obj: &T) -> Dom<T> {
204        assert_in_script();
205        Dom {
206            ptr: ptr::NonNull::from(obj),
207        }
208    }
209
210    /// Return a rooted version of this DOM object ([`DomRoot<T>`]) suitable for use on the stack.
211    pub fn as_rooted(&self) -> DomRoot<T> {
212        DomRoot::from_ref(self)
213    }
214
215    pub fn as_ptr(&self) -> *const T {
216        self.ptr.as_ptr()
217    }
218}
219
220impl<T: DomObject> Deref for Dom<T> {
221    type Target = T;
222
223    fn deref(&self) -> &T {
224        assert_in_script();
225        // We can only have &Dom<T> from a rooted thing, so it's safe to deref
226        // it to &T.
227        unsafe { &*self.ptr.as_ptr() }
228    }
229}
230
231unsafe impl<T: DomObject> JSTraceable for Dom<T> {
232    unsafe fn trace(&self, tracer: *mut JSTracer) {
233        let trace_info = if cfg!(debug_assertions) {
234            std::any::type_name::<T>()
235        } else {
236            "DOM object on heap"
237        };
238        unsafe {
239            trace_reflector(tracer, trace_info, (*self.ptr.as_ptr()).reflector());
240        }
241    }
242}
243
244/// A traced reference to a DOM object that may not be reflected yet.
245#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
246pub struct MaybeUnreflectedDom<T> {
247    ptr: ptr::NonNull<T>,
248}
249
250impl<T> MaybeUnreflectedDom<T>
251where
252    T: DomObject,
253{
254    /// Create a new MaybeUnreflectedDom value from the given boxed DOM object.
255    ///
256    /// # Safety
257    /// TODO: unclear why this is marked unsafe.
258    pub unsafe fn from_box(value: Box<T>) -> Self {
259        Self {
260            ptr: Box::leak(value).into(),
261        }
262    }
263
264    /// Create a new MaybeUnreflectedDom value from the given RCed DOM object.
265    ///
266    /// # Safety
267    /// TODO: unclear why this is marked unsafe.
268    pub unsafe fn from_rc(value: Rc<T>) -> Self {
269        Self {
270            ptr: ptr::NonNull::new(Rc::into_raw(value) as *mut T).unwrap(),
271        }
272    }
273}
274
275impl<T> Root<MaybeUnreflectedDom<T>>
276where
277    T: DomObject,
278{
279    pub fn as_ptr(&self) -> *const T {
280        self.value.ptr.as_ptr()
281    }
282}
283
284impl<T> Root<MaybeUnreflectedDom<T>>
285where
286    T: MutDomObject,
287{
288    /// Treat the given JS object as the reflector of this unreflected object.
289    ///
290    /// # Safety
291    /// obj must point to a valid, non-null JS object.
292    pub unsafe fn reflect_with(self, obj: *mut JSObject) -> DomRoot<T> {
293        let ptr = self.as_ptr();
294        drop(self);
295        let root = DomRoot::from_ref(unsafe { &*ptr });
296        unsafe { root.init_reflector::<T>(obj) };
297        root
298    }
299}
300
301/// A rooted reference to a DOM object.
302pub type DomRoot<T> = Root<Dom<T>>;
303
304impl<T: Castable> DomRoot<T> {
305    /// Cast a DOM object root upwards to one of the interfaces it derives from.
306    pub fn upcast<U>(root: DomRoot<T>) -> DomRoot<U>
307    where
308        U: Castable,
309        T: DerivedFrom<U>,
310    {
311        unsafe { mem::transmute::<DomRoot<T>, DomRoot<U>>(root) }
312    }
313
314    /// Cast a DOM object root downwards to one of the interfaces it might implement.
315    pub fn downcast<U>(root: DomRoot<T>) -> Option<DomRoot<U>>
316    where
317        U: DerivedFrom<T>,
318    {
319        if root.is::<U>() {
320            Some(unsafe { mem::transmute::<DomRoot<T>, DomRoot<U>>(root) })
321        } else {
322            None
323        }
324    }
325}
326
327impl<T: DomObject> DomRoot<T> {
328    /// Generate a new root from a reference
329    pub fn from_ref(unrooted: &T) -> DomRoot<T> {
330        unsafe { DomRoot::new(Dom::from_ref(unrooted)) }
331    }
332
333    /// Create a traced version of this rooted object.
334    ///
335    /// # Safety
336    ///
337    /// This should never be used to create on-stack values. Instead these values should always
338    /// end up as members of other DOM objects.
339    pub fn as_traced(&self) -> Dom<T> {
340        Dom::from_ref(self)
341    }
342}
343
344impl<T> MallocSizeOf for DomRoot<T>
345where
346    T: DomObject + MallocSizeOf,
347{
348    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
349        0
350    }
351}
352
353impl<T> PartialEq for DomRoot<T>
354where
355    T: DomObject,
356{
357    fn eq(&self, other: &Self) -> bool {
358        self.value == other.value
359    }
360}
361
362impl<T: DomObject> Eq for DomRoot<T> {}
363
364impl<T: DomObject> Hash for DomRoot<T> {
365    fn hash<H: Hasher>(&self, state: &mut H) {
366        self.value.hash(state);
367    }
368}
369
370impl<T> Clone for DomRoot<T>
371where
372    T: DomObject,
373{
374    fn clone(&self) -> DomRoot<T> {
375        DomRoot::from_ref(self)
376    }
377}
378
379unsafe impl<T> JSTraceable for DomRoot<T>
380where
381    T: DomObject,
382{
383    unsafe fn trace(&self, _: *mut JSTracer) {
384        // Already traced.
385    }
386}
387
388/// A rooting mechanism for reflectors on the stack.
389/// LIFO is not required.
390///
391/// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*][cstack].
392///
393/// [cstack]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting
394pub struct RootCollection {
395    roots: UnsafeCell<Vec<*const dyn JSTraceable>>,
396}
397
398impl RootCollection {
399    /// Create an empty collection of roots
400    #[expect(clippy::new_without_default)]
401    pub const fn new() -> RootCollection {
402        RootCollection {
403            roots: UnsafeCell::new(vec![]),
404        }
405    }
406
407    /// Starts tracking a trace object.
408    unsafe fn root(&self, object: *const dyn JSTraceable) {
409        assert_in_script();
410        unsafe { (*self.roots.get()).push(object) };
411    }
412
413    /// Stops tracking a trace object, asserting if it isn't found.
414    unsafe fn unroot(&self, object: *const dyn JSTraceable) {
415        assert_in_script();
416        let roots = unsafe { &mut *self.roots.get() };
417        match roots
418            .iter()
419            .rposition(|r| std::ptr::addr_eq(*r as *const (), object as *const ()))
420        {
421            Some(idx) => {
422                // Partial inlining of `Vec::swap_remove` to avoid having to read and return the value since
423                // we don't care about it, doing less work in our case.
424                // SAFETY: the copy source and destination are derived from valid positions in the vector.
425                unsafe {
426                    let len = roots.len() - 1;
427                    if len != idx {
428                        let base_ptr = roots.as_mut_ptr();
429                        ptr::copy_nonoverlapping(base_ptr.add(len), base_ptr.add(idx), 1);
430                    }
431                    roots.set_len(len);
432                }
433            },
434            None => panic!("Can't remove a root that was never rooted!"),
435        }
436    }
437}
438
439thread_local!(pub static STACK_ROOTS: RootCollection = const { RootCollection::new() });
440
441/// SM Callback that traces the rooted reflectors
442///
443/// # Safety
444/// tracer must point to a valid, non-null JS tracer object.
445pub unsafe fn trace_roots(tracer: *mut JSTracer) {
446    trace!("tracing stack roots");
447    STACK_ROOTS.with(|collection| {
448        let collection = unsafe { &*collection.roots.get() };
449        for root in collection {
450            unsafe {
451                (**root).trace(tracer);
452            }
453        }
454    });
455}
456
457/// Get a slice of references to DOM objects.
458pub trait DomSlice<T>
459where
460    T: JSTraceable + DomObject,
461{
462    /// Returns the slice of `T` references.
463    fn r(&self) -> &[&T];
464}
465
466impl<T> DomSlice<T> for [Dom<T>]
467where
468    T: JSTraceable + DomObject,
469{
470    #[inline]
471    fn r(&self) -> &[&T] {
472        let _ = mem::transmute::<Dom<T>, &T>;
473        unsafe { &*(self as *const [Dom<T>] as *const [&T]) }
474    }
475}
476
477/// Returns a handle to a Heap member of a reflected DOM object.
478/// The provided callback acts as a projection of the rooted-ness of
479/// the provided DOM object; it must return a reference to a Heap
480/// member of the DOM object.
481pub fn rooted_heap_handle<'a, T: DomObject, U: GCMethods + Copy>(
482    object: &'a T,
483    f: impl Fn(&'a T) -> &'a Heap<U>,
484) -> Handle<'a, U> {
485    // SAFETY: Heap::handle is safe to call when the Heap is a member
486    //   of a rooted object. Our safety invariants for DOM objects
487    //   ensure that a &T is obtained via a root of T.
488    unsafe { Handle::from_raw(f(object).handle()) }
489}