[Python-checkins] r58964 - in python/branches/release25-maint: Lib/test/test_trace.py Misc/NEWS Python/ceval.c

amaury.forgeotdarc python-checkins at python.org
Tue Nov 13 23:43:06 CET 2007


Author: amaury.forgeotdarc
Date: Tue Nov 13 23:43:05 2007
New Revision: 58964

Modified:
   python/branches/release25-maint/Lib/test/test_trace.py
   python/branches/release25-maint/Misc/NEWS
   python/branches/release25-maint/Python/ceval.c
Log:
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.



Modified: python/branches/release25-maint/Lib/test/test_trace.py
==============================================================================
--- python/branches/release25-maint/Lib/test/test_trace.py	(original)
+++ python/branches/release25-maint/Lib/test/test_trace.py	Tue Nov 13 23:43:05 2007
@@ -204,12 +204,44 @@
                             (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 @@
         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 @@
     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

Modified: python/branches/release25-maint/Misc/NEWS
==============================================================================
--- python/branches/release25-maint/Misc/NEWS	(original)
+++ python/branches/release25-maint/Misc/NEWS	Tue Nov 13 23:43:05 2007
@@ -12,6 +12,10 @@
 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

Modified: python/branches/release25-maint/Python/ceval.c
==============================================================================
--- python/branches/release25-maint/Python/ceval.c	(original)
+++ python/branches/release25-maint/Python/ceval.c	Tue Nov 13 23:43:05 2007
@@ -105,7 +105,7 @@
 #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,8 +710,9 @@
 			   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,
-				       f, PyTrace_CALL, Py_None)) {
+			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,9 +720,9 @@
 		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,
-				       tstate->c_profileobj,
-				       f, PyTrace_CALL, Py_None)) {
+			if (call_trace_protected(tstate->c_profilefunc,
+						 tstate->c_profileobj,
+						 f, PyTrace_CALL, Py_None)) {
 				/* Profile function raised an error */
 				goto exit_eval_frame;
 			}
@@ -3192,7 +3193,7 @@
 	}
 }
 
-static void
+static int
 call_trace_protected(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
 		     int what, PyObject *arg)
 {
@@ -3201,11 +3202,15 @@
 	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;
 	}
 }
 


More information about the Python-checkins mailing list