#1750076: Debugger did not step on every iteration of a while statement.

The mapping between bytecode offsets and source lines (lnotab) did not contain
an entry for the beginning of the loop.

Now it does, and the lnotab can be a bit larger:
in particular, several statements on the same line generate several entries.
However, this does not bother the settrace function, which will trigger only
one 'line' event.

The lnotab seems to be exactly the same as with python2.4.
This commit is contained in:
Amaury Forgeot d'Arc 2008-02-04 21:45:05 +00:00
parent 578a8caf43
commit 6fd03bb607
3 changed files with 67 additions and 17 deletions

View File

@ -252,14 +252,16 @@ class TraceTestCase(unittest.TestCase):
"\n".join(difflib.ndiff([str(x) for x in expected_events], "\n".join(difflib.ndiff([str(x) for x in expected_events],
[str(x) for x in events]))) [str(x) for x in events])))
def run_and_compare(self, func, events):
def run_test(self, func):
tracer = Tracer() tracer = Tracer()
sys.settrace(tracer.trace) sys.settrace(tracer.trace)
func() func()
sys.settrace(None) sys.settrace(None)
self.compare_events(func.func_code.co_firstlineno, 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): def run_test2(self, func):
tracer = Tracer() tracer = Tracer()
@ -321,6 +323,49 @@ class TraceTestCase(unittest.TestCase):
self.compare_events(generator_example.__code__.co_firstlineno, self.compare_events(generator_example.__code__.co_firstlineno,
tracer.events, generator_example.events) 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): class RaisingTraceFuncTestCase(unittest.TestCase):
def trace(self, frame, event, arg): def trace(self, frame, event, arg):
"""A trace function that raises an exception in response to a """A trace function that raises an exception in response to a

View File

@ -388,6 +388,10 @@ Core and builtins
Library 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 - #1765140: add an optional delay argument to FileHandler and its
subclasses. Defaults to false (existing behaviour), but if true, subclasses. Defaults to false (existing behaviour), but if true,
defers opening the file until the first call to emit(). defers opening the file until the first call to emit().

View File

@ -638,11 +638,16 @@ compiler_next_instr(struct compiler *c, basicblock *b)
return b->b_iused++; return b->b_iused++;
} }
/* Set the i_lineno member of the instruction at offse off if the /* Set the i_lineno member of the instruction at offset off if the
line number for the current expression/statement (?) has not line number for the current expression/statement has not
already been set. If it has been set, the call has no effect. 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 static void
@ -1611,9 +1616,8 @@ compiler_for(struct compiler *c, stmt_ty s)
VISIT(c, expr, s->v.For.iter); VISIT(c, expr, s->v.For.iter);
ADDOP(c, GET_ITER); ADDOP(c, GET_ITER);
compiler_use_next_block(c, start); compiler_use_next_block(c, start);
/* XXX(nnorwitz): is there a better way to handle this? /* for expressions must be traced on each iteration,
for loops are special, we want to be able to trace them so we need to set an extra line number. */
each time around, so we need to set an extra line number. */
c->u->u_lineno_set = false; c->u->u_lineno_set = false;
ADDOP_JREL(c, FOR_ITER, cleanup); ADDOP_JREL(c, FOR_ITER, cleanup);
VISIT(c, expr, s->v.For.target); 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)) if (!compiler_push_fblock(c, LOOP, loop))
return 0; return 0;
if (constant == -1) { 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); VISIT(c, expr, s->v.While.test);
ADDOP_JREL(c, JUMP_IF_FALSE, anchor); ADDOP_JREL(c, JUMP_IF_FALSE, anchor);
ADDOP(c, POP_TOP); ADDOP(c, POP_TOP);
@ -1840,8 +1847,8 @@ compiler_try_except(struct compiler *c, stmt_ty s)
s->v.TryExcept.handlers, i); s->v.TryExcept.handlers, i);
if (!handler->type && i < n-1) if (!handler->type && i < n-1)
return compiler_error(c, "default 'except:' must be last"); return compiler_error(c, "default 'except:' must be last");
c->u->u_lineno_set = false; c->u->u_lineno_set = false;
c->u->u_lineno = handler->lineno; c->u->u_lineno = handler->lineno;
except = compiler_new_block(c); except = compiler_new_block(c);
if (except == NULL) if (except == NULL)
return 0; return 0;
@ -3553,12 +3560,6 @@ assemble_lnotab(struct assembler *a, struct instr *i)
assert(d_bytecode >= 0); assert(d_bytecode >= 0);
assert(d_lineno >= 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) { if (d_bytecode > 255) {
int j, nbytes, ncodes = d_bytecode / 255; int j, nbytes, ncodes = d_bytecode / 255;
nbytes = a->a_lnotab_off + 2 * ncodes; nbytes = a->a_lnotab_off + 2 * ncodes;