diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py index 144fc66dcea..7f7db334ed0 100644 --- a/Lib/test/test_trace.py +++ b/Lib/test/test_trace.py @@ -252,14 +252,16 @@ class TraceTestCase(unittest.TestCase): "\n".join(difflib.ndiff([str(x) for x in expected_events], [str(x) for x in events]))) - - def run_test(self, func): + def run_and_compare(self, func, events): tracer = Tracer() sys.settrace(tracer.trace) func() sys.settrace(None) self.compare_events(func.func_code.co_firstlineno, - tracer.events, func.events) + tracer.events, events) + + def run_test(self, func): + self.run_and_compare(func, func.events) def run_test2(self, func): tracer = Tracer() @@ -321,6 +323,49 @@ class TraceTestCase(unittest.TestCase): self.compare_events(generator_example.__code__.co_firstlineno, tracer.events, generator_example.events) + def test_14_onliner_if(self): + def onliners(): + if True: False + else: True + return 0 + self.run_and_compare( + onliners, + [(0, 'call'), + (1, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_15_loops(self): + # issue1750076: "while" expression is skipped by debugger + def for_example(): + for x in range(2): + pass + self.run_and_compare( + for_example, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (1, 'line'), + (2, 'line'), + (1, 'line'), + (1, 'return')]) + + def while_example(): + # While expression should be traced on every loop + x = 2 + while x > 0: + x -= 1 + self.run_and_compare( + while_example, + [(0, 'call'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (3, 'line'), + (4, 'line'), + (3, 'line'), + (3, 'return')]) + class RaisingTraceFuncTestCase(unittest.TestCase): def trace(self, frame, event, arg): """A trace function that raises an exception in response to a diff --git a/Misc/NEWS b/Misc/NEWS index 38dc8edcb20..8a4b4492a27 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -388,6 +388,10 @@ Core and builtins Library ------- +- #175006: The debugger used to skip the condition of a "while" statement + after the first iteration. Now it correctly steps on the expression, and + breakpoints on the "while" statement are honored on each loop. + - #1765140: add an optional delay argument to FileHandler and its subclasses. Defaults to false (existing behaviour), but if true, defers opening the file until the first call to emit(). diff --git a/Python/compile.c b/Python/compile.c index 0dd1082151e..f9b5ac4a523 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -638,11 +638,16 @@ compiler_next_instr(struct compiler *c, basicblock *b) return b->b_iused++; } -/* Set the i_lineno member of the instruction at offse off if the - line number for the current expression/statement (?) has not +/* Set the i_lineno member of the instruction at offset off if the + line number for the current expression/statement has not already been set. If it has been set, the call has no effect. - Every time a new node is b + The line number is reset in the following cases: + - when entering a new scope + - on each statement + - on each expression that start a new line + - before the "except" clause + - before the "for" and "while" expressions */ static void @@ -1611,9 +1616,8 @@ compiler_for(struct compiler *c, stmt_ty s) VISIT(c, expr, s->v.For.iter); ADDOP(c, GET_ITER); compiler_use_next_block(c, start); - /* XXX(nnorwitz): is there a better way to handle this? - for loops are special, we want to be able to trace them - each time around, so we need to set an extra line number. */ + /* for expressions must be traced on each iteration, + so we need to set an extra line number. */ c->u->u_lineno_set = false; ADDOP_JREL(c, FOR_ITER, cleanup); VISIT(c, expr, s->v.For.target); @@ -1660,6 +1664,9 @@ compiler_while(struct compiler *c, stmt_ty s) if (!compiler_push_fblock(c, LOOP, loop)) return 0; if (constant == -1) { + /* while expressions must be traced on each iteration, + so we need to set an extra line number. */ + c->u->u_lineno_set = false; VISIT(c, expr, s->v.While.test); ADDOP_JREL(c, JUMP_IF_FALSE, anchor); ADDOP(c, POP_TOP); @@ -1840,8 +1847,8 @@ compiler_try_except(struct compiler *c, stmt_ty s) s->v.TryExcept.handlers, i); if (!handler->type && i < n-1) return compiler_error(c, "default 'except:' must be last"); - c->u->u_lineno_set = false; - c->u->u_lineno = handler->lineno; + c->u->u_lineno_set = false; + c->u->u_lineno = handler->lineno; except = compiler_new_block(c); if (except == NULL) return 0; @@ -3553,12 +3560,6 @@ assemble_lnotab(struct assembler *a, struct instr *i) assert(d_bytecode >= 0); assert(d_lineno >= 0); - /* XXX(nnorwitz): is there a better way to handle this? - for loops are special, we want to be able to trace them - each time around, so we need to set an extra line number. */ - if (d_lineno == 0 && i->i_opcode != FOR_ITER) - return 1; - if (d_bytecode > 255) { int j, nbytes, ncodes = d_bytecode / 255; nbytes = a->a_lnotab_off + 2 * ncodes;