Skip to content

Commit bc2f920

Browse files
authored
Fix IndexError in traceback formatter
This protects us from differences between the file on disk and the file that was originally loaded. This also corrects a bug where we'd incorrectly show the last line of the file if the detected line number was 0 (as it is for shim frames). Signed-off-by: Kirill Ignatev <kiri11@users.noreply.github.com>
1 parent b87b6b6 commit bc2f920

3 files changed

Lines changed: 61 additions & 14 deletions

File tree

news/191.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``IndexError`` in traceback formatting when the source file has fewer lines than expected.

src/pystack/traceback_formatter.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,19 @@ def format_frame(frame: PyFrame) -> Iterable[str]:
2727
if os.path.exists(code.filename):
2828
with open(code.filename, "r") as fp:
2929
lines = fp.readlines()
30-
source = lines[code.location.lineno - 1]
31-
line_start, line_end, col_start, col_end = code.location
32-
if col_start == col_end == 0:
33-
yield f" {source.strip()}"
34-
else:
35-
if line_end != line_start:
36-
col_end = len(source)
37-
a = source[:col_start]
38-
b = source[col_start:col_end].strip("\n")
39-
c = source[col_end:]
40-
final = f'{a}{colored(b, color="blue")}{c}'
41-
yield f" {final.strip()}"
30+
if 1 <= code.location.lineno <= len(lines):
31+
source = lines[code.location.lineno - 1]
32+
line_start, line_end, col_start, col_end = code.location
33+
if col_start == col_end == 0:
34+
yield f" {source.strip()}"
35+
else:
36+
if line_end != line_start:
37+
col_end = len(source)
38+
a = source[:col_start]
39+
b = source[col_start:col_end].strip("\n")
40+
c = source[col_end:]
41+
final = f'{a}{colored(b, color="blue")}{c}'
42+
yield f" {final.strip()}"
4243

4344
if frame.arguments:
4445
yield f" {colored('Arguments:', attrs=['faint'])}"

tests/unit/test_traceback_formatter.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,53 @@ def test_traceback_formatter_no_mergeable_native_frames():
241241
]
242242

243243

244+
def test_traceback_formatter_with_source_lineno_out_of_range():
245+
# GIVEN
246+
247+
frame = PyFrame(
248+
prev=None,
249+
next=None,
250+
code=PyCodeObject(
251+
filename="template.html",
252+
scope="root",
253+
location=LocationInfo(13, 13, 0, 0),
254+
),
255+
arguments={"arg": "value"},
256+
locals={"local": "value"},
257+
is_entry=True,
258+
is_shim=False,
259+
)
260+
261+
thread = PyThread(
262+
tid=1,
263+
frame=frame,
264+
native_frames=[],
265+
holds_the_gil=False,
266+
is_gc_collecting=False,
267+
python_version=(3, 8),
268+
)
269+
270+
# WHEN
271+
source_data = ""
272+
with (
273+
patch("builtins.open", mock_open(read_data=source_data)),
274+
patch("os.path.exists", return_value=True),
275+
):
276+
lines = list(format_thread(thread, NativeReportingMode.OFF))
277+
278+
# THEN
279+
280+
assert lines == [
281+
"Traceback for thread 1 [] (most recent call last):",
282+
' (Python) File "template.html", line 13, in root',
283+
" Arguments:",
284+
" arg: value",
285+
" Locals:",
286+
" local: value",
287+
"",
288+
]
289+
290+
244291
def test_traceback_formatter_with_source():
245292
# GIVEN
246293

@@ -1621,12 +1668,10 @@ def test_native_traceback_with_shim_frames():
16211668
' (Python) File "file1.py", line 1, in function1',
16221669
' x = "This is the line 1" or (1+1)',
16231670
' (Python) File "<shim>", line 0, in <shim>',
1624-
' x = "This is the line 4" or (1+1)',
16251671
' (C) File "native_file2.c", line 2, in native_function2 (library.so)',
16261672
' (Python) File "file2.py", line 2, in function2',
16271673
' x = "This is the line 2" or (1+1)',
16281674
' (Python) File "<shim>", line 0, in <shim>',
1629-
' x = "This is the line 4" or (1+1)',
16301675
' (C) File "native_file3.c", line 3, in native_function3 (library.so)',
16311676
' (Python) File "file3.py", line 3, in function3',
16321677
' x = "This is the line 3" or (1+1)',

0 commit comments

Comments
 (0)