Commit f05149a2 authored by Amaury Forgeot d'Arc's avatar Amaury Forgeot d'Arc

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.
parent 8161a65c
...@@ -204,12 +204,44 @@ tighterloop_example.events = [(0, 'call'), ...@@ -204,12 +204,44 @@ tighterloop_example.events = [(0, 'call'),
(6, 'line'), (6, 'line'),
(6, 'return')] (6, 'return')]
def generator_function():
try:
yield True
"continued"
finally:
"finally"
def generator_example():
# any() will leave the generator before its end
x = any(generator_function())
# the following lines were not traced
for x in range(10):
y = x
generator_example.events = ([(0, 'call'),
(2, 'line'),
(-6, 'call'),
(-5, 'line'),
(-4, 'line'),
(-4, 'return'),
(-4, 'call'),
(-4, 'exception'),
(-1, 'line'),
(-1, 'return')] +
[(5, 'line'), (6, 'line')] * 10 +
[(5, 'line'), (5, 'return')])
class Tracer: class Tracer:
def __init__(self): def __init__(self):
self.events = [] self.events = []
def trace(self, frame, event, arg): def trace(self, frame, event, arg):
self.events.append((frame.f_lineno, event)) self.events.append((frame.f_lineno, event))
return self.trace return self.trace
def traceWithGenexp(self, frame, event, arg):
(o for o in [1])
self.events.append((frame.f_lineno, event))
return self.trace
class TraceTestCase(unittest.TestCase): class TraceTestCase(unittest.TestCase):
def compare_events(self, line_offset, events, expected_events): def compare_events(self, line_offset, events, expected_events):
...@@ -217,8 +249,8 @@ class TraceTestCase(unittest.TestCase): ...@@ -217,8 +249,8 @@ class TraceTestCase(unittest.TestCase):
if events != expected_events: if events != expected_events:
self.fail( self.fail(
"events did not match expectation:\n" + "events did not match expectation:\n" +
"\n".join(difflib.ndiff(map(str, expected_events), "\n".join(difflib.ndiff([str(x) for x in expected_events],
map(str, events)))) [str(x) for x in events])))
def run_test(self, func): def run_test(self, func):
...@@ -262,6 +294,19 @@ class TraceTestCase(unittest.TestCase): ...@@ -262,6 +294,19 @@ class TraceTestCase(unittest.TestCase):
def test_12_tighterloop(self): def test_12_tighterloop(self):
self.run_test(tighterloop_example) self.run_test(tighterloop_example)
def test_13_genexp(self):
self.run_test(generator_example)
# issue1265: if the trace function contains a generator,
# and if the traced function contains another generator
# that is not completely exhausted, the trace stopped.
# Worse: the 'finally' clause was not invoked.
tracer = Tracer()
sys.settrace(tracer.traceWithGenexp)
generator_example()
sys.settrace(None)
self.compare_events(generator_example.__code__.co_firstlineno,
tracer.events, generator_example.events)
class RaisingTraceFuncTestCase(unittest.TestCase): class RaisingTraceFuncTestCase(unittest.TestCase):
def trace(self, frame, event, arg): def trace(self, frame, event, arg):
"""A trace function that raises an exception in response to a """A trace function that raises an exception in response to a
......
...@@ -107,7 +107,7 @@ static int prtrace(PyObject *, char *); ...@@ -107,7 +107,7 @@ static int prtrace(PyObject *, char *);
#endif #endif
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *, static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
int, PyObject *); int, PyObject *);
static void call_trace_protected(Py_tracefunc, PyObject *, static int call_trace_protected(Py_tracefunc, PyObject *,
PyFrameObject *, int, PyObject *); PyFrameObject *, int, PyObject *);
static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *); static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *);
static int maybe_call_line_trace(Py_tracefunc, PyObject *, static int maybe_call_line_trace(Py_tracefunc, PyObject *,
...@@ -717,8 +717,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -717,8 +717,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
an argument which depends on the situation. an argument which depends on the situation.
The global trace function is also called The global trace function is also called
whenever an exception is detected. */ whenever an exception is detected. */
if (call_trace(tstate->c_tracefunc, tstate->c_traceobj, if (call_trace_protected(tstate->c_tracefunc,
f, PyTrace_CALL, Py_None)) { tstate->c_traceobj,
f, PyTrace_CALL, Py_None)) {
/* Trace function raised an error */ /* Trace function raised an error */
goto exit_eval_frame; goto exit_eval_frame;
} }
...@@ -726,9 +727,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -726,9 +727,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
if (tstate->c_profilefunc != NULL) { if (tstate->c_profilefunc != NULL) {
/* Similar for c_profilefunc, except it needn't /* Similar for c_profilefunc, except it needn't
return itself and isn't called for "line" events */ return itself and isn't called for "line" events */
if (call_trace(tstate->c_profilefunc, if (call_trace_protected(tstate->c_profilefunc,
tstate->c_profileobj, tstate->c_profileobj,
f, PyTrace_CALL, Py_None)) { f, PyTrace_CALL, Py_None)) {
/* Profile function raised an error */ /* Profile function raised an error */
goto exit_eval_frame; goto exit_eval_frame;
} }
...@@ -3127,7 +3128,7 @@ call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f) ...@@ -3127,7 +3128,7 @@ call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f)
} }
} }
static void static int
call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame, call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
int what, PyObject *arg) int what, PyObject *arg)
{ {
...@@ -3136,11 +3137,15 @@ call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame, ...@@ -3136,11 +3137,15 @@ call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
PyErr_Fetch(&type, &value, &traceback); PyErr_Fetch(&type, &value, &traceback);
err = call_trace(func, obj, frame, what, arg); err = call_trace(func, obj, frame, what, arg);
if (err == 0) if (err == 0)
{
PyErr_Restore(type, value, traceback); PyErr_Restore(type, value, traceback);
return 0;
}
else { else {
Py_XDECREF(type); Py_XDECREF(type);
Py_XDECREF(value); Py_XDECREF(value);
Py_XDECREF(traceback); Py_XDECREF(traceback);
return -1;
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment