Merge from py3k branch:

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.

Backport candidate, even if the case is less frequent in 2.5.
This commit is contained in:
Amaury Forgeot d'Arc 2007-11-13 21:54:28 +00:00
parent 0288cb0ba8
commit 0d75f09177
3 changed files with 63 additions and 9 deletions

View file

@ -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):
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):
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

View file

@ -12,6 +12,10 @@ What's New in Python 2.6 alpha 1?
Core and builtins 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.
- sets and frozensets now have an isdisjoint() method. - sets and frozensets now have an isdisjoint() method.
- optimize the performance of builtin.sum(). - optimize the performance of builtin.sum().

View file

@ -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 *,
@ -714,8 +714,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;
} }
@ -723,9 +724,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;
} }
@ -3214,7 +3215,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)
{ {
@ -3223,11 +3224,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;
} }
} }