diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 8f87fa8571a..b9cfca6df21 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -957,8 +957,6 @@ def test_getsource_stdlib_decimal(self): class TestGetsourceInteractive(unittest.TestCase): @support.force_not_colorized - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getclasses_interactive(self): # bpo-44648: simulate a REPL session; # there is no `__file__` in the __main__ module diff --git a/Lib/test/test_unittest/test_case.py b/Lib/test/test_unittest/test_case.py index ae6a2b94dba..82a442a04e6 100644 --- a/Lib/test/test_unittest/test_case.py +++ b/Lib/test/test_unittest/test_case.py @@ -1970,8 +1970,6 @@ def testNoCycles(self): del case self.assertFalse(wr()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_no_exception_leak(self): # Issue #19880: TestCase.run() should not keep a reference # to the exception diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 6500f578dca..1f74d07f87c 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1739,7 +1739,42 @@ impl ExecutingFrame<'_> { // We do not have any more blocks to unwind. Inspect the reason we are here: match reason { UnwindReason::Raising { exception } => Err(exception), - UnwindReason::Returning { value } => Ok(Some(ExecutionResult::Return(value))), + UnwindReason::Returning { value } => { + // Clear tracebacks of exceptions in fastlocals to break reference cycles. + // This is needed because when returning from inside an except block, + // the exception cleanup code (e = None; del e) is skipped, leaving the + // exception with a traceback that references this frame, which references + // the exception in fastlocals, creating a cycle that can't be collected + // since RustPython doesn't have a tracing GC. + // + // We only clear tracebacks of exceptions that: + // 1. Are not the return value itself (will be needed by caller) + // 2. Are not the current active exception (still being handled) + // 3. Have a traceback whose top frame is THIS frame (we created it) + let current_exc = vm.current_exception(); + let fastlocals = self.fastlocals.lock(); + for obj in fastlocals.iter().flatten() { + // Skip if this object is the return value + if obj.is(&value) { + continue; + } + if let Ok(exc) = obj.clone().downcast::() { + // Skip if this is the current active exception + if current_exc.as_ref().is_some_and(|cur| exc.is(cur)) { + continue; + } + // Only clear if traceback's top frame is this frame + if exc + .__traceback__() + .is_some_and(|tb| core::ptr::eq::>(&*tb.frame, self.object)) + { + exc.set_traceback_typed(None); + } + } + } + drop(fastlocals); + Ok(Some(ExecutionResult::Return(value))) + } UnwindReason::Break { .. } | UnwindReason::Continue { .. } => { self.fatal("break or continue must occur within a loop block.") } // UnwindReason::NoWorries => Ok(None), diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index b5f10e47aa1..07ab4b833d2 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -115,11 +115,14 @@ impl VirtualMachine { .compile(source, compiler::Mode::Exec, source_path.clone()) .map_err(|err| self.new_syntax_error(&err, Some(source)))?; // trace!("Code object: {:?}", code_obj.borrow()); - scope.globals.set_item( - identifier!(self, __file__), - self.new_pyobj(source_path), - self, - )?; + // Only set __file__ for real file paths, not pseudo-paths like + if !(source_path.starts_with('<') && source_path.ends_with('>')) { + scope.globals.set_item( + identifier!(self, __file__), + self.new_pyobj(source_path), + self, + )?; + } self.run_code_obj(code_obj, scope) }