diff options
Diffstat (limited to 'Objects/codeobject.c')
-rw-r--r-- | Objects/codeobject.c | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/Objects/codeobject.c b/Objects/codeobject.c index f832911..8ae2399 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -451,3 +451,136 @@ PyCode_Addr2Line(PyCodeObject *co, int addrq) } return line; } + +/* + Check whether the current instruction is at the start of a line. + + */ + + /* The theory of SET_LINENO-less tracing. + + In a nutshell, we use the co_lnotab field of the code object + to tell when execution has moved onto a different line. + + As mentioned above, the basic idea is so set things up so + that + + *instr_lb <= frame->f_lasti < *instr_ub + + is true so long as execution does not change lines. + + This is all fairly simple. Digging the information out of + co_lnotab takes some work, but is conceptually clear. + + Somewhat harder to explain is why we don't *always* call the + line trace function when the above test fails. + + Consider this code: + + 1: def f(a): + 2: if a: + 3: print 1 + 4: else: + 5: print 2 + + which compiles to this: + + 2 0 LOAD_FAST 0 (a) + 3 JUMP_IF_FALSE 9 (to 15) + 6 POP_TOP + + 3 7 LOAD_CONST 1 (1) + 10 PRINT_ITEM + 11 PRINT_NEWLINE + 12 JUMP_FORWARD 6 (to 21) + >> 15 POP_TOP + + 5 16 LOAD_CONST 2 (2) + 19 PRINT_ITEM + 20 PRINT_NEWLINE + >> 21 LOAD_CONST 0 (None) + 24 RETURN_VALUE + + If 'a' is false, execution will jump to instruction at offset + 15 and the co_lnotab will claim that execution has moved to + line 3. This is at best misleading. In this case we could + associate the POP_TOP with line 4, but that doesn't make + sense in all cases (I think). + + What we do is only call the line trace function if the co_lnotab + indicates we have jumped to the *start* of a line, i.e. if the + current instruction offset matches the offset given for the + start of a line by the co_lnotab. + + This also takes care of the situation where 'a' is true. + Execution will jump from instruction offset 12 to offset 21. + Then the co_lnotab would imply that execution has moved to line + 5, which is again misleading. + + Why do we set f_lineno when tracing? Well, consider the code + above when 'a' is true. If stepping through this with 'n' in + pdb, you would stop at line 1 with a "call" type event, then + line events on lines 2 and 3, then a "return" type event -- but + you would be shown line 5 during this event. This is a change + from the behaviour in 2.2 and before, and I've found it + confusing in practice. By setting and using f_lineno when + tracing, one can report a line number different from that + suggested by f_lasti on this one occasion where it's desirable. + */ + + +int +PyCode_CheckLineNumber(PyCodeObject* co, int lasti, PyAddrPair *bounds) +{ + int size, addr, line; + unsigned char* p; + + p = (unsigned char*)PyString_AS_STRING(co->co_lnotab); + size = PyString_GET_SIZE(co->co_lnotab) / 2; + + addr = 0; + line = co->co_firstlineno; + assert(line > 0); + + /* possible optimization: if f->f_lasti == instr_ub + (likely to be a common case) then we already know + instr_lb -- if we stored the matching value of p + somwhere we could skip the first while loop. */ + + /* see comments in compile.c for the description of + co_lnotab. A point to remember: increments to p + should come in pairs -- although we don't care about + the line increments here, treating them as byte + increments gets confusing, to say the least. */ + + while (size > 0) { + if (addr + *p > lasti) + break; + addr += *p++; + if (*p) + bounds->ap_lower = addr; + line += *p++; + --size; + } + + /* If lasti and addr don't match exactly, we don't want to + change the lineno slot on the frame or execute a trace + function. Return -1 instead. + */ + if (addr != lasti) + line = -1; + + if (size > 0) { + while (--size >= 0) { + addr += *p++; + if (*p++) + break; + } + bounds->ap_upper = addr; + } + else { + bounds->ap_upper = INT_MAX; + } + + return line; +} |