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

Skip to content

Commit f05149a

Browse files
committed
Correction for issue1265 (pdb bug with "with" statement).
When an unfinished generator-iterator is garbage collected, PyEval_EvalFrameEx is called with a GeneratorExit exception set. This leads to funny results if the sys.settrace function itself makes use of generators. A visible effect is that the settrace function is reset to None. Another is that the eventual "finally" block of the generator is not called. It is necessary to save/restore the exception around the call to the trace function. This happens a lot with py3k: isinstance() of an ABCMeta instance runs def __instancecheck__(cls, instance): """Override for isinstance(instance, cls).""" return any(cls.__subclasscheck__(c) for c in {instance.__class__, type(instance)}) which lets an opened generator expression each time it returns True. Seems a backport candidate, even if the case is less frequent in 2.5.
1 parent 8161a65 commit f05149a

2 files changed

Lines changed: 59 additions & 9 deletions

File tree

Lib/test/test_trace.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,21 +204,53 @@ def tighterloop_example():
204204
(6, 'line'),
205205
(6, 'return')]
206206

207+
def generator_function():
208+
try:
209+
yield True
210+
"continued"
211+
finally:
212+
"finally"
213+
def generator_example():
214+
# any() will leave the generator before its end
215+
x = any(generator_function())
216+
217+
# the following lines were not traced
218+
for x in range(10):
219+
y = x
220+
221+
generator_example.events = ([(0, 'call'),
222+
(2, 'line'),
223+
(-6, 'call'),
224+
(-5, 'line'),
225+
(-4, 'line'),
226+
(-4, 'return'),
227+
(-4, 'call'),
228+
(-4, 'exception'),
229+
(-1, 'line'),
230+
(-1, 'return')] +
231+
[(5, 'line'), (6, 'line')] * 10 +
232+
[(5, 'line'), (5, 'return')])
233+
234+
207235
class Tracer:
208236
def __init__(self):
209237
self.events = []
210238
def trace(self, frame, event, arg):
211239
self.events.append((frame.f_lineno, event))
212240
return self.trace
241+
def traceWithGenexp(self, frame, event, arg):
242+
(o for o in [1])
243+
self.events.append((frame.f_lineno, event))
244+
return self.trace
213245

214246
class TraceTestCase(unittest.TestCase):
215247
def compare_events(self, line_offset, events, expected_events):
216248
events = [(l - line_offset, e) for (l, e) in events]
217249
if events != expected_events:
218250
self.fail(
219251
"events did not match expectation:\n" +
220-
"\n".join(difflib.ndiff(map(str, expected_events),
221-
map(str, events))))
252+
"\n".join(difflib.ndiff([str(x) for x in expected_events],
253+
[str(x) for x in events])))
222254

223255

224256
def run_test(self, func):
@@ -262,6 +294,19 @@ def test_11_tightloop(self):
262294
def test_12_tighterloop(self):
263295
self.run_test(tighterloop_example)
264296

297+
def test_13_genexp(self):
298+
self.run_test(generator_example)
299+
# issue1265: if the trace function contains a generator,
300+
# and if the traced function contains another generator
301+
# that is not completely exhausted, the trace stopped.
302+
# Worse: the 'finally' clause was not invoked.
303+
tracer = Tracer()
304+
sys.settrace(tracer.traceWithGenexp)
305+
generator_example()
306+
sys.settrace(None)
307+
self.compare_events(generator_example.__code__.co_firstlineno,
308+
tracer.events, generator_example.events)
309+
265310
class RaisingTraceFuncTestCase(unittest.TestCase):
266311
def trace(self, frame, event, arg):
267312
"""A trace function that raises an exception in response to a

Python/ceval.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ static int prtrace(PyObject *, char *);
107107
#endif
108108
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
109109
int, PyObject *);
110-
static void call_trace_protected(Py_tracefunc, PyObject *,
110+
static int call_trace_protected(Py_tracefunc, PyObject *,
111111
PyFrameObject *, int, PyObject *);
112112
static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *);
113113
static int maybe_call_line_trace(Py_tracefunc, PyObject *,
@@ -717,18 +717,19 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
717717
an argument which depends on the situation.
718718
The global trace function is also called
719719
whenever an exception is detected. */
720-
if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
721-
f, PyTrace_CALL, Py_None)) {
720+
if (call_trace_protected(tstate->c_tracefunc,
721+
tstate->c_traceobj,
722+
f, PyTrace_CALL, Py_None)) {
722723
/* Trace function raised an error */
723724
goto exit_eval_frame;
724725
}
725726
}
726727
if (tstate->c_profilefunc != NULL) {
727728
/* Similar for c_profilefunc, except it needn't
728729
return itself and isn't called for "line" events */
729-
if (call_trace(tstate->c_profilefunc,
730-
tstate->c_profileobj,
731-
f, PyTrace_CALL, Py_None)) {
730+
if (call_trace_protected(tstate->c_profilefunc,
731+
tstate->c_profileobj,
732+
f, PyTrace_CALL, Py_None)) {
732733
/* Profile function raised an error */
733734
goto exit_eval_frame;
734735
}
@@ -3127,7 +3128,7 @@ call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f)
31273128
}
31283129
}
31293130

3130-
static void
3131+
static int
31313132
call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
31323133
int what, PyObject *arg)
31333134
{
@@ -3136,11 +3137,15 @@ call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
31363137
PyErr_Fetch(&type, &value, &traceback);
31373138
err = call_trace(func, obj, frame, what, arg);
31383139
if (err == 0)
3140+
{
31393141
PyErr_Restore(type, value, traceback);
3142+
return 0;
3143+
}
31403144
else {
31413145
Py_XDECREF(type);
31423146
Py_XDECREF(value);
31433147
Py_XDECREF(traceback);
3148+
return -1;
31443149
}
31453150
}
31463151

0 commit comments

Comments
 (0)