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
Update faulthandler to match CPython 3.14.2
- Rewrite faulthandler with live frame walking via
  Frame.previous AtomicPtr chain and thread-local
  CURRENT_FRAME (AtomicPtr) instead of frame snapshots
- Add signal-safe traceback dumping (dump_live_frames,
  dump_frame_from_raw) walking the Frame.previous chain
- Add safe_truncate/dump_ascii for UTF-8 safe string
  truncation in signal handlers
- Refactor write_thread_id to accept thread_id parameter
- Add SA_RESTART for user signal registration, SA_NODEFER
  only when chaining
- Save/restore errno in faulthandler_user_signal
- Add signal re-entrancy guard in trigger_signals to
  prevent recursive handler invocation
- Add thread frame tracking (push/pop/cleanup/reinit)
  with force_unlock fallback for post-fork recovery
- Remove expectedFailure markers for now-passing tests
  • Loading branch information
youknowone committed Feb 3, 2026
commit cdadde55efcb29550856d94b095fb0f3166195db
8 changes: 0 additions & 8 deletions Lib/test/test_faulthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,6 @@ def funcA():
def test_dump_traceback(self):
self.check_dump_traceback()

@unittest.expectedFailure # TODO: RUSTPYTHON; - binary file write needs different handling
def test_dump_traceback_file(self):
with temporary_filename() as filename:
self.check_dump_traceback(filename=filename)
Expand Down Expand Up @@ -629,11 +628,9 @@ def run(self):
self.assertRegex(output, regex)
self.assertEqual(exitcode, 0)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Thread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\n(?: File ".*threading.py", line [0-9]+ in [_a-z]+\n){1,3} File "<string>", line (?:22|23) in run\n File ".*threading.py", line [0-9]+ in _bootstrap_inner\n File ".*threading.py", line [0-9]+ in _bootstrap\n\nCurrent thread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\n File "<string>", line 10 in dump\n File "<string>", line 28 in <module>$' not found in 'Stack (most recent call first):\n File "<string>", line 10 in dump\n File "<string>", line 28 in <module>'
def test_dump_traceback_threads(self):
self.check_dump_traceback_threads(None)

@unittest.expectedFailure # TODO: RUSTPYTHON; - TypeError: a bytes-like object is required, not 'str'
def test_dump_traceback_threads_file(self):
with temporary_filename() as filename:
self.check_dump_traceback_threads(filename)
Expand Down Expand Up @@ -701,19 +698,15 @@ def func(timeout, repeat, cancel, file, loops):
self.assertEqual(trace, '')
self.assertEqual(exitcode, 0)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>$' not found in 'Traceback (most recent call last):\n File "<string>", line 26, in <module>\n File "<string>", line 14, in func\nAttributeError: \'NoneType\' object has no attribute \'fileno\''
def test_dump_traceback_later(self):
self.check_dump_traceback_later()

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>\nTimeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>' not found in 'Traceback (most recent call last):\n File "<string>", line 26, in <module>\n File "<string>", line 14, in func\nAttributeError: \'NoneType\' object has no attribute \'fileno\''
def test_dump_traceback_later_repeat(self):
self.check_dump_traceback_later(repeat=True)

@unittest.expectedFailure # TODO: RUSTPYTHON; - AttributeError: 'NoneType' object has no attribute 'fileno'
def test_dump_traceback_later_cancel(self):
self.check_dump_traceback_later(cancel=True)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>$' not found in 'Timeout (00:00:00.500000)!\n<timeout: cannot dump traceback from watchdog thread>'
def test_dump_traceback_later_file(self):
with temporary_filename() as filename:
self.check_dump_traceback_later(filename=filename)
Expand All @@ -724,7 +717,6 @@ def test_dump_traceback_later_fd(self):
with tempfile.TemporaryFile('wb+') as fp:
self.check_dump_traceback_later(fd=fp.fileno())

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^Timeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>\nTimeout \\(0:00:00.500000\\)!\\nThread 0x[0-9a-f]+( \\[.*\\])? \\(most recent call first\\):\\n File "<string>", line 17 in func\n File "<string>", line 26 in <module>' not found in 'Traceback (most recent call last):\n File "<string>", line 26, in <module>\n File "<string>", line 14, in func\nAttributeError: \'NoneType\' object has no attribute \'fileno\''
@support.requires_resource('walltime')
def test_dump_traceback_later_twice(self):
self.check_dump_traceback_later(loops=2)
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,6 @@ def test_abuse_done(self):
self.istest(inspect.istraceback, 'git.ex.__traceback__')
self.istest(inspect.isframe, 'mod.fr')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_stack(self):
self.assertTrue(len(mod.st) >= 5)
frame1, frame2, frame3, frame4, *_ = mod.st
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_listcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,8 +716,6 @@ def test_multiple_comprehension_name_reuse(self):
self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, scopes=["class"])
self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, scopes=["function", "module"])

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_locations(self):
# The location of an exception raised from __init__ or
# __next__ should should be the iterator expression
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_setcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@
"""

class SetComprehensionTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FrameSummary' object has no attribute 'end_lineno'
def test_exception_locations(self):
# The location of an exception raised from __init__ or
# __next__ should should be the iterator expression
Expand Down
4 changes: 0 additions & 4 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3423,8 +3423,6 @@ def test_no_locals(self):
s = traceback.StackSummary.extract(iter([(f, 6)]))
self.assertEqual(s[0].locals, None)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_locals(self):
def some_inner(k, v):
a = 1
Expand All @@ -3441,8 +3439,6 @@ def some_inner(k, v):
' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
], s.format())

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_custom_format_frame(self):
class CustomStackSummary(traceback.StackSummary):
def format_frame_summary(self, frame_summary, colorize=False):
Expand Down
19 changes: 15 additions & 4 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4638,7 +4638,7 @@ impl Compiler {
self.emit_load_const(ConstantData::Str { value: name.into() });

if let Some(arguments) = arguments {
self.codegen_call_helper(2, arguments)?;
self.codegen_call_helper(2, arguments, self.current_source_range)?;
} else {
Comment on lines 4640 to 4642
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 | 🟡 Minor

Preserve class statement range for build_class call.

current_source_range is likely overwritten while compiling decorators and the class body, so the call location can incorrectly point inside the class body instead of the class statement. Capture the class statement range at the start of compile_class_def and reuse it here.

🛠️ Suggested fix
 fn compile_class_def(
     &mut self,
     name: &str,
     body: &[ast::Stmt],
     decorator_list: &[ast::Decorator],
     type_params: Option<&ast::TypeParams>,
     arguments: Option<&ast::Arguments>,
 ) -> CompileResult<()> {
+    // Preserve the class statement range for __build_class__ call location.
+    let class_call_range = self.current_source_range;
     self.prepare_decorators(decorator_list)?;

     let is_generic = type_params.is_some();
     let firstlineno = self.get_source_line_number().get().to_u32();
@@
-            if let Some(arguments) = arguments {
-                self.codegen_call_helper(2, arguments, self.current_source_range)?;
+            if let Some(arguments) = arguments {
+                self.codegen_call_helper(2, arguments, class_call_range)?;
             } else {
                 emit!(self, Instruction::Call { nargs: 2 });
             }
🤖 Prompt for AI Agents
In `@crates/codegen/src/compile.rs` around lines 4626 - 4628, In
compile_class_def, capture the class statement source range at the start (e.g.,
let class_stmt_range = self.current_source_range.clone() or equivalent) before
compiling decorators/body, and then use that saved class_stmt_range when
emitting the __build_class__ call (replace uses of self.current_source_range in
the codegen_call_helper(2, arguments, self.current_source_range)? invocation
with the saved class_stmt_range) so the call site points to the class statement
instead of a range overwritten by decorator/body compilation.

emit!(self, Instruction::Call { nargs: 2 });
}
Expand Down Expand Up @@ -7079,6 +7079,10 @@ impl Compiler {
}

fn compile_call(&mut self, func: &ast::Expr, args: &ast::Arguments) -> CompileResult<()> {
// Save the call expression's source range so CALL instructions use the
// call start line, not the last argument's line.
let call_range = self.current_source_range;

// Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
// Regular call: func → PUSH_NULL → args → CALL
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &func {
Expand All @@ -7096,21 +7100,21 @@ impl Compiler {
self.emit_load_zero_super_method(idx);
}
}
self.codegen_call_helper(0, args)?;
self.codegen_call_helper(0, args, call_range)?;
} else {
// Normal method call: compile object, then LOAD_ATTR with method flag
// LOAD_ATTR(method=1) pushes [method, self_or_null] on stack
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
self.emit_load_attr_method(idx);
self.codegen_call_helper(0, args)?;
self.codegen_call_helper(0, args, call_range)?;
}
} else {
// Regular call: push func, then NULL for self_or_null slot
// Stack layout: [func, NULL, args...] - same as method call [func, self, args...]
self.compile_expression(func)?;
emit!(self, Instruction::PushNull);
self.codegen_call_helper(0, args)?;
self.codegen_call_helper(0, args, call_range)?;
}
Ok(())
}
Expand Down Expand Up @@ -7152,10 +7156,13 @@ impl Compiler {
}

/// Compile call arguments and emit the appropriate CALL instruction.
/// `call_range` is the source range of the call expression, used to set
/// the correct line number on the CALL instruction.
fn codegen_call_helper(
&mut self,
additional_positional: u32,
arguments: &ast::Arguments,
call_range: TextRange,
) -> CompileResult<()> {
let nelts = arguments.args.len();
let nkwelts = arguments.keywords.len();
Expand Down Expand Up @@ -7186,13 +7193,16 @@ impl Compiler {
self.compile_expression(&keyword.value)?;
}

// Restore call expression range for kwnames and CALL_KW
self.set_source_range(call_range);
self.emit_load_const(ConstantData::Tuple {
elements: kwarg_names,
});

let nargs = additional_positional + nelts.to_u32() + nkwelts.to_u32();
emit!(self, Instruction::CallKw { nargs });
} else {
self.set_source_range(call_range);
let nargs = additional_positional + nelts.to_u32();
emit!(self, Instruction::Call { nargs });
}
Expand Down Expand Up @@ -7284,6 +7294,7 @@ impl Compiler {
emit!(self, Instruction::PushNull);
}

self.set_source_range(call_range);
emit!(self, Instruction::CallFunctionEx);
}

Expand Down
Loading
Loading