Skip to content

Commit e435299

Browse files
committed
gh-154544: Fix TracebackException mutating attributes during formatting
1 parent aad438b commit e435299

3 files changed

Lines changed: 43 additions & 20 deletions

File tree

Lib/test/test_traceback.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,17 @@ def test_no_keyword_suggestion_for_comma_errors(self):
18641864
self.assertIn("Perhaps you forgot a comma", stderr_text)
18651865
self.assertNotIn("Did you mean", stderr_text)
18661866

1867+
def test_format_is_idempotent(self):
1868+
code = "fr x in range(10):\n pass\n"
1869+
try:
1870+
compile(code, "<test>", "exec")
1871+
except SyntaxError as exc:
1872+
te = traceback.TracebackException(type(exc), exc, None)
1873+
r1 = list(te.format_exception_only())
1874+
r2 = list(te.format_exception_only())
1875+
self.assertEqual(r1, r2)
1876+
self.assertEqual(te.msg, exc.msg)
1877+
18671878
@requires_debug_ranges()
18681879
@force_not_colorized_test_class
18691880
class PurePythonTracebackErrorCaretTests(

Lib/traceback.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,14 +1519,14 @@ def _find_keyword_typos(self):
15191519
except SyntaxError:
15201520
continue
15211521

1522-
# Keep token.line but handle offsets correctly
1523-
self.text = token.line
1524-
self.offset = token.start[1] + 1
1525-
self.end_offset = token.end[1] + 1
1526-
self.lineno = start[0]
1527-
self.end_lineno = end[0]
1528-
self.msg = f"invalid syntax. Did you mean '{suggestion}'?"
1529-
return
1522+
return (
1523+
token.line,
1524+
token.start[1] + 1,
1525+
token.end[1] + 1,
1526+
start[0],
1527+
end[0],
1528+
f"invalid syntax. Did you mean '{suggestion}'?",
1529+
)
15301530

15311531

15321532
def _format_syntax_error(self, stype, **kwargs):
@@ -1555,31 +1555,38 @@ def _format_syntax_error(self, stype, **kwargs):
15551555
# text = " foo\n"
15561556
# rtext = " foo"
15571557
# ltext = "foo"
1558+
typo = None
15581559
with suppress(Exception):
1559-
self._find_keyword_typos()
1560-
text = self.text
1560+
typo = self._find_keyword_typos()
1561+
if typo is not None:
1562+
text, offset, end_offset, lineno, end_lineno, msg = typo
1563+
else:
1564+
offset = self.offset
1565+
end_offset = self.end_offset
1566+
lineno = self.lineno
1567+
end_lineno = self.end_lineno
1568+
msg = self.msg
15611569
rtext = text.rstrip('\n')
15621570
ltext = rtext.lstrip(' \n\f')
15631571
spaces = len(rtext) - len(ltext)
1564-
if self.offset is None:
1572+
if offset is None:
15651573
yield ' {}\n'.format(ltext)
1566-
elif isinstance(self.offset, int):
1567-
offset = self.offset
1568-
if self.lineno == self.end_lineno:
1574+
elif isinstance(offset, int):
1575+
if lineno == end_lineno:
15691576
end_offset = (
1570-
self.end_offset
1577+
end_offset
15711578
if (
1572-
isinstance(self.end_offset, int)
1573-
and self.end_offset != 0
1579+
isinstance(end_offset, int)
1580+
and end_offset != 0
15741581
)
15751582
else offset
15761583
)
15771584
else:
15781585
end_offset = len(rtext) + 1
15791586

1580-
if self.text and offset > len(self.text):
1587+
if text and offset > len(text):
15811588
offset = len(rtext) + 1
1582-
if self.text and end_offset > len(self.text):
1589+
if text and end_offset > len(text):
15831590
end_offset = len(rtext) + 1
15841591
if offset >= end_offset or end_offset < 0:
15851592
end_offset = offset + 1
@@ -1611,7 +1618,9 @@ def _format_syntax_error(self, stype, **kwargs):
16111618
)
16121619
else:
16131620
yield ' {}\n'.format(ltext)
1614-
msg = self.msg or "<no detail available>"
1621+
if not isinstance(text, str):
1622+
msg = self.msg
1623+
msg = msg or "<no detail available>"
16151624
yield "{}{}{}: {}{}{}{}\n".format(
16161625
theme.type,
16171626
stype,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`traceback.TracebackException.format_exception_only` mutating
2+
its own attributes when suggesting a keyword typo fix, making repeated
3+
calls produce different output. Patch by tonghuaroot.

0 commit comments

Comments
 (0)