Skip to content

Commit cdadde5

Browse files
committed
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
1 parent b90530c commit cdadde5

File tree

12 files changed

+516
-307
lines changed

12 files changed

+516
-307
lines changed

Lib/test/test_faulthandler.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,6 @@ def funcA():
533533
def test_dump_traceback(self):
534534
self.check_dump_traceback()
535535

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

632-
@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>'
633631
def test_dump_traceback_threads(self):
634632
self.check_dump_traceback_threads(None)
635633

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

704-
@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\''
705701
def test_dump_traceback_later(self):
706702
self.check_dump_traceback_later()
707703

708-
@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\''
709704
def test_dump_traceback_later_repeat(self):
710705
self.check_dump_traceback_later(repeat=True)
711706

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

716-
@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>'
717710
def test_dump_traceback_later_file(self):
718711
with temporary_filename() as filename:
719712
self.check_dump_traceback_later(filename=filename)
@@ -724,7 +717,6 @@ def test_dump_traceback_later_fd(self):
724717
with tempfile.TemporaryFile('wb+') as fp:
725718
self.check_dump_traceback_later(fd=fp.fileno())
726719

727-
@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\''
728720
@support.requires_resource('walltime')
729721
def test_dump_traceback_later_twice(self):
730722
self.check_dump_traceback_later(loops=2)

Lib/test/test_inspect/test_inspect.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,6 @@ def test_abuse_done(self):
540540
self.istest(inspect.istraceback, 'git.ex.__traceback__')
541541
self.istest(inspect.isframe, 'mod.fr')
542542

543-
@unittest.expectedFailure # TODO: RUSTPYTHON
544543
def test_stack(self):
545544
self.assertTrue(len(mod.st) >= 5)
546545
frame1, frame2, frame3, frame4, *_ = mod.st

Lib/test/test_listcomps.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,8 +716,6 @@ def test_multiple_comprehension_name_reuse(self):
716716
self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, scopes=["class"])
717717
self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, scopes=["function", "module"])
718718

719-
# TODO: RUSTPYTHON
720-
@unittest.expectedFailure
721719
def test_exception_locations(self):
722720
# The location of an exception raised from __init__ or
723721
# __next__ should should be the iterator expression

Lib/test/test_setcomps.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@
152152
"""
153153

154154
class SetComprehensionTest(unittest.TestCase):
155-
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FrameSummary' object has no attribute 'end_lineno'
156155
def test_exception_locations(self):
157156
# The location of an exception raised from __init__ or
158157
# __next__ should should be the iterator expression

Lib/test/test_traceback.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3423,8 +3423,6 @@ def test_no_locals(self):
34233423
s = traceback.StackSummary.extract(iter([(f, 6)]))
34243424
self.assertEqual(s[0].locals, None)
34253425

3426-
# TODO: RUSTPYTHON
3427-
@unittest.expectedFailure
34283426
def test_format_locals(self):
34293427
def some_inner(k, v):
34303428
a = 1
@@ -3441,8 +3439,6 @@ def some_inner(k, v):
34413439
' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
34423440
], s.format())
34433441

3444-
# TODO: RUSTPYTHON
3445-
@unittest.expectedFailure
34463442
def test_custom_format_frame(self):
34473443
class CustomStackSummary(traceback.StackSummary):
34483444
def format_frame_summary(self, frame_summary, colorize=False):

crates/codegen/src/compile.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4638,7 +4638,7 @@ impl Compiler {
46384638
self.emit_load_const(ConstantData::Str { value: name.into() });
46394639

46404640
if let Some(arguments) = arguments {
4641-
self.codegen_call_helper(2, arguments)?;
4641+
self.codegen_call_helper(2, arguments, self.current_source_range)?;
46424642
} else {
46434643
emit!(self, Instruction::Call { nargs: 2 });
46444644
}
@@ -7079,6 +7079,10 @@ impl Compiler {
70797079
}
70807080

70817081
fn compile_call(&mut self, func: &ast::Expr, args: &ast::Arguments) -> CompileResult<()> {
7082+
// Save the call expression's source range so CALL instructions use the
7083+
// call start line, not the last argument's line.
7084+
let call_range = self.current_source_range;
7085+
70827086
// Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
70837087
// Regular call: func → PUSH_NULL → args → CALL
70847088
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &func {
@@ -7096,21 +7100,21 @@ impl Compiler {
70967100
self.emit_load_zero_super_method(idx);
70977101
}
70987102
}
7099-
self.codegen_call_helper(0, args)?;
7103+
self.codegen_call_helper(0, args, call_range)?;
71007104
} else {
71017105
// Normal method call: compile object, then LOAD_ATTR with method flag
71027106
// LOAD_ATTR(method=1) pushes [method, self_or_null] on stack
71037107
self.compile_expression(value)?;
71047108
let idx = self.name(attr.as_str());
71057109
self.emit_load_attr_method(idx);
7106-
self.codegen_call_helper(0, args)?;
7110+
self.codegen_call_helper(0, args, call_range)?;
71077111
}
71087112
} else {
71097113
// Regular call: push func, then NULL for self_or_null slot
71107114
// Stack layout: [func, NULL, args...] - same as method call [func, self, args...]
71117115
self.compile_expression(func)?;
71127116
emit!(self, Instruction::PushNull);
7113-
self.codegen_call_helper(0, args)?;
7117+
self.codegen_call_helper(0, args, call_range)?;
71147118
}
71157119
Ok(())
71167120
}
@@ -7152,10 +7156,13 @@ impl Compiler {
71527156
}
71537157

71547158
/// Compile call arguments and emit the appropriate CALL instruction.
7159+
/// `call_range` is the source range of the call expression, used to set
7160+
/// the correct line number on the CALL instruction.
71557161
fn codegen_call_helper(
71567162
&mut self,
71577163
additional_positional: u32,
71587164
arguments: &ast::Arguments,
7165+
call_range: TextRange,
71597166
) -> CompileResult<()> {
71607167
let nelts = arguments.args.len();
71617168
let nkwelts = arguments.keywords.len();
@@ -7186,13 +7193,16 @@ impl Compiler {
71867193
self.compile_expression(&keyword.value)?;
71877194
}
71887195

7196+
// Restore call expression range for kwnames and CALL_KW
7197+
self.set_source_range(call_range);
71897198
self.emit_load_const(ConstantData::Tuple {
71907199
elements: kwarg_names,
71917200
});
71927201

71937202
let nargs = additional_positional + nelts.to_u32() + nkwelts.to_u32();
71947203
emit!(self, Instruction::CallKw { nargs });
71957204
} else {
7205+
self.set_source_range(call_range);
71967206
let nargs = additional_positional + nelts.to_u32();
71977207
emit!(self, Instruction::Call { nargs });
71987208
}
@@ -7284,6 +7294,7 @@ impl Compiler {
72847294
emit!(self, Instruction::PushNull);
72857295
}
72867296

7297+
self.set_source_range(call_range);
72877298
emit!(self, Instruction::CallFunctionEx);
72887299
}
72897300

0 commit comments

Comments
 (0)