Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 25 additions & 31 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ unsafe impl crate::object::Traverse for PyType {
fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn<'_>) {
self.base.traverse(tracer_fn);
self.bases.traverse(tracer_fn);
self.mro.traverse(tracer_fn);
// mro contains self as mro[0], so skip traversing to avoid circular reference
// self.mro.traverse(tracer_fn);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Uncommenting MRO traversal may be necessary for proper garbage collection.

The MRO field contains references to types that should be traversed. Commenting this out could lead to memory leaks or incorrect reference cycle detection.

-        // self.mro.traverse(tracer_fn);
+        self.mro.traverse(tracer_fn);
🤖 Prompt for AI Agents
In vm/src/builtins/type.rs at line 51, the call to traverse the MRO field is
commented out, which may cause memory leaks or incorrect reference cycle
detection. Uncomment the line that calls self.mro.traverse(tracer_fn) to ensure
proper traversal of references in the MRO field during garbage collection.

self.subclasses.traverse(tracer_fn);
self.attributes
.read_recursive()
Expand Down Expand Up @@ -341,6 +342,7 @@ impl PyType {
metaclass,
None,
);
new_type.mro.write().insert(0, new_type.clone());

new_type.init_slots(ctx);

Expand Down Expand Up @@ -393,6 +395,7 @@ impl PyType {
metaclass,
None,
);
new_type.mro.write().insert(0, new_type.clone());

// Note: inherit_slots is called in PyClassImpl::init_class after
// slots are fully initialized by make_slots()
Expand All @@ -413,9 +416,8 @@ impl PyType {
}

pub(crate) fn init_slots(&self, ctx: &Context) {
// Inherit slots from MRO
// Note: self.mro does NOT include self, so we iterate all elements
let mro: Vec<_> = self.mro.read().iter().cloned().collect();
// Inherit slots from MRO (mro[0] is self, so skip it)
let mro: Vec<_> = self.mro.read()[1..].to_vec();
for base in mro.iter() {
self.inherit_slots(base);
}
Expand All @@ -424,7 +426,8 @@ impl PyType {
#[allow(clippy::mutable_key_type)]
let mut slot_name_set = std::collections::HashSet::new();

for cls in self.mro.read().iter() {
// mro[0] is self, so skip it; self.attributes is checked separately below
for cls in self.mro.read()[1..].iter() {
for &name in cls.attributes.read().keys() {
if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") {
slot_name_set.insert(name);
Expand Down Expand Up @@ -503,18 +506,12 @@ impl PyType {
/// Equivalent to CPython's find_name_in_mro
/// Look in tp_dict of types in MRO - bypasses descriptors and other attribute access machinery
fn find_name_in_mro(&self, name: &'static PyStrInterned) -> Option<PyObjectRef> {
// First check in our own dict
if let Some(value) = self.attributes.read().get(name) {
return Some(value.clone());
}

// Then check in MRO
for base in self.mro.read().iter() {
if let Some(value) = base.attributes.read().get(name) {
// mro[0] is self, so we just iterate through the entire MRO
for cls in self.mro.read().iter() {
if let Some(value) = cls.attributes.read().get(name) {
return Some(value.clone());
}
}

None
}

Expand All @@ -530,18 +527,15 @@ impl PyType {
}

pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
self.mro
.read()
self.mro.read()[1..]
.iter()
.find_map(|class| class.attributes.read().get(attr_name).cloned())
}

// This is the internal has_attr implementation for fast lookup on a class.
pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool {
self.attributes.read().contains_key(attr_name)
|| self
.mro
.read()
|| self.mro.read()[1..]
.iter()
.any(|c| c.attributes.read().contains_key(attr_name))
}
Expand All @@ -550,10 +544,8 @@ impl PyType {
// Gather all members here:
let mut attributes = PyAttributes::default();

for bc in core::iter::once(self)
.chain(self.mro.read().iter().map(|cls| -> &Self { cls }))
.rev()
{
// mro[0] is self, so we iterate through the entire MRO in reverse
for bc in self.mro.read().iter().map(|cls| -> &Self { cls }).rev() {
for (name, value) in bc.attributes.read().iter() {
attributes.insert(name.to_owned(), value.clone());
}
Expand Down Expand Up @@ -661,22 +653,21 @@ impl Py<PyType> {
/// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic
/// method.
pub fn fast_issubclass(&self, cls: &impl Borrow<PyObject>) -> bool {
self.as_object().is(cls.borrow()) || self.mro.read().iter().any(|c| c.is(cls.borrow()))
self.as_object().is(cls.borrow()) || self.mro.read()[1..].iter().any(|c| c.is(cls.borrow()))
}

pub fn mro_map_collect<F, R>(&self, f: F) -> Vec<R>
where
F: Fn(&Self) -> R,
{
core::iter::once(self)
.chain(self.mro.read().iter().map(|x| x.deref()))
.map(f)
.collect()
self.mro.read().iter().map(|x| x.deref()).map(f).collect()
}

pub fn mro_collect(&self) -> Vec<PyRef<PyType>> {
core::iter::once(self)
.chain(self.mro.read().iter().map(|x| x.deref()))
self.mro
.read()
.iter()
.map(|x| x.deref())
.map(|x| x.to_owned())
.collect()
}
Expand Down Expand Up @@ -745,8 +736,11 @@ impl PyType {
*zelf.bases.write() = bases;
// Recursively update the mros of this class and all subclasses
fn update_mro_recursively(cls: &PyType, vm: &VirtualMachine) -> PyResult<()> {
*cls.mro.write() =
let mut mro =
PyType::resolve_mro(&cls.bases.read()).map_err(|msg| vm.new_type_error(msg))?;
// Preserve self (mro[0]) when updating MRO
mro.insert(0, cls.mro.read()[0].to_owned());
*cls.mro.write() = mro;
for subclass in cls.subclasses.write().iter() {
let subclass = subclass.upgrade().unwrap();
let subclass: &Py<PyType> = subclass.downcast_ref().unwrap();
Expand Down
8 changes: 7 additions & 1 deletion crates/vm/src/object/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1310,12 +1310,16 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
ptr::write(&mut (*type_type_ptr).typ, PyAtomicRef::from(type_type));

let object_type = PyTypeRef::from_raw(object_type_ptr.cast());
// object's mro is [object]
(*object_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]);

(*type_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]);
(*type_type_ptr).payload.bases = PyRwLock::new(vec![object_type.clone()]);
(*type_type_ptr).payload.base = Some(object_type.clone());

let type_type = PyTypeRef::from_raw(type_type_ptr.cast());
// type's mro is [type, object]
(*type_type_ptr).payload.mro =
PyRwLock::new(vec![type_type.clone(), object_type.clone()]);

(type_type, object_type)
}
Expand All @@ -1331,6 +1335,8 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
heaptype_ext: None,
};
let weakref_type = PyRef::new_ref(weakref_type, type_type.clone(), None);
// weakref's mro is [weakref, object]
weakref_type.mro.write().insert(0, weakref_type.clone());

object_type.subclasses.write().push(
type_type
Expand Down
7 changes: 4 additions & 3 deletions crates/vm/src/types/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,9 @@ impl PyType {
let in_self = cmp_names.iter().any(|n| attrs.contains_key(*n));
drop(attrs);

// mro[0] is self, so skip it since we already checked self above
in_self
|| self.mro.read().iter().any(|cls| {
|| self.mro.read()[1..].iter().any(|cls| {
let attrs = cls.attributes.read();
cmp_names.iter().any(|n| {
if let Some(attr) = attrs.get(*n) {
Expand Down Expand Up @@ -1273,8 +1274,8 @@ impl PyType {
return None;
}

// Look up in MRO
for cls in self.mro.read().iter() {
// Look up in MRO (mro[0] is self, so skip it)
for cls in self.mro.read()[1..].iter() {
if let Some(attr) = cls.attributes.read().get(name).cloned() {
if let Some(func) = try_extract(&attr) {
return Some(func);
Expand Down
5 changes: 3 additions & 2 deletions crates/vm/src/types/slot_defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,9 @@ impl SlotAccessor {

/// Inherit slot value from MRO
pub fn inherit_from_mro(&self, typ: &crate::builtins::PyType) {
// Note: typ.mro does NOT include typ itself
let mro = typ.mro.read();
// mro[0] is self, so skip it
let mro_guard = typ.mro.read();
let mro = &mro_guard[1..];

macro_rules! inherit_main {
($slot:ident) => {{
Expand Down
Loading