Skip to main content

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