Visitar URL original
Update bdb from v3.14.3 and fix trace_event by youknowone · Pull Request #7255 · RustPython/RustPython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Fix trace_event to return trace function result
- Return trace function's return value from trace_event()
  to support per-frame f_trace assignment
- Match CPython's trace_trampoline: set f_trace from call
  event return value, clear on error
- Fire return event only when frame is traced or profiled
- Remove expectedFailure from passing bdb/settrace tests
  • Loading branch information
youknowone committed Feb 28, 2026
commit 754fc85fb8fde62aba882e39922a4f566d4d9819
4 changes: 0 additions & 4 deletions Lib/test/test_bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,6 @@ def test_skip_with_no_name_module(self):
bdb = Bdb(skip=['anything*'])
self.assertIs(bdb.is_skipped_module(None), False)

@unittest.expectedFailure # TODO: RUSTPYTHON; Error in atexit._run_exitfuncs
def test_down(self):
# Check that set_down() raises BdbError at the newest frame.
self.expect_set = [
Expand All @@ -784,15 +783,13 @@ def test_up(self):
class BreakpointTestCase(BaseTestCase):
"""Test the breakpoint set method."""

@unittest.expectedFailure # TODO: RUSTPYTHON; Error in atexit._run_exitfuncs
def test_bp_on_non_existent_module(self):
self.expect_set = [
('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1))
]
with TracerRun(self) as tracer:
self.assertRaises(BdbError, tracer.runcall, tfunc_import)

@unittest.expectedFailure # TODO: RUSTPYTHON; Error in atexit._run_exitfuncs
def test_bp_after_last_statement(self):
code = """
def main():
Expand Down Expand Up @@ -969,7 +966,6 @@ def main():
with TracerRun(self) as tracer:
tracer.runcall(tfunc_import)

@unittest.expectedFailure # TODO: RUSTPYTHON; Error in atexit._run_exitfuncs
def test_clear_at_no_bp(self):
self.expect_set = [
('line', 2, 'tfunc_import'), ('clear', (__file__, 1))
Expand Down
14 changes: 0 additions & 14 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,6 @@ def test_return(self):
def test_exception(self):
self.run_test_for_event('exception')

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_trash_stack(self):
def f():
for i in range(5):
Expand Down Expand Up @@ -1785,15 +1783,11 @@ async def test_jump_over_async_for_block_before_else(output):

# The second set of 'jump' tests are for things that are not allowed:

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(2, 3, [1], (ValueError, 'after'))
def test_no_jump_too_far_forwards(output):
output.append(1)
output.append(2)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(2, -2, [1], (ValueError, 'before'))
def test_no_jump_too_far_backwards(output):
output.append(1)
Expand Down Expand Up @@ -1840,8 +1834,6 @@ def test_no_jump_to_except_4(output):
output.append(4)
raise e

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(1, 3, [], (ValueError, 'into'))
def test_no_jump_forwards_into_for_block(output):
output.append(1)
Expand All @@ -1857,8 +1849,6 @@ async def test_no_jump_forwards_into_async_for_block(output):
output.append(3)
pass

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(3, 2, [2, 2], (ValueError, 'into'))
def test_no_jump_backwards_into_for_block(output):
for i in 1, 2:
Expand Down Expand Up @@ -2020,8 +2010,6 @@ def test_no_jump_into_bare_except_block_from_try_block(output):
raise
output.append(8)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(3, 6, [2], (ValueError, "into an 'except'"))
def test_no_jump_into_qualified_except_block_from_try_block(output):
try:
Expand Down Expand Up @@ -2087,8 +2075,6 @@ def test_no_jump_over_return_out_of_finally_block(output):
return
output.append(7)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(7, 4, [1, 6], (ValueError, 'into'))
def test_no_jump_into_for_block_before_else(output):
output.append(1)
Expand Down
46 changes: 38 additions & 8 deletions crates/vm/src/protocol/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,37 +96,67 @@ impl core::fmt::Display for TraceEvent {

impl VirtualMachine {
/// Call registered trace function.
///
/// Returns the trace function's return value:
/// - `Some(obj)` if the trace function returned a non-None value
/// - `None` if it returned Python None or no trace function was active
///
/// In CPython's trace protocol:
/// - For 'call' events: the return value determines the per-frame `f_trace`
/// - For 'line'/'return' events: the return value can update `f_trace`
#[inline]
pub(crate) fn trace_event(&self, event: TraceEvent, arg: Option<PyObjectRef>) -> PyResult<()> {
pub(crate) fn trace_event(
&self,
event: TraceEvent,
arg: Option<PyObjectRef>,
) -> PyResult<Option<PyObjectRef>> {
if self.use_tracing.get() {
self._trace_event_inner(event, arg)
} else {
Ok(())
Ok(None)
}
}
fn _trace_event_inner(&self, event: TraceEvent, arg: Option<PyObjectRef>) -> PyResult<()> {
fn _trace_event_inner(
&self,
event: TraceEvent,
arg: Option<PyObjectRef>,
) -> PyResult<Option<PyObjectRef>> {
let trace_func = self.trace_func.borrow().to_owned();
let profile_func = self.profile_func.borrow().to_owned();
if self.is_none(&trace_func) && self.is_none(&profile_func) {
return Ok(());
return Ok(None);
}

let Some(frame_ref) = self.current_frame() else {
return Ok(());
return Ok(None);
};

let frame: PyObjectRef = frame_ref.into();
let event = self.ctx.new_str(event.to_string()).into();
let args = vec![frame, event, arg.unwrap_or_else(|| self.ctx.none())];

let mut trace_result = None;

// temporarily disable tracing, during the call to the
// tracing function itself.
if !self.is_none(&trace_func) {
self.use_tracing.set(false);
let res = trace_func.call(args.clone(), self);
self.use_tracing.set(true);
if res.is_err() {
*self.trace_func.borrow_mut() = self.ctx.none();
match res {
Ok(result) => {
if !self.is_none(&result) {
trace_result = Some(result);
}
}
Err(e) => {
// trace_trampoline behavior: clear per-frame f_trace
// and propagate the error.
if let Some(frame_ref) = self.current_frame() {
*frame_ref.trace.lock() = self.ctx.none();
}
return Err(e);
}
}
}

Expand All @@ -138,6 +168,6 @@ impl VirtualMachine {
*self.profile_func.borrow_mut() = self.ctx.none();
}
}
Ok(())
Ok(trace_result)
}
}
39 changes: 27 additions & 12 deletions crates/vm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,19 +1084,25 @@ impl VirtualMachine {

// Fire 'call' trace event after pushing frame
// (current_frame() now returns the callee's frame)
//
// trace_dispatch protocol (matching CPython's trace_trampoline):
// - For 'call' events, the global trace function is called.
// If it returns non-None, set f_trace to that value (trace this frame).
// If it returns None, leave f_trace unset (skip tracing this frame).
// - For 'return' events, fire if this frame has f_trace set OR if
// a profile function is active (profiling is independent of f_trace).
match self.trace_event(TraceEvent::Call, None) {
Ok(()) => {
// Set per-frame trace function so line events fire for this frame.
// Frames entered before sys.settrace() keep trace=None and skip line events.
if self.use_tracing.get() {
let trace_func = self.trace_func.borrow().clone();
if !self.is_none(&trace_func) {
*frame.trace.lock() = trace_func;
}
Ok(trace_result) => {
if let Some(local_trace) = trace_result {
*frame.trace.lock() = local_trace;
}
let result = f(frame.clone());
// Fire 'return' trace event on success
if result.is_ok() {
// Fire 'return' event if frame is being traced or profiled
if result.is_ok()
&& self.use_tracing.get()
&& (!self.is_none(&frame.trace.lock())
|| !self.is_none(&self.profile_func.borrow()))
{
let _ = self.trace_event(TraceEvent::Return, None);
}
result
Expand Down Expand Up @@ -1155,9 +1161,18 @@ impl VirtualMachine {

use crate::protocol::TraceEvent;
match self.trace_event(TraceEvent::Call, None) {
Ok(()) => {
Ok(trace_result) => {
// Update per-frame trace if trace function returned a new local trace
if let Some(local_trace) = trace_result {
*frame.trace.lock() = local_trace;
}
let result = f(frame);
if result.is_ok() {
// Fire 'return' event if frame is being traced or profiled
if result.is_ok()
&& self.use_tracing.get()
&& (!self.is_none(&frame.trace.lock())
|| !self.is_none(&self.profile_func.borrow()))
{
let _ = self.trace_event(TraceEvent::Return, None);
}
result
Expand Down
Loading