diff --git a/Lib/bdb.py b/Lib/bdb.py --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -73,12 +73,12 @@ # First call of dispatch since reset() self.botframe = frame.f_back # (CT) Note that this may also be None! return self.trace_dispatch + # Ignore call events in generator except when stepping. + if self.stopframe and frame.f_code.co_flags & CO_GENERATOR: + return self.trace_dispatch if not (self.stop_here(frame) or self.break_anywhere(frame)): # No need to trace this function return # None - # Ignore call events in generator except when stepping. - if self.stopframe and frame.f_code.co_flags & CO_GENERATOR: - return self.trace_dispatch self.user_call(frame, arg) if self.quitting: raise BdbQuit return self.trace_dispatch diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -905,6 +905,45 @@ (Pdb) continue """ +def test_pdb_suspended_generator(): + """The frame of a suspended generator should not have a local trace function. + + >>> def test_gen(): + ... for i in range(2): + ... yield sys._getframe() + + >>> def test_function(): + ... import pdb; pdb.Pdb(nosigint=True).set_trace() + ... for f in test_gen(): + ... assert f.f_trace is None + + >>> with PdbTestInput(['step', + ... 'next', + ... 'next', + ... 'next', + ... 'next', + ... 'continue']): + ... test_function() + > (3)test_function() + -> for f in test_gen(): + (Pdb) step + --Call-- + > (1)test_gen() + -> def test_gen(): + (Pdb) next + > (2)test_gen() + -> for i in range(2): + (Pdb) next + > (3)test_gen() + -> yield sys._getframe() + (Pdb) next + > (2)test_gen() + -> for i in range(2): + (Pdb) next + > (3)test_gen() + -> yield sys._getframe() + (Pdb) continue + """ class PdbTestCase(unittest.TestCase): diff --git a/Python/sysmodule.c b/Python/sysmodule.c --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -420,6 +420,7 @@ { PyObject *callback; PyObject *result; + PyThreadState *tstate = PyThreadState_GET(); if (what == PyTrace_CALL) callback = self; @@ -433,7 +434,15 @@ Py_CLEAR(frame->f_trace); return -1; } - if (result != Py_None) { + /* Do not hold a reference to the local trace function in a suspended + * generator's frame and clear the local trace function on a 'return' + * event as we are quitting the local scope, or when the system's trace + * function has been removed. */ + if (what == PyTrace_RETURN || tstate->c_traceobj == NULL) { + Py_CLEAR(frame->f_trace); + Py_DECREF(result); + } + else if (result != Py_None) { PyObject *temp = frame->f_trace; frame->f_trace = NULL; Py_XDECREF(temp);