lua_vm/trace_impls.rs
1//! `Trace` implementations for GC-rooted types defined in this crate. Types
2//! in `lua-types` (LuaValue, LuaString, UpVal) have their Trace impls in
3//! `lua-types/src/trace_impls.rs` because of Rust's orphan rule.
4//!
5//! Guidance for adding a new impl (Trace impls have subtle invariants, so
6//! change one type at a time):
7//! 1. Read the type definition; enumerate every field
8//! 2. For every `Gc<T>`, `GcRef<T>`, or container (Vec/Option/HashMap)
9//! thereof, call `m.mark(field)` or `field.trace(m)` appropriately
10//! 3. Skip non-GC fields (primitives, `String`, `Vec<u8>`)
11//! 4. Skip "intentionally not traced" fields (weak refs)
12//! 5. Reference `lgc.c`'s `reallymarkobject` for the C original's approach
13
14use crate::state::{FinalizerObject, GlobalState, LuaState};
15use crate::string::{LuaStringImpl, LuaUserDataImpl};
16use lua_gc::{Marker, Trace};
17
18/// The byte buffer is a Rust `Rc<[u8]>` (not GC-managed); no fields to mark.
19impl Trace for LuaStringImpl {
20
21 fn type_name(&self) -> &'static str {
22 std::any::type_name::<Self>()
23 }
24
25 fn trace(&self, _m: &mut Marker) {}
26}
27
28/// `metatable` and `uv` are `Option<()>` / `Vec<()>` placeholders with no GC
29/// edges to walk; full userdata is `lua_types::value::LuaUserData` instead.
30impl Trace for LuaUserDataImpl {
31
32 fn type_name(&self) -> &'static str {
33 std::any::type_name::<Self>()
34 }
35
36 fn trace(&self, _m: &mut Marker) {}
37}
38
39impl Trace for FinalizerObject {
40
41 fn type_name(&self) -> &'static str {
42 std::any::type_name::<Self>()
43 }
44
45 fn trace(&self, m: &mut Marker) {
46 match self {
47 FinalizerObject::Table(t) => t.trace(m),
48 FinalizerObject::UserData(u) => u.trace(m),
49 }
50 }
51}
52
53impl Trace for LuaState {
54
55 fn type_name(&self) -> &'static str {
56 std::any::type_name::<Self>()
57 }
58
59 fn trace(&self, m: &mut Marker) {
60 // C's traversethread (lgc.c) walks [stack .. top) and relies on two
61 // companion invariants this port mirrors via `gc_trace_bound` (the
62 // savestate half — widen to ci.top only for a Lua current frame)
63 // and `clear_dead_stack_tail` (the atomic-clear half, run before
64 // every collect). Every slot below the bound is therefore
65 // valid-or-nil; the old frame-bounded range walk and the saved_pc
66 // debug-local heuristic (#140 bug B's two faces) are gone.
67 let bound = self.gc_trace_bound();
68 for slot in &self.stack[..bound] {
69 slot.val.trace(m);
70 }
71
72 for uv in self.openupval.iter() {
73 uv.trace(m);
74 }
75
76 // `global` (Rc<RefCell<GlobalState>>) is reached from the heap's root
77 // via GlobalState::trace; tracing it from each thread would re-enter
78 // the root and is explicitly excluded.
79 // `call_info` entries carry pc offsets and stack indices but no direct
80 // GcRef fields. The active closure is reached through the stack slot
81 // at `ci.func`, already covered by the stack walk.
82 // `tbclist` holds StackIdx values only; the to-be-closed objects
83 // themselves live on the stack and are traced there.
84 }
85}
86
87impl Trace for GlobalState {
88
89 fn type_name(&self) -> &'static str {
90 std::any::type_name::<Self>()
91 }
92
93 fn trace(&self, m: &mut Marker) {
94 // per-type metatables, and pending finalizers. We expand the set to
95 // include preallocated short strings (memerrmsg, tmname[]) and the
96 // open-upvalue thread list, both of which the panic-driven Phase-D
97 // mega-loop expects to see at the root.
98
99 self.l_registry.trace(m);
100
101 // Values held by Rust-side embedding handles are rooted outside the
102 // Lua registry table so handle Drop can unroot without touching the
103 // Lua stack/API. They are still ordinary GC roots during marking.
104 for value in self.external_roots.iter_values() {
105 value.trace(m);
106 }
107
108 // Cross-thread open-upvalue mirrors are live roots while a coroutine
109 // resume holds the home thread's stack behind an outer mutable borrow.
110 for value in self.cross_thread_upvals.values() {
111 value.trace(m);
112 }
113
114 // `globals` and `loaded` are kept as direct GlobalState fields rather
115 // than inside the registry table (see `init_registry`), so they must
116 // be traced explicitly as roots here.
117 self.globals.trace(m);
118 self.loaded.trace(m);
119
120 // Lua 5.1 per-thread and per-closure environments are GC roots: a
121 // coroutine's global table and a no-`_ENV`-upvalue closure's
122 // environment are reachable from the live thread / closure but stored
123 // off to the side, so they must be traced here to survive collection.
124 // Both maps are empty on 5.2–5.5, making these loops no-ops there.
125 // Dead-thread / dead-closure keys are pruned after each collection.
126 for value in self.thread_globals.values() {
127 value.trace(m);
128 }
129 for value in self.closure_envs.values() {
130 value.trace(m);
131 }
132
133 if let Some(t) = &self.mainthread {
134 t.trace(m);
135 }
136
137 self.main_thread_value.trace(m);
138
139 if self.current_thread_id != self.main_thread_id {
140 if let Some(entry) = self.threads.get(&self.current_thread_id) {
141 entry.value.trace(m);
142 }
143 }
144
145 // Registered coroutines are not roots by registration alone. The
146 // post-mark hook traces stacks only for thread handles that were
147 // reached from a real root, matching Lua's collectable coroutine
148 // semantics.
149
150 for slot in self.mt.iter() {
151 if let Some(t) = slot {
152 t.trace(m);
153 }
154 }
155
156 for s in self.tmname.iter() {
157 s.trace(m);
158 }
159
160 self.memerrmsg.trace(m);
161
162 for th in self.twups.iter() {
163 th.trace(m);
164 }
165
166 // `interned_lt` is a weak short-string cache. The collector prunes
167 // unmarked entries from the post-mark hook instead of tracing them as
168 // roots here.
169 for row in self.strcache.iter() {
170 for s in row.iter() {
171 s.trace(m);
172 }
173 }
174
175 // Pending finalizers are NOT traced here — that's what lets the mark
176 // phase distinguish "still reachable from the user program" from
177 // "only kept alive by the finalizer registry". `collect_via_heap`'s
178 // post-mark hook checks each entry against the visited set; an
179 // unvisited entry is moved to `to_be_finalized` and explicitly
180 // marked there so it survives the sweep.
181 //
182 // `to_be_finalized` IS traced as a strong root: objects in this list
183 // are awaiting their `__gc` call but are otherwise dead, and the
184 // object (plus its descendants) must survive long enough for the
185 // finalizer to run.
186 for object in self.finalizers.to_be_finalized().iter() {
187 object.trace(m);
188 }
189
190 // Trace suspended parent stacks. When a coroutine is running, any
191 // parent threads are suspended and their stacks are not reachable from
192 // `threads` (which only holds coroutines, not the main thread). Before
193 // `aux_resume` resumes a coroutine it pushes a snapshot of the parent's
194 // live stack onto `suspended_parent_stacks` so those GC-managed values
195 // remain marked during collections triggered from inside the coroutine.
196 for stack_snapshot in self.suspended_parent_stacks.iter() {
197 for v in stack_snapshot.iter() {
198 v.trace(m);
199 }
200 }
201 for upval_snapshot in self.suspended_parent_open_upvals.iter() {
202 for uv in upval_snapshot.iter() {
203 uv.trace(m);
204 }
205 }
206
207 // `strt` (the internal LuaStringImpl intern table) is a weak table in
208 // C; entries are cleared during the atomic weak-table pass
209 // (`clearbykeys`), not marked as roots. There is no incremental
210 // weak-sweep here, but `strt` is keyed by byte-content rather than by
211 // `Gc` identity, so a dangling entry there is silently recreated by
212 // the next `intern_str` — no UAF, unlike `interned_lt`.
213 // `fixedgc` holds objects pre-marked fixed/black at allocation
214 // (`luaC_fix`); the mark phase never re-visits them, and
215 // `dyn Collectable` does not implement `Trace` here.
216 // `allgc`, `finobj`, `gray`, `grayagain`, `tobefnz`, `weak`,
217 // `ephemeron`, `allweak` are GC bookkeeping lists owned by `heap` —
218 // they are the universe of allocated objects, not roots.
219 }
220}