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