Default dealloc: handles __del__, weakref clearing, tp_clear, and memory free. Equivalent to subtype_dealloc.
(obj: *mut PyObject)
| 156 | /// Default dealloc: handles __del__, weakref clearing, tp_clear, and memory free. |
| 157 | /// Equivalent to subtype_dealloc. |
| 158 | pub(super) unsafe fn default_dealloc<T: PyPayload>(obj: *mut PyObject) { |
| 159 | let obj_ref = unsafe { &*(obj as *const PyObject) }; |
| 160 | if let Err(()) = obj_ref.drop_slow_inner() { |
| 161 | return; // resurrected by __del__ |
| 162 | } |
| 163 | |
| 164 | // Trashcan: limit recursive deallocation depth to prevent stack overflow |
| 165 | if !unsafe { trashcan::begin(obj, default_dealloc::<T>) } { |
| 166 | return; // deferred to queue |
| 167 | } |
| 168 | |
| 169 | let vtable = obj_ref.0.vtable; |
| 170 | |
| 171 | // Untrack from GC BEFORE deallocation. |
| 172 | // Must happen before memory is freed because intrusive list removal |
| 173 | // reads the object's gc_pointers (prev/next). |
| 174 | if obj_ref.is_gc_tracked() { |
| 175 | let ptr = unsafe { NonNull::new_unchecked(obj) }; |
| 176 | unsafe { |
| 177 | crate::gc_state::gc_state().untrack_object(ptr); |
| 178 | } |
| 179 | // Verify untrack cleared the tracked flag and generation |
| 180 | debug_assert!( |
| 181 | !obj_ref.is_gc_tracked(), |
| 182 | "object still tracked after untrack_object" |
| 183 | ); |
| 184 | debug_assert_eq!( |
| 185 | obj_ref.gc_generation(), |
| 186 | crate::object::GC_UNTRACKED, |
| 187 | "gc_generation not reset after untrack_object" |
| 188 | ); |
| 189 | } |
| 190 | |
| 191 | // Try to store in freelist for reuse BEFORE tp_clear, so that |
| 192 | // size-based freelists (e.g. PyTuple) can read the payload directly. |
| 193 | // Only exact base types (not heaptype or structseq subtypes) go into the freelist. |
| 194 | let typ = obj_ref.class(); |
| 195 | let pushed = if T::HAS_FREELIST |
| 196 | && typ.heaptype_ext.is_none() |
| 197 | && core::ptr::eq(typ, T::class(crate::vm::Context::genesis())) |
| 198 | { |
| 199 | unsafe { T::freelist_push(obj) } |
| 200 | } else { |
| 201 | false |
| 202 | }; |
| 203 | |
| 204 | // Extract child references to break circular refs (tp_clear). |
| 205 | // This runs regardless of freelist push — the object's children must be released. |
| 206 | let mut edges = Vec::new(); |
| 207 | if let Some(clear_fn) = vtable.clear { |
| 208 | unsafe { clear_fn(obj, &mut edges) }; |
| 209 | } |
| 210 | |
| 211 | if !pushed { |
| 212 | // Deallocate the object memory (handles ObjExt prefix if present) |
| 213 | unsafe { PyInner::dealloc(obj as *mut PyInner<T>) }; |
| 214 | } |
| 215 |
nothing calls this directly
no test coverage detected