Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 53d58bb

Browse files
author
Michael W. Hudson
committed
Further SET_LINENO reomval fixes. See comments in patch #587933.
Use a slightly different strategy to determine when not to call the line trace function. This removes the need for the RETURN_NONE opcode, so that's gone again. Update docs and comments to match. Thanks to Neal and Armin! Also add a test suite. This should have come with the original patch...
1 parent b05e056 commit 53d58bb

7 files changed

Lines changed: 144 additions & 55 deletions

File tree

Doc/lib/libdis.tex

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ \section{\module{dis} ---
2727
3 LOAD_FAST 0 (alist)
2828
6 CALL_FUNCTION 1
2929
9 RETURN_VALUE
30-
10 RETURN_NONE
30+
10 LOAD_CONST 0 (None)
31+
13 RETURN_VALUE
3132
\end{verbatim}
3233

3334
(The ``2'' is a line number).
@@ -401,14 +402,6 @@ \subsection{Python Byte Code Instructions}
401402
Returns with TOS to the caller of the function.
402403
\end{opcodedesc}
403404

404-
\begin{opcodedesc}{RETURN_NONE}{}
405-
Returns \constant{None} to the caller of the function. This opcode is
406-
generated as the last opcode of every function and only then, for
407-
reasons to do with tracing support. See the comments in the function
408-
\cfunction{maybe_call_line_trace} in \file{Python/ceval.c} for the
409-
gory details. \versionadded{2.3}.
410-
\end{opcodedesc}
411-
412405
\begin{opcodedesc}{YIELD_VALUE}{}
413406
Pops \code{TOS} and yields it from a generator.
414407
\end{opcodedesc}

Doc/whatsnew/whatsnew23.tex

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,11 +1225,6 @@ \section{Other Changes and Fixes}
12251225
This will have the added effect of making the code work as desired
12261226
under ``python -O'' in earlier versions of Python.
12271227

1228-
To make tracing work as expected, it was found necessary to add a new
1229-
opcode, \cdata{RETURN_NONE}, to the VM. If you want to know why, read
1230-
the comments in the function \cfunction{maybe_call_line_trace} in
1231-
\file{Python/ceval.c}.
1232-
12331228
\end{itemize}
12341229

12351230

Include/opcode.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ extern "C" {
7171
#define INPLACE_OR 79
7272
#define BREAK_LOOP 80
7373

74-
#define RETURN_NONE 81 /* *only* for function epilogues
75-
-- see comments in
76-
ceval.c:maybe_call_line_trace for why */
7774
#define LOAD_LOCALS 82
7875
#define RETURN_VALUE 83
7976
#define IMPORT_STAR 84

Lib/dis.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ def jabs_op(name, op):
254254
def_op('INPLACE_OR', 79)
255255
def_op('BREAK_LOOP', 80)
256256

257-
def_op('RETURN_NONE', 81)
258257
def_op('LOAD_LOCALS', 82)
259258
def_op('RETURN_VALUE', 83)
260259
def_op('IMPORT_STAR', 84)

Lib/test/test_trace.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Testing the line trace facility.
2+
3+
from test import test_support
4+
import unittest
5+
import sys
6+
import difflib
7+
8+
# A very basic example. If this fails, we're in deep trouble.
9+
def basic():
10+
return 1
11+
12+
basic.events = [(0, 'call'),
13+
(1, 'line'),
14+
(1, 'return')]
15+
16+
# Armin Rigo's failing example:
17+
def arigo_example():
18+
x = 1
19+
del x
20+
while 0:
21+
pass
22+
x = 1
23+
24+
arigo_example.events = [(0, 'call'),
25+
(1, 'line'),
26+
(2, 'line'),
27+
(3, 'line'),
28+
(5, 'line'),
29+
(5, 'return')]
30+
31+
# check that lines consisting of just one instruction get traced:
32+
def one_instr_line():
33+
x = 1
34+
del x
35+
x = 1
36+
37+
one_instr_line.events = [(0, 'call'),
38+
(1, 'line'),
39+
(2, 'line'),
40+
(3, 'line'),
41+
(3, 'return')]
42+
43+
def no_pop_tops(): # 0
44+
x = 1 # 1
45+
for a in range(2): # 2
46+
if a: # 3
47+
x = 1 # 4
48+
else: # 5
49+
x = 1 # 6
50+
51+
no_pop_tops.events = [(0, 'call'),
52+
(1, 'line'),
53+
(2, 'line'),
54+
(3, 'line'),
55+
(6, 'line'),
56+
(2, 'line'),
57+
(3, 'line'),
58+
(4, 'line'),
59+
(2, 'line'),
60+
(6, 'return')]
61+
62+
def no_pop_blocks():
63+
while 0:
64+
bla
65+
x = 1
66+
67+
no_pop_blocks.events = [(0, 'call'),
68+
(1, 'line'),
69+
(3, 'line'),
70+
(3, 'return')]
71+
72+
class Tracer:
73+
def __init__(self):
74+
self.events = []
75+
def trace(self, frame, event, arg):
76+
self.events.append((frame.f_lineno, event))
77+
return self.trace
78+
79+
class TraceTestCase(unittest.TestCase):
80+
def run_test(self, func):
81+
tracer = Tracer()
82+
sys.settrace(tracer.trace)
83+
func()
84+
sys.settrace(None)
85+
fl = func.func_code.co_firstlineno
86+
events = [(l - fl, e) for (l, e) in tracer.events]
87+
if events != func.events:
88+
self.fail(
89+
"events did not match expectation:\n" +
90+
"\n".join(difflib.ndiff(map(str, func.events),
91+
map(str, events))))
92+
93+
def test_1_basic(self):
94+
self.run_test(basic)
95+
def test_2_arigo(self):
96+
self.run_test(arigo_example)
97+
def test_3_one_instr(self):
98+
self.run_test(one_instr_line)
99+
def test_4_no_pop_blocks(self):
100+
self.run_test(no_pop_blocks)
101+
def test_5_no_pop_tops(self):
102+
self.run_test(no_pop_tops)
103+
104+
105+
106+
def test_main():
107+
test_support.run_unittest(TraceTestCase)
108+
109+
if __name__ == "__main__":
110+
test_main()

Python/ceval.c

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,12 +1515,6 @@ eval_frame(PyFrameObject *f)
15151515
why = WHY_RETURN;
15161516
break;
15171517

1518-
case RETURN_NONE:
1519-
retval = Py_None;
1520-
Py_INCREF(retval);
1521-
why = WHY_RETURN;
1522-
break;
1523-
15241518
case YIELD_VALUE:
15251519
retval = POP();
15261520
f->f_stacktop = stack_pointer;
@@ -2880,9 +2874,8 @@ maybe_call_line_trace(int opcode, Py_tracefunc func, PyObject *obj,
28802874
This is all fairly simple. Digging the information out of
28812875
co_lnotab takes some work, but is conceptually clear.
28822876
2883-
Somewhat harder to explain is why we don't call the line
2884-
trace function when executing a POP_TOP or RETURN_NONE
2885-
opcodes. An example probably serves best.
2877+
Somewhat harder to explain is why we don't *always* call the
2878+
line trace function when the above test fails.
28862879
28872880
Consider this code:
28882881
@@ -2907,64 +2900,57 @@ maybe_call_line_trace(int opcode, Py_tracefunc func, PyObject *obj,
29072900
5 16 LOAD_CONST 2 (2)
29082901
19 PRINT_ITEM
29092902
20 PRINT_NEWLINE
2910-
>> 21 RETURN_NONE
2903+
>> 21 LOAD_CONST 0 (None)
2904+
24 RETURN_VALUE
29112905
29122906
If a is false, execution will jump to instruction at offset
29132907
15 and the co_lnotab will claim that execution has moved to
29142908
line 3. This is at best misleading. In this case we could
29152909
associate the POP_TOP with line 4, but that doesn't make
29162910
sense in all cases (I think).
29172911
2918-
On the other hand, if a is true, execution will jump from
2919-
instruction offset 12 to offset 21. Then the co_lnotab would
2920-
imply that execution has moved to line 5, which is again
2921-
misleading.
2922-
2923-
This is why it is important that RETURN_NONE is *only* used
2924-
for the "falling off the end of the function" form of
2925-
returning None -- using it for code like
2926-
2927-
1: def f():
2928-
2: return
2912+
What we do is only call the line trace function if the co_lnotab
2913+
indicates we have jumped to the *start* of a line, i.e. if the
2914+
current instruction offset matches the offset given for the
2915+
start of a line by the co_lnotab.
29292916
2930-
would, once again, lead to misleading tracing behaviour.
2931-
2932-
It is also worth mentioning that getting tracing behaviour
2933-
right is the *entire* motivation for adding the RETURN_NONE
2934-
opcode.
2917+
This also takes care of the situation where a is true.
2918+
Execution will jump from instruction offset 12 to offset 21.
2919+
Then the co_lnotab would imply that execution has moved to line
2920+
5, which is again misleading.
29352921
*/
29362922

2937-
if (opcode != POP_TOP && opcode != RETURN_NONE &&
2938-
(frame->f_lasti < *instr_lb || frame->f_lasti > *instr_ub)) {
2923+
if ((frame->f_lasti < *instr_lb || frame->f_lasti >= *instr_ub)) {
29392924
PyCodeObject* co = frame->f_code;
29402925
int size, addr;
29412926
unsigned char* p;
29422927

2943-
call_trace(func, obj, frame, PyTrace_LINE, Py_None);
2928+
size = PyString_GET_SIZE(co->co_lnotab) / 2;
2929+
p = (unsigned char*)PyString_AS_STRING(co->co_lnotab);
29442930

2945-
size = PyString_Size(co->co_lnotab) / 2;
2946-
p = (unsigned char*)PyString_AsString(co->co_lnotab);
2931+
addr = 0;
29472932

29482933
/* possible optimization: if f->f_lasti == instr_ub
29492934
(likely to be a common case) then we already know
29502935
instr_lb -- if we stored the matching value of p
29512936
somwhere we could skip the first while loop. */
29522937

2953-
addr = 0;
2954-
29552938
/* see comments in compile.c for the description of
29562939
co_lnotab. A point to remember: increments to p
29572940
should come in pairs -- although we don't care about
29582941
the line increments here, treating them as byte
29592942
increments gets confusing, to say the least. */
29602943

2961-
while (size >= 0) {
2944+
while (size > 0) {
29622945
if (addr + *p > frame->f_lasti)
29632946
break;
29642947
addr += *p++;
29652948
p++;
29662949
--size;
29672950
}
2951+
if (addr == frame->f_lasti)
2952+
call_trace(func, obj, frame,
2953+
PyTrace_LINE, Py_None);
29682954
*instr_lb = addr;
29692955
if (size > 0) {
29702956
while (--size >= 0) {

Python/compile.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4014,7 +4014,10 @@ compile_funcdef(struct compiling *c, node *n)
40144014
c->c_infunction = 1;
40154015
com_node(c, CHILD(n, 4));
40164016
c->c_infunction = 0;
4017-
com_addbyte(c, RETURN_NONE);
4017+
com_addoparg(c, LOAD_CONST, com_addconst(c, Py_None));
4018+
com_push(c, 1);
4019+
com_addbyte(c, RETURN_VALUE);
4020+
com_pop(c, 1);
40184021
}
40194022

40204023
static void
@@ -4081,13 +4084,19 @@ compile_node(struct compiling *c, node *n)
40814084
n = CHILD(n, 0);
40824085
if (TYPE(n) != NEWLINE)
40834086
com_node(c, n);
4084-
com_addbyte(c, RETURN_NONE);
4087+
com_addoparg(c, LOAD_CONST, com_addconst(c, Py_None));
4088+
com_push(c, 1);
4089+
com_addbyte(c, RETURN_VALUE);
4090+
com_pop(c, 1);
40854091
c->c_interactive--;
40864092
break;
40874093

40884094
case file_input: /* A whole file, or built-in function exec() */
40894095
com_file_input(c, n);
4090-
com_addbyte(c, RETURN_NONE);
4096+
com_addoparg(c, LOAD_CONST, com_addconst(c, Py_None));
4097+
com_push(c, 1);
4098+
com_addbyte(c, RETURN_VALUE);
4099+
com_pop(c, 1);
40914100
break;
40924101

40934102
case eval_input: /* Built-in function input() */

0 commit comments

Comments
 (0)