diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 3ada08f7dc..5936d7535e 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -233,8 +233,6 @@ class TestTracebackType(unittest.TestCase): def raiser(self): raise ValueError - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_attrs(self): try: self.raiser() diff --git a/vm/src/builtins/traceback.rs b/vm/src/builtins/traceback.rs index 05e9944e09..051fa67c8e 100644 --- a/vm/src/builtins/traceback.rs +++ b/vm/src/builtins/traceback.rs @@ -1,4 +1,5 @@ use rustpython_common::lock::PyMutex; +use std::ops::Deref; use super::{PyType, PyTypeRef}; use crate::{ @@ -63,8 +64,57 @@ impl PyTraceback { } #[pygetset(setter)] - fn set_tb_next(&self, value: Option>) { + fn set_tb_next(&self, value: Option>, vm: &VirtualMachine) -> PyResult<()> { + // Check for circular references using Floyd's cycle detection algorithm + if let Some(ref _new_tb) = value { + // Temporarily make the assignment to simulate the new chain + let old_next = self.next.lock().clone(); + *self.next.lock() = value.clone(); + + // Use Floyd's cycle detection on the chain starting from self + let has_cycle = Self::has_cycle_from(self); + + // Restore the original state + *self.next.lock() = old_next; + + if has_cycle { + return Err(vm.new_value_error("circular reference in traceback chain".to_owned())); + } + } + *self.next.lock() = value; + Ok(()) + } + + /// Detect cycles in traceback chain using Floyd's cycle detection algorithm + fn has_cycle_from(start: &PyTraceback) -> bool { + let mut slow = start.tb_next(); + let mut fast = start.tb_next(); + + while let (Some(slow_tb), Some(fast_tb)) = (&slow, &fast) { + // Move slow pointer one step + slow = slow_tb.tb_next(); + + // Move fast pointer two steps + fast = fast_tb.tb_next(); + if let Some(ref fast_tb2) = fast { + fast = fast_tb2.tb_next(); + } else { + break; + } + + // Check if slow and fast pointers meet (cycle detected) + if let (Some(slow_ptr), Some(fast_ptr)) = (&slow, &fast) { + if std::ptr::eq( + slow_ptr.deref().deref() as *const PyTraceback, + fast_ptr.deref().deref() as *const PyTraceback + ) { + return true; + } + } + } + + false } }