Backport 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.

And the problem can be reproduced in 2.5 with pure python code.
This commit is contained in:
Amaury Forgeot d'Arc 2007-11-13 22:43:05 +00:00
parent f5ccd459d7
commit c572dc3752
3 changed files with 63 additions and 9 deletions

View File

@ -204,12 +204,44 @@ tighterloop_example.events = [(0, 'call'),
(6, 'line'),
(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:
def __init__(self):
self.events = []
def trace(self, frame, event, arg):
self.events.append((frame.f_lineno, event))
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):
def compare_events(self, line_offset, events, expected_events):
@ -217,8 +249,8 @@ class TraceTestCase(unittest.TestCase):
if events != expected_events:
self.fail(
"events did not match expectation:\n" +
"\n".join(difflib.ndiff(map(str, expected_events),
map(str, events))))
"\n".join(difflib.ndiff([str(x) for x in expected_events],
[str(x) for x in events])))
def run_test(self, func):
@ -262,6 +294,19 @@ class TraceTestCase(unittest.TestCase):
def test_12_tighterloop(self):
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.func_code.co_firstlineno,
tracer.events, generator_example.events)
class RaisingTraceFuncTestCase(unittest.TestCase):
def trace(self, frame, event, arg):
"""A trace function that raises an exception in response to a

View File

@ -12,6 +12,10 @@ What's New in Python 2.5.2c1?
Core and builtins
-----------------
- Issue #1265: Fix a problem with sys.settrace, if the tracing function uses a
generator expression when at the same time the executed code is closing a
paused generator.
- Issue 1704621: Fix segfaults in list_repeat() and list_inplace_repeat().
- Issue #1147: Generators were not raising a DeprecationWarning when a string

View File

@ -105,7 +105,7 @@ static int prtrace(PyObject *, char *);
#endif
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
int, PyObject *);
static void call_trace_protected(Py_tracefunc, PyObject *,
static int call_trace_protected(Py_tracefunc, PyObject *,
PyFrameObject *, int, PyObject *);
static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *);
static int maybe_call_line_trace(Py_tracefunc, PyObject *,
@ -710,7 +710,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
an argument which depends on the situation.
The global trace function is also called
whenever an exception is detected. */
if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
if (call_trace_protected(tstate->c_tracefunc,
tstate->c_traceobj,
f, PyTrace_CALL, Py_None)) {
/* Trace function raised an error */
goto exit_eval_frame;
@ -719,7 +720,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
if (tstate->c_profilefunc != NULL) {
/* Similar for c_profilefunc, except it needn't
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,
f, PyTrace_CALL, Py_None)) {
/* Profile function raised an error */
@ -3192,7 +3193,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,
int what, PyObject *arg)
{
@ -3201,11 +3202,15 @@ call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
PyErr_Fetch(&type, &value, &traceback);
err = call_trace(func, obj, frame, what, arg);
if (err == 0)
{
PyErr_Restore(type, value, traceback);
return 0;
}
else {
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
return -1;
}
}