rquickjs_core/class/
trace.rs

1use super::JsClass;
2use crate::{markers::Invariant, qjs, Class, Ctx, Module, Value};
3use std::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
105#[cfg(feature = "either")]
106#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "either")))]
107impl<'js, L, R> Trace<'js> for Either<L, R>
108where
109    L: Trace<'js>,
110    R: Trace<'js>,
111{
112    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
113        match self {
114            Left(l) => l.trace(tracer),
115            Right(r) => r.trace(tracer),
116        }
117    }
118}
119
120macro_rules! trace_impls {
121
122    (primitive: $( $(#[$meta:meta])* $($type:ident)::+$(<$lt:lifetime>)?,)*) => {
123        $(
124        $(#[$meta])*
125        impl<'js> Trace<'js> for $($type)::*$(<$lt>)*{
126            fn trace<'a>(&self, _tracer: Tracer<'a,'js>) { }
127        }
128        )*
129    };
130
131    (base: $( $(#[$meta:meta])* $($type:ident)::+,)*) => {
132        $(
133        $(#[$meta])*
134        impl<'js> Trace<'js> for $($type)::*<'js>{
135            fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
136                self.as_value().trace(tracer)
137            }
138        }
139        )*
140    };
141
142    (ref: $($($type:ident)::+,)*) => {
143        $(
144            impl<'js, T> Trace<'js> for $($type)::*<T>
145            where
146            T: Trace<'js>,
147            {
148                fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
149                    let this: &T = &self;
150                    this.trace(tracer);
151                }
152            }
153        )*
154    };
155
156    (tup: $($($type:ident)*,)*) => {
157        $(
158            impl<'js, $($type),*> Trace<'js> for ($($type,)*)
159            where
160            $($type: Trace<'js>,)*
161            {
162                #[allow(non_snake_case)]
163                fn trace<'a>(&self, _tracer: Tracer<'a,'js>) {
164                    let ($($type,)*) = &self;
165                    $($type.trace(_tracer);)*
166                }
167            }
168        )*
169    };
170
171    (list: $($(#[$meta:meta])* $($type:ident)::+ $({$param:ident})*,)*) => {
172        $(
173            $(#[$meta])*
174            impl<'js, T $(,$param)*> Trace<'js> for $($type)::*<T $(,$param)*>
175            where
176            T: Trace<'js>,
177            {
178                fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
179                    for item in self.iter() {
180                        item.trace(tracer);
181                    }
182                }
183            }
184        )*
185    };
186
187    (map: $($(#[$meta:meta])* $($type:ident)::+ $({$param:ident})*,)*) => {
188        $(
189            $(#[$meta])*
190            impl<'js, K, V $(,$param)*> Trace<'js> for $($type)::*<K, V $(,$param)*>
191            where
192            K: Trace<'js>,
193            V: Trace<'js>,
194            {
195                fn trace<'a>(&self, tracer: Tracer<'a,'js>) {
196                    for (key,item) in self.iter() {
197                        key.trace(tracer);
198                        item.trace(tracer);
199                    }
200                }
201            }
202        )*
203    };
204}
205
206trace_impls! {
207    primitive:
208    u8,u16,u32,u64,usize,
209    i8,i16,i32,i64,isize,
210    f32,f64,
211    bool,char,
212    String,
213    crate::Atom<'js>,
214}
215
216trace_impls! {
217    base:
218    crate::Object,
219    crate::Array,
220    crate::Function,
221    crate::BigInt,
222    crate::Symbol,
223    crate::Exception,
224    crate::String,
225}
226
227trace_impls! {
228    ref:
229    Box,
230    std::rc::Rc,
231    std::sync::Arc,
232}
233
234trace_impls! {
235    tup:
236    ,
237    A,
238    A B,
239    A B C,
240    A B C D,
241    A B C D E,
242    A B C D E F,
243    A B C D E F G,
244    A B C D E F G H,
245    A B C D E F G H I,
246    A B C D E F G H I J,
247    A B C D E F G H I J K,
248    A B C D E F G H I J K L,
249    A B C D E F G H I J K L M,
250    A B C D E F G H I J K L M N,
251    A B C D E F G H I J K L M N O,
252    A B C D E F G H I J K L M N O P,
253}
254
255trace_impls! {
256    list:
257    Vec,
258    std::collections::VecDeque,
259    std::collections::LinkedList,
260    std::collections::HashSet {S},
261    std::collections::BTreeSet,
262    #[cfg(feature = "indexmap")]
263    #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "indexmap"))))]
264    indexmap::IndexSet {S},
265}
266
267trace_impls! {
268    map:
269    std::collections::HashMap {S},
270    std::collections::BTreeMap,
271    #[cfg(feature = "indexmap")]
272    #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "indexmap"))))]
273    indexmap::IndexMap {S},
274}