diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs
index 19055df25..0eed91f4a 100644
--- a/vm/src/object/core.rs
+++ b/vm/src/object/core.rs
@@ -31,6 +31,7 @@ use std::{
any::TypeId,
borrow::Borrow,
cell::UnsafeCell,
+ collections::HashMap,
fmt,
marker::PhantomData,
mem::ManuallyDrop,
@@ -38,6 +39,11 @@ use std::{
ptr::{self, NonNull},
};
+use once_cell::sync::Lazy;
+
+pub static ID2TYPE: Lazy<PyMutex<HashMap<TypeId, String>>> =
+ Lazy::new(|| PyMutex::new(HashMap::new()));
+
// so, PyObjectRef is basically equivalent to `PyRc<PyInner<dyn PyObjectPayload>>`, except it's
// only one pointer in width rather than 2. We do that by manually creating a vtable, and putting
// a &'static reference to it inside the `PyRc` rather than adjacent to it, like trait objects do.
@@ -109,6 +115,7 @@ impl PyObjVTable {
#[repr(C)]
struct PyInner<T> {
ref_count: RefCount,
+ is_drop: PyMutex<bool>,
// TODO: move typeid into vtable once TypeId::of is const
typeid: TypeId,
vtable: &'static PyObjVTable,
@@ -433,6 +440,7 @@ impl<T: PyObjectPayload> PyInner<T> {
let member_count = typ.slots.member_count;
Box::new(PyInner {
ref_count: RefCount::new(),
+ is_drop: PyMutex::new(false),
typeid: TypeId::of::<T>(),
vtable: PyObjVTable::of::<T>(),
typ: PyAtomicRef::from(typ),
@@ -849,7 +857,15 @@ impl<'a, T: PyObjectPayload> From<&'a Py<T>> for &'a PyObject {
impl Drop for PyObjectRef {
#[inline]
fn drop(&mut self) {
+ if *self.0.is_drop.lock() {
+ error!(
+ "Double drop on PyObjectRef, typeid={:?}, type={:?}",
+ self.0.typeid,
+ ID2TYPE.lock().get(&self.0.typeid)
+ );
+ }
if self.0.ref_count.dec() {
+ *self.0.is_drop.lock() = true;
unsafe { PyObject::drop_slow(self.ptr) }
}
}
@@ -953,7 +969,21 @@ impl<T: PyObjectPayload> fmt::Debug for PyRef<T> {
impl<T: PyObjectPayload> Drop for PyRef<T> {
#[inline]
fn drop(&mut self) {
+ if *self.as_object().0.is_drop.lock() {
+ error!(
+ "Double drop on PyRef, type={:?}",
+ std::any::type_name::<T>()
+ );
+ }
+
+ let tid = TypeId::of::<T>();
+ ID2TYPE
+ .lock()
+ .entry(tid)
+ .or_insert_with(|| std::any::type_name::<T>().to_string());
+
if self.0.ref_count.dec() {
+ *self.0.is_drop.lock() = true;
unsafe { PyObject::drop_slow(self.ptr.cast::<PyObject>()) }
}
}
@@ -1136,6 +1166,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
let type_type_ptr = Box::into_raw(Box::new(partially_init!(
PyInner::<PyType> {
ref_count: RefCount::new(),
+ is_drop: PyMutex::new(false),
typeid: TypeId::of::<PyType>(),
vtable: PyObjVTable::of::<PyType>(),
dict: None,
@@ -1148,6 +1179,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
let object_type_ptr = Box::into_raw(Box::new(partially_init!(
PyInner::<PyType> {
ref_count: RefCount::new(),
+ is_drop: PyMutex::new(false),
typeid: TypeId::of::<PyType>(),
vtable: PyObjVTable::of::<PyType>(),
dict: None,
Bug behavior
Added a
is_dropfield in PyInner. and found out bothBufferedReader&FileIOgets double drop? Did some search on the code, no clue where misses aclone()to PyRef or PyObjectRefPossible root of bug
a obscure memcpy on a struct containing a PyRef/PyObjectRef
Necessity of fixing the bug
Useful for writing garbage collecting, also prevent UB
Part of the log
the remaining of log file just basically repeats those lines. The command is just
cargo run, and alright it did run, but with all those double drop:log_buf.log
Details
Test Code
added
is_dropfield toPyInner<T>in core.rsThis is the
git diffpatch file(Also added abacktracecrate for debugging in this patch)drop_guard.patch.txt
And here is core code to check double dropping:
Details