From 4c53e63cc966f98e141a09bc435b9f9c713b152d Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Fri, 10 Jan 2020 09:24:22 +0000 Subject: [PATCH] bpo-39166: Fix trace of last iteration of async for loops (#17800) --- Lib/test/test_sys_settrace.py | 76 +++++++++++++++++++ .../2020-01-02-22-22-03.bpo-39166.Qt-UeD.rst | 2 + Python/ceval.c | 14 ++-- 3 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-01-02-22-22-03.bpo-39166.Qt-UeD.rst diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index a0d1122fad8..bead57c44b4 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -526,6 +526,82 @@ class TraceTestCase(unittest.TestCase): (7, 'line'), (7, 'return')]) + def test_20_async_for_loop(self): + class AsyncIteratorWrapper: + def __init__(self, obj): + self._it = iter(obj) + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return next(self._it) + except StopIteration: + raise StopAsyncIteration + + async def doit_async(): + async for letter in AsyncIteratorWrapper("abc"): + x = letter + y = 42 + + def run(tracer): + x = doit_async() + try: + sys.settrace(tracer) + x.send(None) + finally: + sys.settrace(None) + + tracer = self.make_tracer() + events = [ + (0, 'call'), + (1, 'line'), + (-12, 'call'), + (-11, 'line'), + (-11, 'return'), + (-9, 'call'), + (-8, 'line'), + (-8, 'return'), + (-6, 'call'), + (-5, 'line'), + (-4, 'line'), + (-4, 'return'), + (1, 'exception'), + (2, 'line'), + (1, 'line'), + (-6, 'call'), + (-5, 'line'), + (-4, 'line'), + (-4, 'return'), + (1, 'exception'), + (2, 'line'), + (1, 'line'), + (-6, 'call'), + (-5, 'line'), + (-4, 'line'), + (-4, 'return'), + (1, 'exception'), + (2, 'line'), + (1, 'line'), + (-6, 'call'), + (-5, 'line'), + (-4, 'line'), + (-4, 'exception'), + (-3, 'line'), + (-2, 'line'), + (-2, 'exception'), + (-2, 'return'), + (1, 'exception'), + (3, 'line'), + (3, 'return')] + try: + run(tracer.trace) + except Exception: + pass + self.compare_events(doit_async.__code__.co_firstlineno, + tracer.events, events) + class SkipLineEventsTraceTestCase(TraceTestCase): """Repeat the trace tests, but with per-line events skipped""" diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-01-02-22-22-03.bpo-39166.Qt-UeD.rst b/Misc/NEWS.d/next/Core and Builtins/2020-01-02-22-22-03.bpo-39166.Qt-UeD.rst new file mode 100644 index 00000000000..4737e9c4d2e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-01-02-22-22-03.bpo-39166.Qt-UeD.rst @@ -0,0 +1,2 @@ +Fix incorrect line execution reporting in trace functions when tracing the +last iteration of asynchronous for loops. Patch by Pablo Galindo. diff --git a/Python/ceval.c b/Python/ceval.c index bd9454b2812..3bbd0ca9667 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3610,11 +3610,15 @@ exception_unwind: PUSH(val); PUSH(exc); JUMPTO(handler); - if (_Py_TracingPossible(ceval) && - ((f->f_lasti < instr_lb || f->f_lasti >= instr_ub) || - !(f->f_lasti == instr_lb || f->f_lasti < instr_prev))) { - /* Make sure that we trace line after exception */ - instr_prev = INT_MAX; + if (_Py_TracingPossible(ceval)) { + int needs_new_execution_window = (f->f_lasti < instr_lb || f->f_lasti >= instr_ub); + int needs_line_update = (f->f_lasti == instr_lb || f->f_lasti < instr_prev); + /* Make sure that we trace line after exception if we are in a new execution + * window or we don't need a line update and we are not in the first instruction + * of the line. */ + if (needs_new_execution_window || (!needs_line_update && instr_lb > 0)) { + instr_prev = INT_MAX; + } } /* Resume normal execution */ goto main_loop;