Skip to main content

rustpython_vm/object/
traverse.rs

1use core::ptr::NonNull;
2
3use rustpython_common::lock::{PyMutex, PyRwLock};
4
5use crate::{
6    AsObject, PyObject, PyObjectRef, PyRef, PyStackRef, function::Either, object::PyObjectPayload,
7};
8
9pub type TraverseFn<'a> = dyn FnMut(&PyObject) + 'a;
10
11/// This trait is used as a "Optional Trait"(I 'd like to use `Trace?` but it's not allowed yet) for PyObjectPayload type
12///
13/// impl for PyObjectPayload, `pyclass` proc macro will handle the actual dispatch if type impl `Trace`
14/// Every PyObjectPayload impl `MaybeTrace`, which may or may not be traceable
15pub trait MaybeTraverse {
16    /// if is traceable, will be used by vtable to determine
17    const HAS_TRAVERSE: bool = false;
18    /// if has clear implementation for circular reference resolution (tp_clear)
19    const HAS_CLEAR: bool = false;
20    // if this type is traceable, then call with tracer_fn, default to do nothing
21    fn try_traverse(&self, traverse_fn: &mut TraverseFn<'_>);
22    // if this type has clear, extract child refs for circular reference resolution (tp_clear)
23    fn try_clear(&mut self, _out: &mut Vec<PyObjectRef>) {}
24}
25
26/// Type that need traverse it's children should impl [`Traverse`] (not [`MaybeTraverse`])
27/// # Safety
28/// Please carefully read [`Traverse::traverse()`] and follow the guideline
29pub unsafe trait Traverse {
30    /// impl `traverse()` with caution! Following those guideline so traverse doesn't cause memory error!:
31    /// - Make sure that every owned object(Every PyObjectRef/PyRef) is called with traverse_fn **at most once**.
32    ///   If some field is not called, the worst results is just memory leak,
33    ///   but if some field is called repeatedly, panic and deadlock can happen.
34    ///
35    /// - _**DO NOT**_ clone a [`PyObjectRef`] or [`PyRef<T>`] in [`Traverse::traverse()`]
36    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>);
37
38    /// Extract all owned child PyObjectRefs for circular reference resolution (tp_clear).
39    /// Called just before object deallocation to break circular references.
40    /// Default implementation does nothing.
41    fn clear(&mut self, _out: &mut Vec<PyObjectRef>) {}
42}
43
44unsafe impl Traverse for PyObjectRef {
45    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
46        traverse_fn(self)
47    }
48}
49
50unsafe impl Traverse for PyStackRef {
51    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
52        // A borrowed stack ref owns no strong count, so it is not an edge the
53        // cycle collector may subtract: counting it would push a live object's
54        // `gc_refs` to zero and free it out from under the frame. The object a
55        // borrow points at is always reachable another way -- through the
56        // fastlocals slot that keeps it alive, which this same frame traversal
57        // visits -- so skipping it loses no reachability either.
58        if !self.is_borrowed() {
59            traverse_fn(self.as_object())
60        }
61    }
62}
63
64unsafe impl<T: PyObjectPayload> Traverse for PyRef<T> {
65    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
66        traverse_fn(self.as_object())
67    }
68}
69
70unsafe impl Traverse for () {
71    fn traverse(&self, _traverse_fn: &mut TraverseFn<'_>) {}
72}
73
74unsafe impl<T: Traverse> Traverse for Option<T> {
75    #[inline]
76    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
77        if let Some(v) = self {
78            v.traverse(traverse_fn);
79        }
80    }
81}
82
83unsafe impl<T> Traverse for [T]
84where
85    T: Traverse,
86{
87    #[inline]
88    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
89        for elem in self {
90            elem.traverse(traverse_fn);
91        }
92    }
93}
94
95unsafe impl<T> Traverse for Box<[T]>
96where
97    T: Traverse,
98{
99    #[inline]
100    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
101        for elem in &**self {
102            elem.traverse(traverse_fn);
103        }
104    }
105}
106
107unsafe impl<T> Traverse for Vec<T>
108where
109    T: Traverse,
110{
111    #[inline]
112    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
113        for elem in self {
114            elem.traverse(traverse_fn);
115        }
116    }
117}
118
119unsafe impl<T: crate::PyPayload> Traverse for super::ext::PyAtomicRef<Option<T>> {
120    #[inline]
121    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
122        if let Some(obj) = self.deref() {
123            traverse_fn(obj.as_object());
124        }
125    }
126}
127
128unsafe impl<T: crate::PyPayload> Traverse for super::ext::PyAtomicRef<T> {
129    #[inline]
130    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
131        traverse_fn(self.as_object());
132    }
133}
134
135unsafe impl Traverse for super::ext::PyAtomicRef<PyObject> {
136    #[inline]
137    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
138        traverse_fn(self);
139    }
140}
141
142unsafe impl Traverse for super::ext::PyAtomicRef<Option<PyObject>> {
143    #[inline]
144    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
145        let ptr = self.load_ptr();
146        // SAFETY: traversal runs with other threads stopped, so a non-null
147        // slot pointer stays allocated for this borrow.
148        if let Some(obj) = unsafe { ptr.as_ref() } {
149            traverse_fn(obj);
150        }
151    }
152}
153
154unsafe impl<T: Traverse> Traverse for PyRwLock<T> {
155    #[inline]
156    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
157        // A failed try_read means a writer holds the lock. Traversal runs with
158        // the world stopped, but a thread force-parked while DETACHED (CAS'd
159        // straight to SUSPENDED from native code) may still hold the write lock
160        // it was in the middle of taking. Skipping such an object is safe: the
161        // collector then does not see its outgoing edges, which only
162        // under-traverses and thus over-approximates liveness (a conservative
163        // keep-alive), never freeing a reachable object. In single-threaded
164        // builds a failure only reflects the current thread's own re-entrant
165        // read, likewise safely skipped.
166        if let Some(inner) = self.try_read_recursive() {
167            inner.traverse(traverse_fn)
168        }
169    }
170}
171
172/// Safety: the lock is not held across visiting children to avoid a re-entrant
173/// deadlock. In threading builds traversal runs under stop-the-world so no
174/// other thread mutates the guarded value while we read it; in single-threaded
175/// builds there is no other writer.
176unsafe impl<T: Traverse> Traverse for PyMutex<T> {
177    #[inline]
178    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
179        let mut chs: Vec<NonNull<PyObject>> = Vec::new();
180        if let Some(obj) = self.try_lock() {
181            obj.traverse(&mut |ch| {
182                chs.push(NonNull::from(ch));
183            })
184        }
185        chs.iter()
186            .map(|ch| {
187                // Safety: the world is stopped (threading builds) or the
188                // interpreter is single-threaded, so `ch` is not concurrently
189                // freed while we hand it to the tracer.
190                let ch = unsafe { ch.as_ref() };
191                traverse_fn(ch);
192            })
193            .count();
194    }
195}
196
197macro_rules! trace_tuple {
198    ($(($NAME: ident, $NUM: tt)),*) => {
199        unsafe impl<$($NAME: Traverse),*> Traverse for ($($NAME),*) {
200            #[inline]
201            fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
202                $(
203                    self.$NUM.traverse(traverse_fn);
204                )*
205            }
206        }
207
208    };
209}
210
211unsafe impl<A: Traverse, B: Traverse> Traverse for Either<A, B> {
212    #[inline]
213    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
214        match self {
215            Self::A(a) => a.traverse(tracer_fn),
216            Self::B(b) => b.traverse(tracer_fn),
217        }
218    }
219}
220
221// only tuple with 12 elements or less is supported,
222// because long tuple is extremely rare in almost every case
223unsafe impl<A: Traverse> Traverse for (A,) {
224    #[inline]
225    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
226        self.0.traverse(tracer_fn);
227    }
228}
229
230trace_tuple!((A, 0), (B, 1));
231trace_tuple!((A, 0), (B, 1), (C, 2));
232trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3));
233trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4));
234trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5));
235trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6));
236trace_tuple!(
237    (A, 0),
238    (B, 1),
239    (C, 2),
240    (D, 3),
241    (E, 4),
242    (F, 5),
243    (G, 6),
244    (H, 7)
245);
246trace_tuple!(
247    (A, 0),
248    (B, 1),
249    (C, 2),
250    (D, 3),
251    (E, 4),
252    (F, 5),
253    (G, 6),
254    (H, 7),
255    (I, 8)
256);
257trace_tuple!(
258    (A, 0),
259    (B, 1),
260    (C, 2),
261    (D, 3),
262    (E, 4),
263    (F, 5),
264    (G, 6),
265    (H, 7),
266    (I, 8),
267    (J, 9)
268);
269trace_tuple!(
270    (A, 0),
271    (B, 1),
272    (C, 2),
273    (D, 3),
274    (E, 4),
275    (F, 5),
276    (G, 6),
277    (H, 7),
278    (I, 8),
279    (J, 9),
280    (K, 10)
281);
282trace_tuple!(
283    (A, 0),
284    (B, 1),
285    (C, 2),
286    (D, 3),
287    (E, 4),
288    (F, 5),
289    (G, 6),
290    (H, 7),
291    (I, 8),
292    (J, 9),
293    (K, 10),
294    (L, 11)
295);