-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add GC infrastructure: object tracking, tp_clear, and helper methods #6994
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,7 @@ fielddesc | |
| fieldlist | ||
| fileutils | ||
| finalbody | ||
| finalizers | ||
| flowgraph | ||
| formatfloat | ||
| freevar | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,7 +82,7 @@ use core::{ | |
| pub(super) struct Erased; | ||
|
|
||
| /// Default dealloc: handles __del__, weakref clearing, tp_clear, and memory free. | ||
| /// Equivalent to subtype_dealloc in CPython. | ||
| /// Equivalent to subtype_dealloc. | ||
| pub(super) unsafe fn default_dealloc<T: PyPayload>(obj: *mut PyObject) { | ||
| let obj_ref = unsafe { &*(obj as *const PyObject) }; | ||
| if let Err(()) = obj_ref.drop_slow_inner() { | ||
|
|
@@ -383,58 +383,101 @@ impl WeakRefList { | |
| weak | ||
| } | ||
|
|
||
| /// PyObject_ClearWeakRefs: clear all weakrefs when the referent dies. | ||
| /// Clear all weakrefs and call their callbacks. | ||
| /// Called when the owner object is being dropped. | ||
| // PyObject_ClearWeakRefs | ||
| fn clear(&self, obj: &PyObject) { | ||
| let obj_addr = obj as *const PyObject as usize; | ||
| let mut to_callback: Vec<(PyRef<PyWeak>, PyObjectRef)> = Vec::new(); | ||
| let _lock = weakref_lock::lock(obj_addr); | ||
|
|
||
| { | ||
| let _lock = weakref_lock::lock(obj_addr); | ||
| // Clear generic cache | ||
| self.generic.store(ptr::null_mut(), Ordering::Relaxed); | ||
|
|
||
| // Walk the list, collecting weakrefs with callbacks | ||
| let mut current = NonNull::new(self.head.load(Ordering::Relaxed)); | ||
| while let Some(node) = current { | ||
| let next = unsafe { WeakLink::pointers(node).as_ref().get_next() }; | ||
| // Walk the list, collecting weakrefs with callbacks | ||
| let mut callbacks: Vec<(PyRef<PyWeak>, PyObjectRef)> = Vec::new(); | ||
| let mut current = NonNull::new(self.head.load(Ordering::Relaxed)); | ||
| while let Some(node) = current { | ||
| let next = unsafe { WeakLink::pointers(node).as_ref().get_next() }; | ||
|
|
||
| let wr = unsafe { node.as_ref() }; | ||
| let wr = unsafe { node.as_ref() }; | ||
|
|
||
| // Set wr_object to null (marks weakref as dead) | ||
| wr.0.payload | ||
| .wr_object | ||
| .store(ptr::null_mut(), Ordering::Relaxed); | ||
| // Mark weakref as dead | ||
| wr.0.payload | ||
| .wr_object | ||
| .store(ptr::null_mut(), Ordering::Relaxed); | ||
|
|
||
| // Unlink from list | ||
| unsafe { | ||
| let mut ptrs = WeakLink::pointers(node); | ||
| ptrs.as_mut().set_prev(None); | ||
| ptrs.as_mut().set_next(None); | ||
| } | ||
| // Unlink from list | ||
| unsafe { | ||
| let mut ptrs = WeakLink::pointers(node); | ||
| ptrs.as_mut().set_prev(None); | ||
| ptrs.as_mut().set_next(None); | ||
| } | ||
|
|
||
| // Collect callback if weakref is still alive (strong_count > 0) | ||
| if wr.0.ref_count.get() > 0 { | ||
| let cb = unsafe { wr.0.payload.callback.get().replace(None) }; | ||
| if let Some(cb) = cb { | ||
| to_callback.push((wr.to_owned(), cb)); | ||
| } | ||
| // Collect callback if present and weakref is still alive | ||
| if wr.0.ref_count.get() > 0 { | ||
| let cb = unsafe { wr.0.payload.callback.get().replace(None) }; | ||
| if let Some(cb) = cb { | ||
| callbacks.push((wr.to_owned(), cb)); | ||
| } | ||
|
|
||
| current = next; | ||
| } | ||
|
|
||
| self.head.store(ptr::null_mut(), Ordering::Relaxed); | ||
| self.generic.store(ptr::null_mut(), Ordering::Relaxed); | ||
| current = next; | ||
| } | ||
| self.head.store(ptr::null_mut(), Ordering::Relaxed); | ||
|
|
||
| // Call callbacks without holding the lock | ||
| for (wr, cb) in to_callback { | ||
| // Invoke callbacks outside the lock | ||
| drop(_lock); | ||
| for (wr, cb) in callbacks { | ||
| crate::vm::thread::with_vm(&cb, |vm| { | ||
| // TODO: handle unraisable exception | ||
| let wr_obj: PyObjectRef = wr.clone().into(); | ||
| let _ = cb.call((wr_obj,), vm); | ||
| let _ = cb.call((wr.clone(),), vm); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /// Clear all weakrefs but DON'T call callbacks. Instead, return them for later invocation. | ||
| /// Used by GC to ensure ALL weakrefs are cleared BEFORE any callbacks are invoked. | ||
| /// handle_weakrefs() clears all weakrefs first, then invokes callbacks. | ||
| fn clear_for_gc_collect_callbacks(&self, obj: &PyObject) -> Vec<(PyRef<PyWeak>, PyObjectRef)> { | ||
| let obj_addr = obj as *const PyObject as usize; | ||
| let _lock = weakref_lock::lock(obj_addr); | ||
|
|
||
| // Clear generic cache | ||
| self.generic.store(ptr::null_mut(), Ordering::Relaxed); | ||
|
|
||
| let mut callbacks = Vec::new(); | ||
| let mut current = NonNull::new(self.head.load(Ordering::Relaxed)); | ||
| while let Some(node) = current { | ||
| let next = unsafe { WeakLink::pointers(node).as_ref().get_next() }; | ||
|
|
||
| let wr = unsafe { node.as_ref() }; | ||
|
|
||
| // Mark weakref as dead | ||
| wr.0.payload | ||
| .wr_object | ||
| .store(ptr::null_mut(), Ordering::Relaxed); | ||
|
|
||
| // Unlink from list | ||
| unsafe { | ||
| let mut ptrs = WeakLink::pointers(node); | ||
| ptrs.as_mut().set_prev(None); | ||
| ptrs.as_mut().set_next(None); | ||
| } | ||
|
|
||
| // Collect callback without invoking | ||
| if wr.0.ref_count.get() > 0 { | ||
| let cb = unsafe { wr.0.payload.callback.get().replace(None) }; | ||
| if let Some(cb) = cb { | ||
| callbacks.push((wr.to_owned(), cb)); | ||
| } | ||
| } | ||
|
|
||
| current = next; | ||
| } | ||
| self.head.store(ptr::null_mut(), Ordering::Relaxed); | ||
|
|
||
| callbacks | ||
| } | ||
|
|
||
| fn count(&self, obj: &PyObject) -> usize { | ||
| let _lock = weakref_lock::lock(obj as *const PyObject as usize); | ||
| let mut count = 0usize; | ||
|
|
@@ -1044,13 +1087,20 @@ impl PyObject { | |
| } | ||
|
|
||
| // __del__ should only be called once (like _PyGC_FINALIZED check in GIL_DISABLED) | ||
| // We call __del__ BEFORE clearing weakrefs to allow the finalizer to access | ||
| // the object's weak references if needed. | ||
| let del = self.class().slots.del.load(); | ||
| if let Some(slot_del) = del | ||
| && !self.gc_finalized() | ||
| { | ||
| self.set_gc_finalized(); | ||
| call_slot_del(self, slot_del)?; | ||
| } | ||
|
|
||
| // Clear weak refs AFTER __del__. | ||
| // Note: This differs from GC behavior which clears weakrefs before finalizers, | ||
| // but for direct deallocation (drop_slow_inner), we need to allow the finalizer | ||
| // to run without triggering use-after-free from WeakRefList operations. | ||
| if let Some(wrl) = self.weak_ref_list() { | ||
| wrl.clear(self); | ||
| } | ||
|
|
@@ -1097,6 +1147,104 @@ impl PyObject { | |
| }); | ||
| result | ||
| } | ||
|
|
||
| /// Call __del__ if present, without triggering object deallocation. | ||
| /// Used by GC to call finalizers before breaking cycles. | ||
| /// This allows proper resurrection detection. | ||
| /// CPython: PyObject_CallFinalizerFromDealloc in Objects/object.c | ||
| pub fn try_call_finalizer(&self) { | ||
| let del = self.class().slots.del.load(); | ||
| if let Some(slot_del) = del | ||
| && !self.gc_finalized() | ||
| { | ||
| // Mark as finalized BEFORE calling __del__ to prevent double-call | ||
| // This ensures drop_slow_inner() won't call __del__ again | ||
| self.set_gc_finalized(); | ||
| let result = crate::vm::thread::with_vm(self, |vm| { | ||
| if let Err(e) = slot_del(self, vm) | ||
| && let Some(del_method) = self.get_class_attr(identifier!(vm, __del__)) | ||
| { | ||
| vm.run_unraisable(e, None, del_method); | ||
| } | ||
| }); | ||
| let _ = result; | ||
| } | ||
| } | ||
|
|
||
| /// Clear weakrefs but collect callbacks instead of calling them. | ||
| /// This is used by GC to ensure ALL weakrefs are cleared BEFORE any callbacks run. | ||
| /// Returns collected callbacks as (PyRef<PyWeak>, callback) pairs. | ||
| // = handle_weakrefs | ||
| pub fn gc_clear_weakrefs_collect_callbacks(&self) -> Vec<(PyRef<PyWeak>, PyObjectRef)> { | ||
| if let Some(wrl) = self.weak_ref_list() { | ||
| wrl.clear_for_gc_collect_callbacks(self) | ||
| } else { | ||
| vec![] | ||
| } | ||
| } | ||
|
|
||
| /// Get raw pointers to referents without incrementing reference counts. | ||
| /// This is used during GC to avoid reference count manipulation. | ||
| /// tp_traverse visits objects without incref | ||
| /// | ||
| /// # Safety | ||
| /// The returned pointers are only valid as long as the object is alive | ||
| /// and its contents haven't been modified. | ||
| pub unsafe fn gc_get_referent_ptrs(&self) -> Vec<NonNull<PyObject>> { | ||
| let mut result = Vec::new(); | ||
| // Traverse the entire object including dict and slots | ||
| self.0.traverse(&mut |child: &PyObject| { | ||
| result.push(NonNull::from(child)); | ||
| }); | ||
| result | ||
| } | ||
|
|
||
| /// Pop edges from this object for cycle breaking. | ||
| /// Returns extracted child references that were removed from this object (tp_clear). | ||
| /// This is used during garbage collection to break circular references. | ||
| /// | ||
| /// # Safety | ||
| /// - ptr must be a valid pointer to a PyObject | ||
| /// - The caller must have exclusive access (no other references exist) | ||
| /// - This is only safe during GC when the object is unreachable | ||
| pub unsafe fn gc_clear_raw(ptr: *mut PyObject) -> Vec<PyObjectRef> { | ||
| let mut result = Vec::new(); | ||
| let obj = unsafe { &*ptr }; | ||
|
|
||
| // 1. Clear payload-specific references (vtable.clear / tp_clear) | ||
| if let Some(clear_fn) = obj.0.vtable.clear { | ||
| unsafe { clear_fn(ptr, &mut result) }; | ||
| } | ||
|
|
||
| // 2. Clear member slots (subtype_clear) | ||
| for slot in obj.0.slots.iter() { | ||
| if let Some(val) = slot.write().take() { | ||
| result.push(val); | ||
| } | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| result | ||
| } | ||
|
|
||
| /// Clear this object for cycle breaking (tp_clear). | ||
| /// This version takes &self but should only be called during GC | ||
| /// when exclusive access is guaranteed. | ||
| /// | ||
| /// # Safety | ||
| /// - The caller must guarantee exclusive access (no other references exist) | ||
| /// - This is only safe during GC when the object is unreachable | ||
| pub unsafe fn gc_clear(&self) -> Vec<PyObjectRef> { | ||
| // SAFETY: During GC collection, this object is unreachable (gc_refs == 0), | ||
| // meaning no other code has a reference to it. The only references are | ||
| // internal cycle references which we're about to break. | ||
| unsafe { Self::gc_clear_raw(self as *const _ as *mut PyObject) } | ||
| } | ||
|
|
||
| /// Check if this object has clear capability (tp_clear) | ||
| // Py_TPFLAGS_HAVE_GC types have tp_clear | ||
| pub fn gc_has_clear(&self) -> bool { | ||
| self.0.vtable.clear.is_some() || self.0.dict.is_some() || !self.0.slots.is_empty() | ||
| } | ||
|
Comment on lines
+1210
to
+1247
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if dict is traversed (would be counted in GC) but not cleared
# Look for Traverse implementation on PyInner or dict handling
echo "=== Checking Traverse implementation for dict ==="
rg -n "impl.*Traverse.*for.*PyInner" crates/vm/src/ -A 20
echo ""
echo "=== Checking if InstanceDict implements clear ==="
rg -n "InstanceDict" crates/vm/src/object/ -A 5 -B 2
echo ""
echo "=== Checking CPython-style subtype_clear patterns ==="
rg -n "subtype_clear|tp_clear.*dict" crates/vm/src/Repository: RustPython/RustPython Length of output: 7211 🏁 Script executed: #!/bin/bash
# Need to check if PyDict type itself has GC clearing
echo "=== Check PyDict type and its clear/tp_clear implementation ==="
rg -n "PyDict|tp_clear" crates/vm/src/builtins/dict.rs -A 3 -B 1 | head -100
echo ""
echo "=== Search for any dict.clear in gc_clear context ==="
rg -n "gc_clear" crates/vm/src/object/core.rs -B 2 -A 10
echo ""
echo "=== Check if dict entries are cleared as part of PyDict's vtable.clear ==="
rg -n "vtable.*clear" crates/vm/src/ -A 5 | grep -A 5 "dict"
echo ""
echo "=== Check try_clear_obj function to see how clearing works ==="
rg -n "try_clear_obj" crates/vm/src/ -A 15Repository: RustPython/RustPython Length of output: 9356 🏁 Script executed: #!/bin/bash
# Check if PyDict implements try_clear or MaybeClear
echo "=== Search for try_clear impl for PyDict ==="
rg -n "impl.*try_clear.*PyDict|impl.*MaybeClear.*PyDict" crates/vm/src/ -A 10
echo ""
echo "=== Check MaybeClear trait definition ==="
rg -n "trait MaybeClear|fn try_clear" crates/vm/src/object/core.rs -A 8
echo ""
echo "=== Verify: does PyDict instance have GC clearing? Search for PyPayload impl ==="
rg -n "impl PyPayload for PyDict" crates/vm/src/ -A 20
echo ""
echo "=== Check what happens when an instance dict field exists ==="
rg -n "instance.*dict|dict.*instance" crates/vm/src/object/core.rs -B 2 -A 2 | head -60Repository: RustPython/RustPython Length of output: 3017 🏁 Script executed: #!/bin/bash
# Check default try_clear implementation
echo "=== Search for default try_clear implementation ==="
rg -n "fn try_clear|impl.*try_clear" crates/vm/src/object/core.rs -B 2 -A 5
echo ""
echo "=== Search for PyPayload trait try_clear method ==="
rg -n "trait PyPayload" crates/vm/src/object/payload.rs -A 30
echo ""
echo "=== Verify dict is not cleared by looking at gc_clear_raw in detail ==="
sed -n '1210,1230p' crates/vm/src/object/core.rs
echo ""
echo "=== Check if there's any dict clearing elsewhere in gc flow ==="
rg -n "dict.*clear\|clear.*dict" crates/vm/src/gc.rsRepository: RustPython/RustPython Length of output: 2410 🏁 Script executed: #!/bin/bash
# Find try_clear trait definition more broadly
echo "=== Search for try_clear trait definition ==="
rg -n "try_clear" crates/vm/src/object/ -B 3 -A 3 | head -80
echo ""
echo "=== Find GC collection code ==="
fd "*.rs" crates/vm/src/ | xargs grep -l "gc_clear\|GC\|garbage" | head -10
echo ""
echo "=== Search for where gc_clear_raw is called ==="
rg -n "gc_clear_raw\|gc_clear\(" crates/vm/src/ -B 2 -A 2Repository: RustPython/RustPython Length of output: 2770 🏁 Script executed: #!/bin/bash
# Search for where gc_clear_raw is called with proper escaping
echo "=== Search for where gc_clear_raw is called ==="
rg "gc_clear_raw|gc_clear" crates/vm/src/ -B 1 -A 1
echo ""
echo "=== Check HAS_CLEAR definition ==="
rg "HAS_CLEAR" crates/vm/src/object/ -B 3 -A 3
echo ""
echo "=== Check if MaybeClear trait has HAS_CLEAR ==="
rg "trait MaybeClear|HAS_CLEAR" crates/vm/src/object/ -B 2 -A 5Repository: RustPython/RustPython Length of output: 3657 🏁 Script executed: #!/bin/bash
# Search more specifically for where gc_clear is called in the codebase
echo "=== Search for calls to gc_clear (not definitions) ==="
rg "\.gc_clear\(|gc_clear_raw\(" crates/vm/src/ -B 2 -A 2
echo ""
echo "=== Search in all files for gc_clear usage ==="
git ls-files crates/vm/src/ | xargs grep -l "gc_clear" | head -20
echo ""
echo "=== Check the actual file that calls gc_clear ==="
git ls-files crates/vm/src/ | xargs grep "\.gc_clear\(" -B 3 -A 3Repository: RustPython/RustPython Length of output: 1099 🏁 Script executed: #!/bin/bash
# Fix the grep search
echo "=== Search for calls to gc_clear ==="
rg "gc_clear" crates/vm/src/ | grep -v "pub unsafe fn\|pub fn\|Clear this object\|SAFETY"
echo ""
echo "=== Check what files call gc_clear_raw or gc_clear ==="
rg "gc_clear" crates/ --type rust | grep -v "pub unsafe\|pub fn\|//\|Clear this"
echo ""
echo "=== Look for GC-related module ==="
fd "gc\|collect" crates/vm/src/ --type f
echo ""
echo "=== Check imports of gc_clear in traverse_object or related files ==="
rg "use.*gc_clear|from.*gc_clear" crates/vm/src/Repository: RustPython/RustPython Length of output: 468 🏁 Script executed: #!/bin/bash
# Search for GC collection, garbage collection, or cycle-related code
echo "=== Search for garbage collection or cycle handling ==="
rg "garbage|collection|cycle|dealloc" crates/vm/src/ --type rust | head -50
echo ""
echo "=== Check if gc_clear is public API that's exported ==="
rg "pub.*gc_clear|pub use.*gc_clear" crates/vm/src/
echo ""
echo "=== Search broader for deallocation and cleanup ==="
rg "dealloc|__del__|finalize|cleanup" crates/vm/src/object/ -B 1 -A 3 | head -60
echo ""
echo "=== Check what gc_has_clear is used for ==="
rg "gc_has_clear" crates/vm/src/ -B 2 -A 2Repository: RustPython/RustPython Length of output: 9664 🏁 Script executed: #!/bin/bash
# Check default_dealloc implementation fully
echo "=== Full default_dealloc implementation ==="
rg "pub\(super\) unsafe fn default_dealloc" crates/vm/src/object/core.rs -A 30
echo ""
echo "=== Check drop_slow_inner implementation ==="
rg "fn drop_slow_inner" crates/vm/src/object/core.rs -A 20
echo ""
echo "=== Check stdlib/gc.rs for actual GC collection logic ==="
head -200 crates/vm/src/stdlib/gc.rs | tail -100Repository: RustPython/RustPython Length of output: 5769 🏁 Script executed: #!/bin/bash
# Now check if gc_has_clear is actually used for GC collection decisions
echo "=== Full gc.rs file to understand GC collection ==="
cat crates/vm/src/stdlib/gc.rs
echo ""
echo "=== Search for gc_has_clear usage ==="
rg "gc_has_clear" crates/ -B 3 -A 3Repository: RustPython/RustPython Length of output: 8982 🏁 Script executed: #!/bin/bash
# Search for all usages of gc_has_clear across the entire codebase
echo "=== Search ALL usages of gc_has_clear ==="
rg "gc_has_clear" crates/ -r '$0' | head -20
echo ""
echo "=== Check gc_state module for actual collection logic ==="
fd "gc_state" crates/ --type f
echo ""
echo "=== Now let's verify: does dict actually participate in cycle detection? ==="
echo "Looking at how dict entries are part of GC traversal..."
rg "impl.*Traverse.*for.*InstanceDict|impl.*Traverse.*for.*PyDict" crates/vm/src/ -A 5Repository: RustPython/RustPython Length of output: 1163 🏁 Script executed: #!/bin/bash
# Get the complete clear implementation for PyDict
echo "=== Full clear() implementation for PyDict ==="
rg "impl Traverse for PyDict" crates/vm/src/builtins/dict.rs -A 20
echo ""
echo "=== Verify: does PyDict set HAS_CLEAR? ==="
rg "impl.*PyPayload.*for.*PyDict" crates/vm/src/builtins/dict.rs -A 10
echo ""
echo "=== Check: MaybeTraverse implementation for PyDict ==="
rg "impl.*MaybeTraverse.*for.*PyDict|HAS_TRAVERSE.*PyDict\|HAS_CLEAR.*PyDict" crates/vm/src/ -B 5 -A 10Repository: RustPython/RustPython Length of output: 1108 🏁 Script executed: #!/bin/bash
# Check if PyDict implements MaybeTraverse
echo "=== Check if PyDict has MaybeTraverse impl ==="
rg "impl.*MaybeTraverse.*for.*PyDict|impl.*Traverse.*for.*PyDict" crates/vm/src/builtins/dict.rs -B 2 -A 15
echo ""
echo "=== Check if Traverse impl for PyDict means HAS_CLEAR is true ==="
echo "Looking at how Traverse.clear maps to HAS_CLEAR..."
rg "Traverse.*clear\|clear.*Traverse" crates/vm/src/object/ -B 3 -A 3
echo ""
echo "=== Now the key question: when an object has dict field, is its clear() called? ==="
echo "Look at gc_clear_raw again and how it calls vtable.clear"
sed -n '1210,1230p' crates/vm/src/object/core.rs
echo ""
echo "=== Check if dict.clear() is invoked somewhere else ==="
rg "dict.*clear\(\)|\.clear\(\).*dict" crates/vm/src/object/ -B 2 -A 2Repository: RustPython/RustPython Length of output: 1658 🏁 Script executed: #!/bin/bash
# Let me trace through the logic one more time to be absolutely sure
echo "=== Confirm: instance dict field is NOT cleared in gc_clear_raw ==="
sed -n '1210,1230p' crates/vm/src/object/core.rs
echo ""
echo "=== Instance dict is a field in PyInner struct ==="
sed -n '160,170p' crates/vm/src/object/core.rs
echo ""
echo "=== InstanceDict has a clear capability? Check Traverse impl for InstanceDict ==="
rg "impl.*Traverse.*for.*InstanceDict" crates/vm/src/ -A 10
echo ""
echo "=== The KEY test: if an object instance has dict, should dict entries be cleared? ==="
echo "Let's check how the slots are cleared to understand the pattern..."
sed -n '1219,1230p' crates/vm/src/object/core.rs
echo ""
echo "=== Compare with default_dealloc to see if it clears dict ==="
sed -n '1140,1165p' crates/vm/src/object/core.rsRepository: RustPython/RustPython Length of output: 4000 🏁 Script executed: #!/bin/bash
# Final verification: check InstanceDict methods and implementation
echo "=== Full InstanceDict implementation ==="
sed -n '643,700p' crates/vm/src/object/core.rs
echo ""
echo "=== Check if InstanceDict has any clear-like method ==="
rg "impl.*InstanceDict" crates/vm/src/object/core.rs -A 30
echo ""
echo "=== Check PyDict drain_entries method ==="
rg "drain_entries" crates/vm/src/builtins/dict.rs -B 3 -A 10
echo ""
echo "=== Final confirmation: slots.iter() in gc_clear_raw ==="
echo "Slots are cleared by iterating and taking ownership. Dict is NOT."
echo "This confirms the bug: dict field is not cleared while slots are."Repository: RustPython/RustPython Length of output: 3358 Instance dict entries are not cleared during GC cycle breaking, but
The instance dict field ( Add dict clearing to Suggested fix// 3. Clear instance dict
if let Some(ref instance_dict) = obj.0.dict {
let old_dict = instance_dict.replace(vm.ctx.new_dict());
result.push(old_dict.into());
}Or remove 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| impl Borrow<PyObject> for PyObjectRef { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.