Index: Python/ceval.c =================================================================== RCS file: /cvsroot/python/python/dist/src/Python/ceval.c,v retrieving revision 2.332 diff -c -r2.332 ceval.c *** Python/ceval.c 23 Aug 2002 14:11:35 -0000 2.332 --- Python/ceval.c 29 Aug 2002 10:34:38 -0000 *************** *** 2880,2888 **** 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 call the line ! trace function when executing a POP_TOP or RETURN_NONE ! opcodes. An example probably serves best. Consider this code: --- 2880,2887 ---- 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: *************** *** 2915,2970 **** associate the POP_TOP with line 4, but that doesn't make sense in all cases (I think). ! On the other hand, if 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. ! ! This is why it is important that RETURN_NONE is *only* used ! for the "falling off the end of the function" form of ! returning None -- using it for code like ! ! 1: def f(): ! 2: return ! ! would, once again, lead to misleading tracing behaviour. ! ! It is also worth mentioning that getting tracing behaviour ! right is the *entire* motivation for adding the RETURN_NONE ! opcode. */ ! if (opcode != POP_TOP && opcode != RETURN_NONE && ! (frame->f_lasti < *instr_lb || frame->f_lasti > *instr_ub)) { PyCodeObject* co = frame->f_code; int size, addr; unsigned char* p; - call_trace(func, obj, frame, PyTrace_LINE, Py_None); - size = PyString_Size(co->co_lnotab) / 2; p = (unsigned char*)PyString_AsString(co->co_lnotab); /* 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. */ - addr = 0; - /* 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 > frame->f_lasti) break; addr += *p++; p++; --size; } *instr_lb = addr; if (size > 0) { while (--size >= 0) { --- 2914,2962 ---- 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. ! */ ! if ((frame->f_lasti < *instr_lb || frame->f_lasti >= *instr_ub)) { PyCodeObject* co = frame->f_code; int size, addr; unsigned char* p; size = PyString_Size(co->co_lnotab) / 2; p = (unsigned char*)PyString_AsString(co->co_lnotab); + addr = 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 > frame->f_lasti) break; addr += *p++; p++; --size; } + if (addr == frame->f_lasti) + call_trace(func, obj, frame, + PyTrace_LINE, Py_None); *instr_lb = addr; if (size > 0) { while (--size >= 0) { Index: Lib/test/test_trace.py =================================================================== RCS file: Lib/test/test_trace.py diff -N Lib/test/test_trace.py *** /dev/null 1 Jan 1970 00:00:00 -0000 --- Lib/test/test_trace.py 29 Aug 2002 10:34:38 -0000 *************** *** 0 **** --- 1,112 ---- + # Testing the line trace facility. + + from test import test_support + import unittest + import sys + import pprint + import os + import difflib + + # A very basic example. If this fails, we're in deep trouble. + def basic(): + return 1 + + basic.events = [(0, 'call'), + (1, 'line'), + (1, 'return')] + + # Armin Rigo's failing example: + def arigo_example(): + x = 1 + del x + while 0: + pass + x = 1 + + arigo_example.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (5, 'line'), + (5, 'return')] + + # check that lines consisting of just one instruction get traced: + def one_instr_line(): + x = 1 + del x + x = 1 + + one_instr_line.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (3, 'return')] + + def no_pop_tops(): # 0 + x = 1 # 1 + for a in range(2): # 2 + if a: # 3 + x = 1 # 4 + else: # 5 + x = 1 # 6 + + no_pop_tops.events = [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (6, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (2, 'line'), + (6, 'return')] + + def no_pop_blocks(): + while 0: + bla + x = 1 + + no_pop_blocks.events = [(0, 'call'), + (1, 'line'), + (3, 'line'), + (3, 'return')] + + class Tracer: + def __init__(self): + self.events = [] + def trace(self, frame, event, arg): + self.events.append((frame.f_lineno, event)) + return self.trace + + class TraceTestCase(unittest.TestCase): + def run_test(self, func): + tracer = Tracer() + sys.settrace(tracer.trace) + func() + sys.settrace(None) + fl = func.func_code.co_firstlineno + events = [(l - fl, e) for (l, e) in tracer.events] + if events != func.events: + self.fail( + "events did not match expectation:\n" + + "\n".join(difflib.ndiff(map(str, func.events), + map(str, events)))) + + def test_1_basic(self): + self.run_test(basic) + def test_2_arigo(self): + self.run_test(arigo_example) + def test_3_one_instr(self): + self.run_test(one_instr_line) + def test_4_no_pop_blocks(self): + self.run_test(no_pop_blocks) + def test_5_no_pop_tops(self): + self.run_test(no_pop_tops) + + + + def test_main(): + test_support.run_unittest(TraceTestCase) + + if __name__ == "__main__": + test_main()