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