https://github.com/python/cpython/commit/fbbde4dc6a69219d831c3098d352310d2fc94e3c
commit: fbbde4dc6a69219d831c3098d352310d2fc94e3c
branch: 3.12
author: Irit Katriel <[email protected]>
committer: iritkatriel <[email protected]>
date: 2024-08-22T10:22:43+01:00
summary:

[3.12] gh-123142: Fix too wide source locations in tracebacks of exceptions 
from broken iterables in comprehensions (GH-123173). (#123210)

(cherry picked from commit ec89620e5e147ba028a46dd695ef073a72000b84)

files:
A Misc/NEWS.d/next/Core and 
Builtins/2024-08-20-12-29-52.gh-issue-123142.3PXiNb.rst
M Lib/test/support/__init__.py
M Lib/test/test_compile.py
M Lib/test/test_dictcomps.py
M Lib/test/test_iter.py
M Lib/test/test_listcomps.py
M Lib/test/test_setcomps.py
M Python/compile.c

diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index c2ef9f46c2ff96..e2310627414797 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -58,6 +58,7 @@
     "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
     "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT",
     "skip_on_s390x",
+    "BrokenIter",
     ]
 
 
@@ -2468,3 +2469,17 @@ def is_slot_wrapper(name, value):
         value = ns[name]
         if is_slot_wrapper(cls, name, value):
             yield name, True
+
+
+class BrokenIter:
+    def __init__(self, init_raises=False, next_raises=False):
+        if init_raises:
+            1/0
+        self.next_raises = next_raises
+
+    def __next__(self):
+        if self.next_raises:
+            1/0
+
+    def __iter__(self):
+        return self
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 4fd71c0c1630dc..72bf87d10e4f3d 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -1065,7 +1065,7 @@ def return_genexp():
                     x
                     in
                     y)
-        genexp_lines = [0, 2, 0]
+        genexp_lines = [0, 4, 2, 0, 4]
 
         genexp_code = return_genexp.__code__.co_consts[1]
         code_lines = self.get_code_lines(genexp_code)
@@ -1431,7 +1431,7 @@ def test_multiline_generator_expression(self):
         self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
             line=1, end_line=2, column=1, end_column=8, occurrence=1)
         self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_CONST',
-            line=1, end_line=6, column=0, end_column=32, occurrence=1)
+            line=4, end_line=4, column=7, end_column=14, occurrence=1)
 
     def test_multiline_async_generator_expression(self):
         snippet = textwrap.dedent("""\
diff --git a/Lib/test/test_dictcomps.py b/Lib/test/test_dictcomps.py
index 472e3dfa0d8a0a..e9df2967627240 100644
--- a/Lib/test/test_dictcomps.py
+++ b/Lib/test/test_dictcomps.py
@@ -1,5 +1,8 @@
+import traceback
 import unittest
 
+from test.support import BrokenIter
+
 # For scope testing.
 g = "Global variable"
 
@@ -127,6 +130,34 @@ def test_star_expression(self):
         self.assertEqual({i: i*i for i in [*range(4)]}, expected)
         self.assertEqual({i: i*i for i in (*range(4),)}, expected)
 
+    def test_exception_locations(self):
+        # The location of an exception raised from __init__ or
+        # __next__ should should be the iterator expression
+        def init_raises():
+            try:
+                {x:x for x in BrokenIter(init_raises=True)}
+            except Exception as e:
+                return e
+
+        def next_raises():
+            try:
+                {x:x for x in BrokenIter(next_raises=True)}
+            except Exception as e:
+                return e
+
+        for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
+                               (next_raises, "BrokenIter(next_raises=True)"),
+                              ]:
+            with self.subTest(func):
+                exc = func()
+                f = traceback.extract_tb(exc.__traceback__)[0]
+                indent = 16
+                co = func.__code__
+                self.assertEqual(f.lineno, co.co_firstlineno + 2)
+                self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
+                self.assertEqual(f.line[f.colno - indent : f.end_colno - 
indent],
+                                 expected)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py
index ec2b68acb90785..502856e3e2fbe0 100644
--- a/Lib/test/test_iter.py
+++ b/Lib/test/test_iter.py
@@ -5,6 +5,7 @@
 from test.support import cpython_only
 from test.support.os_helper import TESTFN, unlink
 from test.support import check_free_after_iterating, ALWAYS_EQ, NEVER_EQ
+from test.support import BrokenIter
 import pickle
 import collections.abc
 import functools
@@ -1148,35 +1149,22 @@ def test_exception_locations(self):
         # The location of an exception raised from __init__ or
         # __next__ should should be the iterator expression
 
-        class Iter:
-            def __init__(self, init_raises=False, next_raises=False):
-                if init_raises:
-                    1/0
-                self.next_raises = next_raises
-
-            def __next__(self):
-                if self.next_raises:
-                    1/0
-
-            def __iter__(self):
-                return self
-
         def init_raises():
             try:
-                for x in Iter(init_raises=True):
+                for x in BrokenIter(init_raises=True):
                     pass
             except Exception as e:
                 return e
 
         def next_raises():
             try:
-                for x in Iter(next_raises=True):
+                for x in BrokenIter(next_raises=True):
                     pass
             except Exception as e:
                 return e
 
-        for func, expected in [(init_raises, "Iter(init_raises=True)"),
-                               (next_raises, "Iter(next_raises=True)"),
+        for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
+                               (next_raises, "BrokenIter(next_raises=True)"),
                               ]:
             with self.subTest(func):
                 exc = func()
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index 59372df0edf93a..bb99c94e942fff 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -1,8 +1,11 @@
 import doctest
 import textwrap
+import traceback
 import types
 import unittest
 
+from test.support import BrokenIter
+
 
 doctests = """
 ########### Tests borrowed from or inspired by test_genexps.py ############
@@ -706,6 +709,35 @@ def test_multiple_comprehension_name_reuse(self):
         self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, 
scopes=["class"])
         self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, 
scopes=["function", "module"])
 
+    def test_exception_locations(self):
+        # The location of an exception raised from __init__ or
+        # __next__ should should be the iterator expression
+
+        def init_raises():
+            try:
+                [x for x in BrokenIter(init_raises=True)]
+            except Exception as e:
+                return e
+
+        def next_raises():
+            try:
+                [x for x in BrokenIter(next_raises=True)]
+            except Exception as e:
+                return e
+
+        for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
+                               (next_raises, "BrokenIter(next_raises=True)"),
+                              ]:
+            with self.subTest(func):
+                exc = func()
+                f = traceback.extract_tb(exc.__traceback__)[0]
+                indent = 16
+                co = func.__code__
+                self.assertEqual(f.lineno, co.co_firstlineno + 2)
+                self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
+                self.assertEqual(f.line[f.colno - indent : f.end_colno - 
indent],
+                                 expected)
+
 __test__ = {'doctests' : doctests}
 
 def load_tests(loader, tests, pattern):
diff --git a/Lib/test/test_setcomps.py b/Lib/test/test_setcomps.py
index 976fa885bd8ef9..ba4173cd6a7dd7 100644
--- a/Lib/test/test_setcomps.py
+++ b/Lib/test/test_setcomps.py
@@ -1,6 +1,9 @@
 import doctest
+import traceback
 import unittest
 
+from test.support import BrokenIter
+
 
 doctests = """
 ########### Tests mostly copied from test_listcomps.py ############
@@ -148,6 +151,35 @@
 
 """
 
+class SetComprehensionTest(unittest.TestCase):
+    def test_exception_locations(self):
+        # The location of an exception raised from __init__ or
+        # __next__ should should be the iterator expression
+
+        def init_raises():
+            try:
+                {x for x in BrokenIter(init_raises=True)}
+            except Exception as e:
+                return e
+
+        def next_raises():
+            try:
+                {x for x in BrokenIter(next_raises=True)}
+            except Exception as e:
+                return e
+
+        for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
+                               (next_raises, "BrokenIter(next_raises=True)"),
+                              ]:
+            with self.subTest(func):
+                exc = func()
+                f = traceback.extract_tb(exc.__traceback__)[0]
+                indent = 16
+                co = func.__code__
+                self.assertEqual(f.lineno, co.co_firstlineno + 2)
+                self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
+                self.assertEqual(f.line[f.colno - indent : f.end_colno - 
indent],
+                                 expected)
 
 __test__ = {'doctests' : doctests}
 
diff --git a/Misc/NEWS.d/next/Core and 
Builtins/2024-08-20-12-29-52.gh-issue-123142.3PXiNb.rst b/Misc/NEWS.d/next/Core 
and Builtins/2024-08-20-12-29-52.gh-issue-123142.3PXiNb.rst
new file mode 100644
index 00000000000000..0aa70f23bfde87
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and 
Builtins/2024-08-20-12-29-52.gh-issue-123142.3PXiNb.rst 
@@ -0,0 +1,2 @@
+Fix too-wide source location in exception tracebacks coming from broken
+iterables in comprehensions.
diff --git a/Python/compile.c b/Python/compile.c
index 81464a974f59b7..f63ee8293d9cf9 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -5272,14 +5272,15 @@ compiler_sync_comprehension_generator(struct compiler 
*c, location loc,
             }
             if (IS_LABEL(start)) {
                 VISIT(c, expr, gen->iter);
-                ADDOP(c, loc, GET_ITER);
+                ADDOP(c, LOC(gen->iter), GET_ITER);
             }
         }
     }
+
     if (IS_LABEL(start)) {
         depth++;
         USE_LABEL(c, start);
-        ADDOP_JUMP(c, loc, FOR_ITER, anchor);
+        ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor);
     }
     VISIT(c, expr, gen->target);
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to