rquickjs_core/class/
trace.rs

1use super::JsClass;
2use crate::{markers::Invariant, qjs, Atom, Class, Ctx, Module, Value};
3use core::marker::PhantomData;
4
5#[cfg(feature = "either")]
6use either::{Either, Left, Right};
7
8/// A trait for classes for tracing references to QuickJS objects.
9///
10/// QuickJS uses reference counting with tracing to break cycles. As a result implementing this
11/// trait incorrectly by not tracing an object cannot result in unsound code. It will however
12/// result in memory leaks as the GC will be unable to break cycles which in turn result in cyclic
13/// references being kept alive forever.
14pub trait Trace<'js> {
15    fn trace<'a>(&self, tracer: Tracer<'a, 'js>);
16}
17
18/// An object used for tracing references
19#[derive(Clone, Copy)]
20pub struct Tracer<'a, 'js> {
21    rt: *mut qjs::JSRuntime,
22    mark_func: qjs::JS_MarkFunc,
23    /// This trace should not be able to be used with different runtimes
24    _inv: Invariant<'js>,
25    /// Marker for acting like a reference so that the tracer can't be stored in an object.
26    _marker: PhantomData<&'a qjs::JSRuntime>,
27}
28
29impl<'a, 'js> Tracer<'a, 'js> {
30    /// Create a tracer from the c implementation.
31    ///
32    /// # Safety
33    /// Caller must ensure that the trace doesn't outlive the lifetime of the `mark_func` and
34    /// `rt` pointer
35    pub unsafe fn from_ffi(rt: *mut qjs::JSRuntime, mark_func: qjs::JS_MarkFunc) -> Self {
36        Self {
37            rt,
38            mark_func,
39            _inv: Invariant::new(),
40            _marker: PhantomData,
41        }
42    }
43
44    pub(crate) unsafe fn cast_js_lifetime<'js2>(self) -> Tracer<'a, 'js2> {
45        Tracer {
46            rt: self.rt,
47            mark_func: self.mark_func,
48            _inv: Invariant::new(),
49            _marker: PhantomData,
50        }
51    }
52
53    /// Mark a value as being reachable from the current traced object.
54    pub fn mark(self, value: &Value<'js>) {
55        self.mark_ctx(value.ctx());
56        let value = value.as_js_value();
57        if unsafe { qjs::JS_VALUE_HAS_REF_COUNT(value) } {
58            unsafe { qjs::JS_MarkValue(self.rt, value, self.mark_func) };
59        }
60    }
61
62    /// Mark the ctx object, this function should be called on any bare ctx objects.
63    pub fn mark_ctx(self, ctx: &Ctx<'js>) {
64        let ptr = ctx.as_ptr();
65        unsafe { (self.mark_func.unwrap())(self.rt, ptr.cast()) }
66    }
67}
68
69impl<'js> Trace<'js> for Value<'js> {
70    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
71        tracer.mark(self);
72    }
73}
74
75impl<'js> Trace<'js> for Ctx<'js> {
76    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
77        tracer.mark_ctx(self);
78    }
79}
80
81impl<'js, T> Trace<'js> for Class<'js, T>
82where
83    T: JsClass<'js>,
84{
85    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
86        self.0.trace(tracer)
87    }
88}
89
90impl<'js, T> Trace<'js> for Module<'js, T> {
91    fn trace<'a>(&self, _tracer: Tracer<'a, 'js>) {}
92}
93
94impl<'js, T> Trace<'js> for Option<T>
95where
96    T: Trace<'js>,
97{
98    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
99        if let Some(inner) = &self {
100            inner.trace(tracer);
101        }
102    }
103}
104
105impl<'js> Trace<'js> for Atom<'js> {
106    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
107        tracer.mark_ctx(&self.ctx);
108    }
109}
110
111#[cfg(feature = "either")]
112#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "either")))]
113impl<'js, L, R> Trace<'js> for Either<L, R>
114where
115    L: Trace<'js>,
116    R: Trace<'js>,
117{
118    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
119        match self {
120            Left(l) => l.trace(tracer),
121            Right(r) => r.trace(tracer),
122        }
123    }
124}
125
126macro_rules! trace_impls {
127
128    (primitive: $( $(#[$meta:meta])* $($type:ident)::+$(<$lt:lifetime>)?,)*) => {
129        $(
130        $(#[$meta])*
131        impl<'js> Trace<'js> for $($type)::*$(<$lt>)*{
132            fn trace<'a>(&self, _tracer: Tracer<'a,'js>) { }
133        }
134        )*
135    };
136
137    (base: $( $(#[$meta:meta])* $($type:ident)::+,)*) => {
138        $(
139        $(#[$meta])*
140        impl<'js> Trace<'js> for $($type)::*<'js>{
141            fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
142                self.as_value().trace(tracer)
143            }
144        }
145        )*
146    };
147
148    (ref: $($($type:ident)::+,)*) => {
149        $(
150            impl<'js, T> Trace<'js> for $($type)::*<T>
151            where
152            T: Trace<'js>,
153            {
154                fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
155                    let this: &T = &self;
156                    this.trace(tracer);
157                }
158            }
159        )*
160    };
161
162    (tup: $($($type:ident)*,)*) => {
163        $(
164            impl<'js, $($type),*> Trace<'js> for ($($type,)*)
165            where
166            $($type: Trace<'js>,)*
167            {
168                #[allow(non_snake_case)]
169                fn trace<'a>(&self, _tracer: Tracer<'a,'js>) {
170                    let ($($type,)*) = &self;
171                    $($type.trace(_tracer);)*
172                }
173            }
174        )*
175    };
176
177    (list: $($(#[$meta:meta])* $($type:ident)::+ $({$param:ident})*,)*) => {
178        $(
179            $(#[$meta])*
180            impl<'js, T $(,$param)*> Trace<'js> for $($type)::*<T $(,$param)*>
181            where
182            T: Trace<'js>,
183            {
184                fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
185                    for item in self.iter() {
186                        item.trace(tracer);
187                    }
188                }
189            }
190        )*
191    };
192
193    (map: $($(#[$meta:meta])* $($type:ident)::+ $({$param:ident})*,)*) => {
194        $(
195            $(#[$meta])*
196            impl<'js, K, V $(,$param)*> Trace<'js> for $($type)::*<K, V $(,$param)*>
197            where
198            K: Trace<'js>,
199            V: Trace<'js>,
200            {
201                fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
202                    for (key,item) in self.iter() {
203                        key.trace(tracer);
204                        item.trace(tracer);
205                    }
206                }
207            }
208        )*
209    };
210}
211
212trace_impls! {
213    primitive:
214    u8,u16,u32,u64,usize,
215    i8,i16,i32,i64,isize,
216    f32,f64,
217    bool,char,
218    alloc::string::String,
219}
220
221trace_impls! {
222    base:
223    crate::Object,
224    crate::Array,
225    crate::Function,
226    crate::BigInt,
227    crate::Symbol,
228    crate::Exception,
229    crate::String,
230}
231
232trace_impls! {
233    ref:
234    alloc::boxed::Box,
235    alloc::rc::Rc,
236    alloc::sync::Arc,
237}
238
239trace_impls! {
240    tup:
241    ,
242    A,
243    A B,
244    A B C,
245    A B C D,
246    A B C D E,
247    A B C D E F,
248    A B C D E F G,
249    A B C D E F G H,
250    A B C D E F G H I,
251    A B C D E F G H I J,
252    A B C D E F G H I J K,
253    A B C D E F G H I J K L,
254    A B C D E F G H I J K L M,
255    A B C D E F G H I J K L M N,
256    A B C D E F G H I J K L M N O,
257    A B C D E F G H I J K L M N O P,
258}
259
260trace_impls! {
261    list:
262    alloc::vec::Vec,
263    alloc::collections::VecDeque,
264    alloc::collections::LinkedList,
265    #[cfg(feature = "std")]
266    std::collections::HashSet {S},
267    alloc::collections::BTreeSet,
268    #[cfg(feature = "indexmap")]
269    #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "indexmap"))))]
270    indexmap::IndexSet {S},
271}
272
273trace_impls! {
274    map:
275    #[cfg(feature = "std")]
276    std::collections::HashMap {S},
277    alloc::collections::BTreeMap,
278    #[cfg(feature = "indexmap")]
279    #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "indexmap"))))]
280    indexmap::IndexMap {S},
281}