Fix traceback (#6569)

* Fix traceback

* Update traceback from CPython 3.13.11

* unmark test_traceback

* fix code

* fix debug range

* fix tests
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-12-30 16:58:53 +09:00
1 parent 6bd1d90b6a
commit e79a1a1a66
16 files changed
+1200 -305

No files matched your search

+1
View File
@@ -178,6 +178,7 @@ PYTHONHASHSEED
PYTHONHOME
PYTHONINSPECT
PYTHONINTMAXSTRDIGITS
PYTHONNODEBUGRANGES
PYTHONNOUSERSITE
PYTHONOPTIMIZE
PYTHONPATH
-4
View File
@@ -425,8 +425,6 @@ class CodeTest(unittest.TestCase):
]
)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_endline_and_columntable_none_when_no_debug_ranges(self):
# Make sure that if `-X no_debug_ranges` is used, there is
# minimal debug info
@@ -442,8 +440,6 @@ class CodeTest(unittest.TestCase):
""")
assert_python_ok('-X', 'no_debug_ranges', '-c', code)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_endline_and_columntable_none_when_no_debug_ranges_env(self):
# Same as above but using the environment variable opt out.
code = textwrap.dedent("""
-110
View File
@@ -218,8 +218,6 @@ class TracebackCases(unittest.TestCase):
lst = traceback.format_exception_only(e.__class__, e)
self.assertEqual(lst, ['KeyboardInterrupt\n'])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_only_bad__str__(self):
class X(Exception):
def __str__(self):
@@ -238,8 +236,6 @@ class TracebackCases(unittest.TestCase):
err = traceback.format_exception_only(eg)
self.assertEqual(err, ['ExceptionGroup: A (1 sub-exception)\n'])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group(self):
eg = ExceptionGroup('A', [ValueError('B')])
err = traceback.format_exception_only(eg, show_group=True)
@@ -248,8 +244,6 @@ class TracebackCases(unittest.TestCase):
' ValueError: B\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_base_exception_group(self):
eg = BaseExceptionGroup('A', [BaseException('B')])
err = traceback.format_exception_only(eg, show_group=True)
@@ -258,8 +252,6 @@ class TracebackCases(unittest.TestCase):
' BaseException: B\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_with_note(self):
exc = ValueError('B')
exc.add_note('Note')
@@ -271,8 +263,6 @@ class TracebackCases(unittest.TestCase):
' Note\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_explicit_class(self):
eg = ExceptionGroup('A', [ValueError('B')])
err = traceback.format_exception_only(ExceptionGroup, eg, show_group=True)
@@ -281,8 +271,6 @@ class TracebackCases(unittest.TestCase):
' ValueError: B\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_multiple_exceptions(self):
eg = ExceptionGroup('A', [ValueError('B'), TypeError('C')])
err = traceback.format_exception_only(eg, show_group=True)
@@ -292,8 +280,6 @@ class TracebackCases(unittest.TestCase):
' TypeError: C\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_multiline_messages(self):
eg = ExceptionGroup('A\n1', [ValueError('B\n2')])
err = traceback.format_exception_only(eg, show_group=True)
@@ -303,8 +289,6 @@ class TracebackCases(unittest.TestCase):
' 2\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_multiline2_messages(self):
exc = ValueError('B\n\n2\n')
exc.add_note('\nC\n\n3')
@@ -323,8 +307,6 @@ class TracebackCases(unittest.TestCase):
' IndexError: D\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_syntax_error(self):
exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
eg = ExceptionGroup('A\n1', [exc])
@@ -336,8 +318,6 @@ class TracebackCases(unittest.TestCase):
' SyntaxError: error\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_nested_with_notes(self):
exc = IndexError('D')
exc.add_note('Note\nmultiline')
@@ -358,8 +338,6 @@ class TracebackCases(unittest.TestCase):
' TypeError: F\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_with_tracebacks(self):
def f():
try:
@@ -385,8 +363,6 @@ class TracebackCases(unittest.TestCase):
' TypeError: g\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_with_cause(self):
def f():
try:
@@ -404,8 +380,6 @@ class TracebackCases(unittest.TestCase):
' ValueError: 0\n',
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_group_syntax_error_with_custom_values(self):
# See https://github.com/python/cpython/issues/128894
for exc in [
@@ -550,16 +524,12 @@ class TracebackCases(unittest.TestCase):
traceback.print_exception(Exception("projector"), file=output)
self.assertEqual(output.getvalue(), "Exception: projector\n")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_print_last(self):
with support.swap_attr(sys, 'last_exc', ValueError(42)):
output = StringIO()
traceback.print_last(file=output)
self.assertEqual(output.getvalue(), "ValueError: 42\n")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_format_exception_exc(self):
e = Exception("projector")
output = traceback.format_exception(e)
@@ -598,8 +568,6 @@ class TracebackCases(unittest.TestCase):
self.assertEqual(
traceback.format_exception_only(None, None), [NONE_EXC_STRING])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_signatures(self):
self.assertEqual(
str(inspect.signature(traceback.print_exception)),
@@ -2286,8 +2254,6 @@ class TracebackFormatMixin:
'Exception expected for value, int found\n')
)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_print_exception_bad_type_python(self):
msg = "Exception expected for value, int found"
with self.assertRaisesRegex(TypeError, msg):
@@ -2366,8 +2332,6 @@ class BaseExceptionReportingTests:
self.assertTrue(lines[1].startswith(' File'))
self.assertIn('1/0 # Marker', lines[2])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_cause(self):
def inner_raise():
try:
@@ -2501,8 +2465,6 @@ class BaseExceptionReportingTests:
err = self.get_report(Exception(''))
self.assertIn('Exception\n', err)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_syntax_error_various_offsets(self):
for offset in range(-5, 10):
for add in [0, 2]:
@@ -2525,8 +2487,6 @@ class BaseExceptionReportingTests:
exp = "\n".join(expected)
self.assertEqual(exp, err)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_with_note(self):
e = ValueError(123)
vanilla = self.get_report(e)
@@ -2545,8 +2505,6 @@ class BaseExceptionReportingTests:
del e.__notes__
self.assertEqual(self.get_report(e), vanilla)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_with_invalid_notes(self):
e = ValueError(123)
vanilla = self.get_report(e)
@@ -2604,8 +2562,6 @@ class BaseExceptionReportingTests:
self.get_report(e),
vanilla + "Ignored error getting __notes__: ValueError('no __notes__')\n")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_with_multiple_notes(self):
for e in [ValueError(42), SyntaxError('bad syntax')]:
with self.subTest(e=e):
@@ -2685,8 +2641,6 @@ class BaseExceptionReportingTests:
exp = f'<unknown>.{X.__qualname__}: I am X\n'
self.assertEqual(exp, err)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_bad__str__(self):
class X(Exception):
def __str__(self):
@@ -2861,8 +2815,6 @@ class BaseExceptionReportingTests:
report = self.get_report(exc)
self.assertEqual(report, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_group_width_limit(self):
excs = []
for i in range(1000):
@@ -2907,8 +2859,6 @@ class BaseExceptionReportingTests:
report = self.get_report(eg)
self.assertEqual(report, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_group_depth_limit(self):
exc = TypeError('bad type')
for i in range(1000):
@@ -3371,8 +3321,6 @@ class TestFrame(unittest.TestCase):
self.assertNotEqual(f, object())
self.assertEqual(f, ALWAYS_EQ)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lazy_lines(self):
linecache.clearcache()
f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
@@ -3509,8 +3457,6 @@ class TestStack(unittest.TestCase):
s.format(),
[f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_dropping_frames(self):
def f():
1/0
@@ -3611,13 +3557,9 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(expected_type_str, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_smoke_builtin(self):
self.do_test_smoke(ValueError(42), 'ValueError')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_smoke_user_exception(self):
class MyException(Exception):
pass
@@ -3630,8 +3572,6 @@ class TestTracebackException(unittest.TestCase):
'test_smoke_user_exception.<locals>.MyException')
self.do_test_smoke(MyException('bad things happened'), expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_from_exception(self):
# Check all the parameters are accepted.
def foo():
@@ -3657,8 +3597,6 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_cause(self):
try:
try:
@@ -3683,8 +3621,6 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_context(self):
try:
try:
@@ -3734,8 +3670,6 @@ class TestTracebackException(unittest.TestCase):
self.assertIn(
"RecursionError: maximum recursion depth exceeded", res[-1])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_compact_with_cause(self):
try:
try:
@@ -3758,8 +3692,6 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_compact_no_cause(self):
try:
try:
@@ -3782,8 +3714,6 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_save_exc_type(self):
try:
1/0
@@ -3912,8 +3842,6 @@ class TestTracebackException(unittest.TestCase):
linecache.updatecache('/foo.py', globals())
self.assertEqual(exc.stack[0].line, "import sys")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_locals(self):
linecache.updatecache('/foo.py', globals())
e = Exception("uh oh")
@@ -3962,8 +3890,6 @@ class TestTracebackException(unittest.TestCase):
'ZeroDivisionError: division by zero',
''])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_dont_swallow_cause_or_context_of_falsey_exception(self):
# see gh-132308: Ensure that __cause__ or __context__ attributes of exceptions
# that evaluate as falsey are included in the output. For falsey term,
@@ -4034,8 +3960,6 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
self.assertEqual(formatted, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_exception_group_format_exception_onlyi_recursive(self):
teg = traceback.TracebackException.from_exception(self.eg)
formatted = ''.join(teg.format_exception_only(show_group=True)).split('\n')
@@ -4100,8 +4024,6 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
self.assertEqual(formatted, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_max_group_width(self):
excs1 = []
excs2 = []
@@ -4140,8 +4062,6 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
self.assertEqual(formatted, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_max_group_depth(self):
exc = TypeError('bad type')
for i in range(3):
@@ -4191,8 +4111,6 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
self.assertNotEqual(exc, object())
self.assertEqual(exc, ALWAYS_EQ)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self):
# see gh-132308: Ensure that subexceptions of exception groups
# that evaluate as falsey are displayed in the output. For falsey term,
@@ -4230,8 +4148,6 @@ class SuggestionFormattingTestBase:
)
return result_lines[0]
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_getattr_suggestions(self):
class Substitution:
noise = more_noise = a = bc = None
@@ -4275,8 +4191,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(cls(), 'bluch')
self.assertIn(suggestion, actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_getattr_suggestions_underscored(self):
class A:
bluch = None
@@ -4330,8 +4244,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(A(), 'bluch')
self.assertNotIn("blech", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_getattr_suggestions_no_args(self):
class A:
blech = None
@@ -4349,8 +4261,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(A(), 'bluch')
self.assertIn("blech", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_getattr_suggestions_invalid_args(self):
class NonStringifyClass:
__str__ = None
@@ -4392,8 +4302,6 @@ class SuggestionFormattingTestBase:
self.assertNotIn("blech", actual)
self.assertNotIn("oh no!", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_attribute_error_with_non_string_candidates(self):
class T:
bluch = 1
@@ -4567,8 +4475,6 @@ class SuggestionFormattingTestBase:
)
self.assertNotIn("?", result_lines[-1])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_suggestions(self):
def Substitution():
noise = more_noise = a = bc = None
@@ -4609,24 +4515,18 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(func)
self.assertIn(suggestion, actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_suggestions_from_globals(self):
def func():
print(global_for_suggestio)
actual = self.get_suggestion(func)
self.assertIn("'global_for_suggestions'?", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_suggestions_from_builtins(self):
def func():
print(ZeroDivisionErrrrr)
actual = self.get_suggestion(func)
self.assertIn("'ZeroDivisionError'?", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_suggestions_from_builtins_when_builtins_is_module(self):
def func():
custom_globals = globals().copy()
@@ -4635,8 +4535,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(func)
self.assertIn("'ZeroDivisionError'?", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_suggestions_with_non_string_candidates(self):
def func():
abc = 1
@@ -4785,8 +4683,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(func)
self.assertNotIn("blech", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_with_instance(self):
class A:
def __init__(self):
@@ -4843,8 +4739,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(func)
self.assertNotIn("something", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_for_stdlib_modules(self):
def func():
stream = io.StringIO()
@@ -4852,8 +4746,6 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(func)
self.assertIn("forget to import 'io'", actual)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_name_error_for_private_stdlib_modules(self):
def func():
stream = _io.StringIO()
@@ -4898,8 +4790,6 @@ class MiscTest(unittest.TestCase):
expected.add(name)
self.assertCountEqual(traceback.__all__, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_levenshtein_distance(self):
# copied from _testinternalcapi.test_edit_cost
# to also exercise the Python implementation
+2
View File
@@ -724,6 +724,8 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
else:
raise AssertionError("This ought to be impossible")
# TODO: RUSTPYTHON; empty caret lines from equal col/end_col
@unittest.expectedFailure
def testTraceback(self):
files = {TESTMOD + ".py": (NOW, raise_src)}
self.doTest(None, files, TESTMOD, call=self.doTraceback)
+1070 -124
View File
File diff suppressed because it is too large. Load diff
+21 -9
View File
@@ -115,11 +115,22 @@ enum DoneWithFuture {
Yes,
}
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct CompileOpts {
/// How optimized the bytecode output should be; any optimize > 0 does
/// not emit assert statements
pub optimize: u8,
/// Include column info in bytecode (-X no_debug_ranges disables)
pub debug_ranges: bool,
}
impl Default for CompileOpts {
fn default() -> Self {
Self {
optimize: 0,
debug_ranges: true,
}
}
}
#[derive(Debug, Clone, Copy)]
@@ -859,7 +870,7 @@ impl Compiler {
let pop = self.code_stack.pop();
let stack_top = compiler_unwrap_option(self, pop);
// No parent scope stack to maintain
unwrap_internal(self, stack_top.finalize_code(self.opts.optimize))
unwrap_internal(self, stack_top.finalize_code(&self.opts))
}
/// Push a new fblock
@@ -1486,7 +1497,9 @@ impl Compiler {
..
}) => self.compile_for(target, iter, body, orelse, *is_async)?,
Stmt::Match(StmtMatch { subject, cases, .. }) => self.compile_match(subject, cases)?,
Stmt::Raise(StmtRaise { exc, cause, .. }) => {
Stmt::Raise(StmtRaise {
exc, cause, range, ..
}) => {
let kind = match exc {
Some(value) => {
self.compile_expression(value)?;
@@ -1500,6 +1513,7 @@ impl Compiler {
}
None => bytecode::RaiseKind::Reraise,
};
self.set_source_range(*range);
emit!(self, Instruction::Raise { kind });
}
Stmt::Try(StmtTry {
@@ -5639,17 +5653,15 @@ impl Compiler {
// Low level helper functions:
fn _emit(&mut self, instr: Instruction, arg: OpArg, target: BlockIdx) {
let range = self.current_source_range;
let location = self
.source_file
.to_source_code()
.source_location(range.start(), PositionEncoding::Utf8);
// TODO: insert source filename
let source = self.source_file.to_source_code();
let location = source.source_location(range.start(), PositionEncoding::Utf8);
let end_location = source.source_location(range.end(), PositionEncoding::Utf8);
self.current_block().instructions.push(ir::InstructionInfo {
instr,
arg,
target,
location,
// range,
end_location,
});
}
+55 -32
View File
@@ -86,9 +86,8 @@ pub struct InstructionInfo {
pub instr: Instruction,
pub arg: OpArg,
pub target: BlockIdx,
// pub range: TextRange,
pub location: SourceLocation,
// TODO: end_location for debug ranges
pub end_location: SourceLocation,
}
// spell-checker:ignore petgraph
@@ -133,8 +132,11 @@ pub struct CodeInfo {
}
impl CodeInfo {
pub fn finalize_code(mut self, optimize: u8) -> crate::InternalResult<CodeObject> {
if optimize > 0 {
pub fn finalize_code(
mut self,
opts: &crate::compile::CompileOpts,
) -> crate::InternalResult<CodeObject> {
if opts.optimize > 0 {
self.dce();
}
@@ -198,7 +200,10 @@ impl CodeInfo {
*arg = new_arg;
}
let (extras, lo_arg) = arg.split();
locations.extend(core::iter::repeat_n(info.location, arg.instr_size()));
locations.extend(core::iter::repeat_n(
(info.location, info.end_location),
arg.instr_size(),
));
instructions.extend(
extras
.map(|byte| CodeUnit::new(Instruction::ExtendedArg, byte))
@@ -217,7 +222,11 @@ impl CodeInfo {
}
// Generate linetable from locations
let linetable = generate_linetable(&locations, first_line_number.get() as i32);
let linetable = generate_linetable(
&locations,
first_line_number.get() as i32,
opts.debug_ranges,
);
Ok(CodeObject {
flags,
@@ -412,7 +421,11 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + '
}
/// Generate CPython 3.11+ format linetable from source locations
fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]> {
fn generate_linetable(
locations: &[(SourceLocation, SourceLocation)],
first_line: i32,
debug_ranges: bool,
) -> Box<[u8]> {
if locations.is_empty() {
return Box::new([]);
}
@@ -424,7 +437,7 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]
let mut i = 0;
while i < locations.len() {
let loc = &locations[i];
let (loc, end_loc) = &locations[i];
// Count consecutive instructions with the same location
let mut length = 1;
@@ -436,18 +449,33 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]
while length > 0 {
let entry_length = length.min(8);
// Get line and column information
// SourceLocation always has row and column (both are OneIndexed)
// Get line information
let line = loc.line.get() as i32;
let col = loc.character_offset.to_zero_indexed() as i32;
let end_line = end_loc.line.get() as i32;
let line_delta = line - prev_line;
let end_line_delta = end_line - line;
// When debug_ranges is disabled, only emit line info (NoColumns format)
if !debug_ranges {
// NoColumns format (code 13): line info only, no column data
linetable.push(
0x80 | ((PyCodeLocationInfoKind::NoColumns as u8) << 3)
| ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
prev_line = line;
length -= entry_length;
i += entry_length;
continue;
}
// Get column information (only when debug_ranges is enabled)
let col = loc.character_offset.to_zero_indexed() as i32;
let end_col = end_loc.character_offset.to_zero_indexed() as i32;
// Choose the appropriate encoding based on line delta and column info
// Note: SourceLocation always has valid column, so we never get NO_COLUMNS case
if line_delta == 0 {
let end_col = col; // Use same column for end (no range info available)
if line_delta == 0 && end_line_delta == 0 {
if col < 80 && end_col - col < 16 && end_col >= col {
// Short form (codes 0-9) for common cases
let code = (col / 8).min(9) as u8; // Short0 to Short9
@@ -470,42 +498,37 @@ fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]
);
write_signed_varint(&mut linetable, 0); // line_delta = 0
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
write_varint(&mut linetable, (col as u32) + 1);
write_varint(&mut linetable, (end_col as u32) + 1);
}
} else if line_delta > 0 && line_delta < 3
/* && column.is_some() */
{
} else if line_delta > 0 && line_delta < 3 && end_line_delta == 0 {
// One-line form (codes 11-12) for line deltas 1-2
let end_col = col; // Use same column for end
if col < 128 && end_col < 128 {
let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8); // 11 for delta=1, 12 for delta=2
let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8);
linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8));
linetable.push(col as u8);
linetable.push(end_col as u8);
} else {
// Long form for columns >= 128 or negative line delta
// Long form for columns >= 128
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3)
| ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
write_varint(&mut linetable, (col as u32) + 1);
write_varint(&mut linetable, (end_col as u32) + 1);
}
} else {
// Long form (code 14) for all other cases
// This handles: line_delta < 0, line_delta >= 3, or columns >= 128
let end_col = col; // Use same column for end
// Handles: line_delta < 0, line_delta >= 3, multi-line spans, or columns >= 128
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) | ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
write_varint(&mut linetable, end_line_delta as u32);
write_varint(&mut linetable, (col as u32) + 1);
write_varint(&mut linetable, (end_col as u32) + 1);
}
prev_line = line;
+3 -3
View File
@@ -258,7 +258,7 @@ impl ConstantBag for BasicBag {
#[derive(Clone)]
pub struct CodeObject<C: Constant = ConstantData> {
pub instructions: CodeUnits,
pub locations: Box<[SourceLocation]>,
pub locations: Box<[(SourceLocation, SourceLocation)]>,
pub flags: CodeFlags,
/// Number of positional-only arguments
pub posonlyarg_count: u32,
@@ -1483,14 +1483,14 @@ impl<C: Constant> CodeObject<C> {
level: usize,
) -> fmt::Result {
let label_targets = self.label_targets();
let line_digits = (3).max(self.locations.last().unwrap().line.digits().get());
let line_digits = (3).max(self.locations.last().unwrap().0.line.digits().get());
let offset_digits = (4).max(1 + self.instructions.len().ilog10() as usize);
let mut last_line = OneIndexed::MAX;
let mut arg_state = OpArgState::default();
for (offset, &instruction) in self.instructions.iter().enumerate() {
let (instruction, arg) = arg_state.get(instruction);
// optional line number
let line = self.locations[offset].line;
let line = self.locations[offset].0.line;
if line != last_line {
if last_line != OneIndexed::MAX {
writeln!(f)?;
+13 -6
View File
@@ -190,12 +190,17 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
let len = rdr.read_u32()?;
let locations = (0..len)
.map(|_| {
Ok(SourceLocation {
let start = SourceLocation {
line: OneIndexed::new(rdr.read_u32()? as _).ok_or(MarshalError::InvalidLocation)?,
character_offset: OneIndexed::from_zero_indexed(rdr.read_u32()? as _),
})
};
let end = SourceLocation {
line: OneIndexed::new(rdr.read_u32()? as _).ok_or(MarshalError::InvalidLocation)?,
character_offset: OneIndexed::from_zero_indexed(rdr.read_u32()? as _),
};
Ok((start, end))
})
.collect::<Result<Box<[SourceLocation]>>>()?;
.collect::<Result<Box<[(SourceLocation, SourceLocation)]>>>()?;
let flags = CodeFlags::from_bits_truncate(rdr.read_u16()?);
@@ -648,9 +653,11 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>)
buf.write_slice(instructions_bytes);
write_len(buf, code.locations.len());
for loc in &*code.locations {
buf.write_u32(loc.line.get() as _);
buf.write_u32(loc.character_offset.to_zero_indexed() as _);
for (start, end) in &*code.locations {
buf.write_u32(start.line.get() as _);
buf.write_u32(start.character_offset.to_zero_indexed() as _);
buf.write_u32(end.line.get() as _);
buf.write_u32(end.character_offset.to_zero_indexed() as _);
}
buf.write_u16(code.flags.bits());
+11 -10
View File
@@ -467,20 +467,22 @@ impl Constructor for PyCode {
.collect::<Vec<_>>()
.into_boxed_slice();
// Create locations
// Create locations (start and end pairs)
let row = if args.firstlineno > 0 {
OneIndexed::new(args.firstlineno as usize).unwrap_or(OneIndexed::MIN)
} else {
OneIndexed::MIN
};
let locations: Box<[rustpython_compiler_core::SourceLocation]> = vec![
rustpython_compiler_core::SourceLocation {
line: row,
character_offset: OneIndexed::from_zero_indexed(0),
};
instructions.len()
]
.into_boxed_slice();
let loc = rustpython_compiler_core::SourceLocation {
line: row,
character_offset: OneIndexed::from_zero_indexed(0),
};
let locations: Box<
[(
rustpython_compiler_core::SourceLocation,
rustpython_compiler_core::SourceLocation,
)],
> = vec![(loc, loc); instructions.len()].into_boxed_slice();
// Build the CodeObject
let code = CodeObject {
@@ -809,7 +811,6 @@ impl PyCode {
Some(line + end_line_delta)
};
// Convert Option to PyObject (None or int)
let line_obj = final_line.to_pyobject(vm);
let end_line_obj = final_endline.to_pyobject(vm);
let column_obj = column.to_pyobject(vm);
+9 -2
View File
@@ -58,12 +58,19 @@ impl Frame {
#[pygetset]
fn f_lasti(&self) -> u32 {
self.lasti()
// Return byte offset (each instruction is 2 bytes) for compatibility
self.lasti() * 2
}
#[pygetset]
pub fn f_lineno(&self) -> usize {
self.current_location().line.get()
// If lasti is 0, execution hasn't started yet - use first line number
// Similar to PyCode_Addr2Line which returns co_firstlineno for addr_q < 0
if self.lasti() == 0 {
self.code.first_line_number.map(|n| n.get()).unwrap_or(1)
} else {
self.current_location().line.get()
}
}
#[pygetset]
+3 -3
View File
@@ -172,7 +172,7 @@ impl Frame {
}
pub fn current_location(&self) -> SourceLocation {
self.code.locations[self.lasti() as usize - 1]
self.code.locations[self.lasti() as usize - 1].0
}
pub fn lasti(&self) -> u32 {
@@ -385,12 +385,12 @@ impl ExecutingFrame<'_> {
// 2. Add new entry with current execution position (filename, lineno, code_object) to traceback.
// 3. Unwind block stack till appropriate handler is found.
let loc = frame.code.locations[idx];
let (loc, _end_loc) = frame.code.locations[idx];
let next = exception.__traceback__();
let new_traceback = PyTraceback::new(
next,
frame.object.to_owned(),
frame.lasti(),
frame.lasti() * 2,
loc.line,
);
vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, loc.line);
+1
View File
@@ -508,6 +508,7 @@ impl VirtualMachine {
pub fn compile_opts(&self) -> crate::compiler::CompileOpts {
crate::compiler::CompileOpts {
optimize: self.state.config.settings.optimize,
debug_ranges: self.state.config.settings.code_debug_ranges,
}
}
+3 -1
View File
@@ -57,7 +57,8 @@ pub struct Settings {
// int tracemalloc;
// int perf_profiling;
// int import_time;
// int code_debug_ranges;
/// -X no_debug_ranges: disable column info in bytecode
pub code_debug_ranges: bool,
// int show_ref_count;
// int dump_refs;
// wchar_t *dump_refs_file;
@@ -192,6 +193,7 @@ impl Default for Settings {
argv: vec![],
hash_seed: None,
faulthandler: false,
code_debug_ranges: true,
buffered_stdio: true,
check_hash_pycs_mode: CheckHashPycsMode::Default,
allow_external_library: cfg!(feature = "importlib"),
+4 -1
View File
@@ -53,7 +53,10 @@ fn main() -> Result<(), lexopt::Error> {
return Err("expected at least one argument".into());
}
let opts = compiler::CompileOpts { optimize };
let opts = compiler::CompileOpts {
optimize,
debug_ranges: true,
};
for script in &scripts {
if script.exists() && script.is_file() {
+4
View File
@@ -270,6 +270,7 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
"faulthandler" => settings.faulthandler = true,
"warn_default_encoding" => settings.warn_default_encoding = true,
"no_sig_int" => settings.install_signal_handlers = false,
"no_debug_ranges" => settings.code_debug_ranges = false,
"int_max_str_digits" => {
settings.int_max_str_digits = match value.unwrap().parse() {
Ok(digits) if digits == 0 || digits >= 640 => digits,
@@ -293,6 +294,9 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
settings.warn_default_encoding =
settings.warn_default_encoding || env_bool("PYTHONWARNDEFAULTENCODING");
settings.faulthandler = settings.faulthandler || env_bool("PYTHONFAULTHANDLER");
if env_bool("PYTHONNODEBUGRANGES") {
settings.code_debug_ranges = false;
}
if settings.dev_mode {
settings.warnoptions.push("default".to_owned());