Skip to content

Commit d54bd16

Browse files
committed
[3.13] pythongh-123142: Fix too wide source locations in tracebacks of exceptions from broken iterables in comprehensions (pythonGH-123173).
(cherry picked from commit ec89620) Co-authored-by: Irit Katriel <[email protected]>
1 parent 50a595b commit d54bd16

File tree

8 files changed

+123
-22
lines changed

8 files changed

+123
-22
lines changed

Lib/test/support/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
"Py_DEBUG", "exceeds_recursion_limit", "get_c_recursion_limit",
6060
"skip_on_s390x",
6161
"without_optimizer",
62-
"force_not_colorized"
62+
"force_not_colorized",
63+
"BrokenIter",
6364
]
6465

6566

@@ -2672,3 +2673,17 @@ def initialized_with_pyrepl():
26722673
"""Detect whether PyREPL was used during Python initialization."""
26732674
# If the main module has a __file__ attribute it's a Python module, which means PyREPL.
26742675
return hasattr(sys.modules["__main__"], "__file__")
2676+
2677+
2678+
class BrokenIter:
2679+
def __init__(self, init_raises=False, next_raises=False):
2680+
if init_raises:
2681+
1/0
2682+
self.next_raises = next_raises
2683+
2684+
def __next__(self):
2685+
if self.next_raises:
2686+
1/0
2687+
2688+
def __iter__(self):
2689+
return self

Lib/test/test_compile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,7 @@ def return_genexp():
11721172
x
11731173
in
11741174
y)
1175-
genexp_lines = [0, 2, 0]
1175+
genexp_lines = [0, 4, 2, 0, 4]
11761176

11771177
genexp_code = return_genexp.__code__.co_consts[1]
11781178
code_lines = self.get_code_lines(genexp_code)
@@ -1627,7 +1627,7 @@ def test_multiline_generator_expression(self):
16271627
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
16281628
line=1, end_line=2, column=1, end_column=8, occurrence=1)
16291629
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_CONST',
1630-
line=1, end_line=6, column=0, end_column=32, occurrence=1)
1630+
line=4, end_line=4, column=7, end_column=14, occurrence=1)
16311631

16321632
def test_multiline_async_generator_expression(self):
16331633
snippet = textwrap.dedent("""\

Lib/test/test_dictcomps.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import traceback
12
import unittest
23

4+
from test.support import BrokenIter
5+
36
# For scope testing.
47
g = "Global variable"
58

@@ -127,6 +130,34 @@ def test_star_expression(self):
127130
self.assertEqual({i: i*i for i in [*range(4)]}, expected)
128131
self.assertEqual({i: i*i for i in (*range(4),)}, expected)
129132

133+
def test_exception_locations(self):
134+
# The location of an exception raised from __init__ or
135+
# __next__ should should be the iterator expression
136+
def init_raises():
137+
try:
138+
{x:x for x in BrokenIter(init_raises=True)}
139+
except Exception as e:
140+
return e
141+
142+
def next_raises():
143+
try:
144+
{x:x for x in BrokenIter(next_raises=True)}
145+
except Exception as e:
146+
return e
147+
148+
for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
149+
(next_raises, "BrokenIter(next_raises=True)"),
150+
]:
151+
with self.subTest(func):
152+
exc = func()
153+
f = traceback.extract_tb(exc.__traceback__)[0]
154+
indent = 16
155+
co = func.__code__
156+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
157+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
158+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
159+
expected)
160+
130161

131162
if __name__ == "__main__":
132163
unittest.main()

Lib/test/test_iter.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from test.support import cpython_only
66
from test.support.os_helper import TESTFN, unlink
77
from test.support import check_free_after_iterating, ALWAYS_EQ, NEVER_EQ
8+
from test.support import BrokenIter
89
import pickle
910
import collections.abc
1011
import functools
@@ -1148,35 +1149,22 @@ def test_exception_locations(self):
11481149
# The location of an exception raised from __init__ or
11491150
# __next__ should should be the iterator expression
11501151

1151-
class Iter:
1152-
def __init__(self, init_raises=False, next_raises=False):
1153-
if init_raises:
1154-
1/0
1155-
self.next_raises = next_raises
1156-
1157-
def __next__(self):
1158-
if self.next_raises:
1159-
1/0
1160-
1161-
def __iter__(self):
1162-
return self
1163-
11641152
def init_raises():
11651153
try:
1166-
for x in Iter(init_raises=True):
1154+
for x in BrokenIter(init_raises=True):
11671155
pass
11681156
except Exception as e:
11691157
return e
11701158

11711159
def next_raises():
11721160
try:
1173-
for x in Iter(next_raises=True):
1161+
for x in BrokenIter(next_raises=True):
11741162
pass
11751163
except Exception as e:
11761164
return e
11771165

1178-
for func, expected in [(init_raises, "Iter(init_raises=True)"),
1179-
(next_raises, "Iter(next_raises=True)"),
1166+
for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
1167+
(next_raises, "BrokenIter(next_raises=True)"),
11801168
]:
11811169
with self.subTest(func):
11821170
exc = func()

Lib/test/test_listcomps.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import doctest
22
import textwrap
3+
import traceback
34
import types
45
import unittest
56

7+
from test.support import BrokenIter
8+
69

710
doctests = """
811
########### Tests borrowed from or inspired by test_genexps.py ############
@@ -711,6 +714,35 @@ def test_multiple_comprehension_name_reuse(self):
711714
self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, scopes=["class"])
712715
self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, scopes=["function", "module"])
713716

717+
def test_exception_locations(self):
718+
# The location of an exception raised from __init__ or
719+
# __next__ should should be the iterator expression
720+
721+
def init_raises():
722+
try:
723+
[x for x in BrokenIter(init_raises=True)]
724+
except Exception as e:
725+
return e
726+
727+
def next_raises():
728+
try:
729+
[x for x in BrokenIter(next_raises=True)]
730+
except Exception as e:
731+
return e
732+
733+
for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
734+
(next_raises, "BrokenIter(next_raises=True)"),
735+
]:
736+
with self.subTest(func):
737+
exc = func()
738+
f = traceback.extract_tb(exc.__traceback__)[0]
739+
indent = 16
740+
co = func.__code__
741+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
742+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
743+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
744+
expected)
745+
714746
__test__ = {'doctests' : doctests}
715747

716748
def load_tests(loader, tests, pattern):

Lib/test/test_setcomps.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import doctest
2+
import traceback
23
import unittest
34

5+
from test.support import BrokenIter
6+
47

58
doctests = """
69
########### Tests mostly copied from test_listcomps.py ############
@@ -148,6 +151,35 @@
148151
149152
"""
150153

154+
class SetComprehensionTest(unittest.TestCase):
155+
def test_exception_locations(self):
156+
# The location of an exception raised from __init__ or
157+
# __next__ should should be the iterator expression
158+
159+
def init_raises():
160+
try:
161+
{x for x in BrokenIter(init_raises=True)}
162+
except Exception as e:
163+
return e
164+
165+
def next_raises():
166+
try:
167+
{x for x in BrokenIter(next_raises=True)}
168+
except Exception as e:
169+
return e
170+
171+
for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
172+
(next_raises, "BrokenIter(next_raises=True)"),
173+
]:
174+
with self.subTest(func):
175+
exc = func()
176+
f = traceback.extract_tb(exc.__traceback__)[0]
177+
indent = 16
178+
co = func.__code__
179+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
180+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
181+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
182+
expected)
151183

152184
__test__ = {'doctests' : doctests}
153185

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix too-wide source location in exception tracebacks coming from broken
2+
iterables in comprehensions.

Python/compile.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5384,14 +5384,15 @@ compiler_sync_comprehension_generator(struct compiler *c, location loc,
53845384
}
53855385
if (IS_LABEL(start)) {
53865386
VISIT(c, expr, gen->iter);
5387-
ADDOP(c, loc, GET_ITER);
5387+
ADDOP(c, LOC(gen->iter), GET_ITER);
53885388
}
53895389
}
53905390
}
5391+
53915392
if (IS_LABEL(start)) {
53925393
depth++;
53935394
USE_LABEL(c, start);
5394-
ADDOP_JUMP(c, loc, FOR_ITER, anchor);
5395+
ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor);
53955396
}
53965397
VISIT(c, expr, gen->target);
53975398

0 commit comments

Comments
 (0)