This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Python 2.5+ skips while statements in debuggers
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.0, Python 2.6, Python 2.5
process
Status: closed Resolution: accepted
Dependencies: Superseder:
Assigned To: amaury.forgeotdarc Nosy List: amaury.forgeotdarc, georg.brandl, gotgenes, ncoghlan, nir1408, nnorwitz, orivej
Priority: high Keywords:

Created on 2007-07-08 23:24 by gotgenes, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
simple.py gotgenes, 2007-07-08 23:32
while_lnotab.patch amaury.forgeotdarc, 2008-01-29 21:48
Messages (12)
msg32465 - (view) Author: Chris Lasher (gotgenes) Date: 2007-07-08 23:24
Starting with Python 2.5, debuggers pass through a "while" statement on its declaration, but never return to it during the remainder of the loop. They should be able to return to the "while" statement as it has an evaluative step in it. This has several important implications: one may not check the state of variables at prior to their evaluation in the "while" statement, and any breakpoints set on the "while" statement are ignored once within the while loop.

Python prior versions (2.4 and below) exhibit expected behavior in that the "while" statement is returned to after each successful iteration through the loop, and breakpoints on "while" statements are honored.
msg32466 - (view) Author: Chris Lasher (gotgenes) Date: 2007-07-08 23:32
Here's a file for demonstration purposes.
File Added: simple.py
msg32467 - (view) Author: Chris Lasher (gotgenes) Date: 2007-07-08 23:35
Notice how in Python 2.5, the breakpoint is met once then passed over, leading straight to the final print statement

--

chris@feathers:~/development/playground$ python2.5 -m pdb simple.py 
> /home/chris/development/playground/simple.py(3)<module>()
-> a = 10
(Pdb) b 5
Breakpoint 1 at /home/chris/development/playground/simple.py:5
(Pdb) l
  1     #!/usr/bin/env python
  2  
  3  -> a = 10
  4  
  5 B   while a > 0:
  6         a -= 1      # how do I check the value of a at end of last iter.?
  7  
  8     print "Fin!"
[EOF]
(Pdb) r
> /home/chris/development/playground/simple.py(5)<module>()
-> while a > 0:
(Pdb) r
Fin!
--Return--
> /home/chris/development/playground/simple.py(8)<module>()->None
-> print "Fin!"
(Pdb) 
msg32468 - (view) Author: Chris Lasher (gotgenes) Date: 2007-07-08 23:37
Notice how in Python 2.4, the breakpoint is honored through each iteration through the while loop.

--

chris@feathers:~/development/playground$ python2.4 -m pdb simple.py 
> /home/chris/development/playground/simple.py(3)?()
-> a = 10
(Pdb) b 5
Breakpoint 1 at /home/chris/development/playground/simple.py:5
(Pdb) l
  1     #!/usr/bin/env python
  2  
  3  -> a = 10
  4  
  5 B   while a > 0:
  6         a -= 1      # how do I check the value of a at end of last iter.?
  7  
  8     print "Fin!"
[EOF]
(Pdb) r
> /home/chris/development/playground/simple.py(5)?()
-> while a > 0:
(Pdb) p a
10
(Pdb) r
> /home/chris/development/playground/simple.py(5)?()
-> while a > 0:
(Pdb) p a
9
(Pdb) r
> /home/chris/development/playground/simple.py(5)?()
-> while a > 0:
(Pdb) p a
8
(Pdb) 
msg32469 - (view) Author: Nir (nir1408) Date: 2007-07-24 12:01
Hi Chris,

I have done some digging and it seems the problem is rooted in the way
co_lnotab is constructed for code objects
(http://docs.python.org/ref/types.html#l2h-139).

Consider for example the following code:

> line 0: def loop():
> line 1:    while True:
> line 2:        pass

Both Python 2.4 and Python 2.5 emit the same disassembly for that code
(using dis.dis()) :

>  1            0 SETUP_LOOP              12 (to 15)
>         >>    3 LOAD_GLOBAL              0 (True)
>               6 JUMP_IF_FALSE            4 (to 13)
>               9 POP_TOP
>
>  2           10 JUMP_ABSOLUTE            3
>         >>   13 POP_TOP
>              14 POP_BLOCK
>         >>   15 LOAD_CONST               0 (None)
>              18 RETURN_VALUE

However there is a difference with the co_lnotab structure between the
versions.
Python 2.4 emits the following offsets:

> byte code offsets:   [0, 3, 7]
> source code offsets: [1, 0, 1]

Python 2.5 emits the following offsets for the same code:

> byte code offsets:   [0, 10]
> source code offsets: [1, 1]

Note: The above output was generated with the following code:

> print 'byte code offsets:  ', [ord(x) for x in
> loop.func_code.co_lnotab[::2]]
> print 'source code offsets:', [ord(x) for x in
> loop.func_code.co_lnotab[1::2]]

So, what happens is that in offset 10, the while loop jumps back with
JUMP_ABSOLUTE 3, but in Python 2.5 that offset is missing from the byte
code offsets in co_lnotab. Python considers only offsets that appear in
that structure for the purpose of tracing, and therefore that line
(source line number 1) is skipped when jumped back to in Python 2.5

co_lnotab is constructed in compile.c but since that file was changed
considerably between Python 2.4 and Python 2.5 I did not try to pinpoint
the source of difference.

In addition I do not know the motivation of this difference. It could be
a mere bug, but it can also be by design, meaning that simply reverting
the co_lnotab construction behavior back might break Python 2.5 elsewhere.

Cheers,
Nir 
msg32470 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2007-07-31 14:48
It's a genuine bug in the AST compiler - there are some workarounds in compile.c to make sure a for loop gets visited properly in the debugger, but there is no equivalent for while loops at this stage.

I've had a quick look, but wasn't able to apply the same trick to while loops - hopefully Neal will have some insight into the matter.
msg61435 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2008-01-21 18:38
Raising priority.
msg61831 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-01-29 21:48
Here is an attempt to correct the problem, with unit tests.

I removed half of the trick marked by a "XXX(nnorwitz)", by adding an
attribute to "struct instr" (this attribute is a bitfield and does not
increase the memory usage). FOR_ITER is no more special-cased in
assemble_lnotab.
Note that the attribute is not always set: only when the target block
has already been filled with instructions. Hopefully this is the case
for the beginning of loops (while & for).

Then the second part of the trick (c->u->u_lineno_set = false, already
used in compiler_for) can be applied to compiler_while. Don't know how
to write this better.

Nir's examples now correctly generate the 3 offsets, and the unit test
checks that a "line" event is repeatedly emitted for the "while" condition.
msg62037 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2008-02-04 06:37
I was hoping you could get rid of my entire hack.  I didn't (still
don't) completely understand the intention of the code, so can't really
offer any more advice.  IMO, the patch is an improvement so you should
check it in.
msg62051 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-02-04 21:52
I finally got rid of the entire hack, by removing the "compression" of
the lnotab: now each (effective) call to compiler_set_lineno() will
generate an entry in lnotab.
The patch is even simpler.
the lnotab will grow a little, but only when several statements are on
the same line: semicolons, and also one-line suites (like "if x: return").

Commited r60575.
msg62053 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2008-02-04 22:15
Very nice solution Amaury. As far as I can tell, this should be
backported to 2.5 as well.
msg62054 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-02-04 22:37
Committed r60579 in release25-maint.
History
Date User Action Args
2022-04-11 14:56:25adminsetgithub: 45160
2008-02-04 22:37:02amaury.forgeotdarcsetstatus: pending -> closed
messages: + msg62054
2008-02-04 22:15:59ncoghlansetmessages: + msg62053
2008-02-04 21:52:13amaury.forgeotdarcsetstatus: open -> pending
messages: + msg62051
2008-02-04 06:37:24nnorwitzsetassignee: nnorwitz -> amaury.forgeotdarc
messages: + msg62037
resolution: accepted
2008-01-29 21:48:27amaury.forgeotdarcsetfiles: + while_lnotab.patch
nosy: + amaury.forgeotdarc
messages: + msg61831
2008-01-29 20:15:43orivejsetnosy: + orivej
2008-01-21 18:38:51georg.brandlsetversions: + Python 2.6, Python 3.0
nosy: + georg.brandl
messages: + msg61435
priority: normal -> high
components: + Library (Lib), - None
type: behavior
2007-07-08 23:24:30gotgenescreate