diff --git a/Lib/pdb.py b/Lib/pdb.py index 60bdb7675c8131..fb23ae3e6a275d 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -201,6 +201,7 @@ def forget(self): self.stack = [] self.curindex = 0 self.curframe = None + self.curframe_locals = None self.tb_lineno.clear() def setup(self, f, tb): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 9aa38e08dd6e6c..392f32c362005e 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -1140,6 +1140,39 @@ def test_pdb_issue_20766(): pdb 2: """ +def test_pdb_issue_33446(): + """Test that the destructor of a local variable is traced. + + >>> def test_function(): + ... class C: + ... def __del__(self): + ... within_destructor = True + ... + ... a = C() + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... pass + + >>> with PdbTestInput(['step', + ... 'step', + ... 'step', + ... 'continue']): + ... test_function() + > (8)test_function() + -> pass + (Pdb) step + --Return-- + > (8)test_function()->None + -> pass + (Pdb) step + --Call-- + > (3)__del__() + -> def __del__(self): + (Pdb) step + > (4)__del__() + -> within_destructor = True + (Pdb) continue + """ + class PdbTestCase(unittest.TestCase): def tearDown(self): diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index f5125a450511d2..67abef7ba94996 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -447,6 +447,32 @@ def func(): [(0, 'call'), (1, 'line')]) + def test_18_trace_locals_destructors(self): + # Issue bpo-33446: destructors of local variables are now traced. + def func(): + class C: + def __del__(self): + lineno = 3 + + a = C() + a = 1 + lineno = 7 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (1, 'call'), + (1, 'line'), + (2, 'line'), + (2, 'return'), + (5, 'line'), + (6, 'line'), + (2, 'call'), + (3, 'line'), + (3, 'return'), + (7, 'line'), + (7, 'return')]) + class SkipLineEventsTraceTestCase(TraceTestCase): """Repeat the trace tests, but with per-line events skipped""" diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-05-08-20-37-16.bpo-33446.GQUaqN.rst b/Misc/NEWS.d/next/Core and Builtins/2018-05-08-20-37-16.bpo-33446.GQUaqN.rst new file mode 100644 index 00000000000000..fd7d0486444bce --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-05-08-20-37-16.bpo-33446.GQUaqN.rst @@ -0,0 +1 @@ +Destructors of local variables are now traced. diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 3fab81aa50536f..80b1467b725b63 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -464,8 +464,21 @@ call_trampoline(PyObject* callback, { PyObject *result; PyObject *stack[3]; - - if (PyFrame_FastToLocalsWithError(frame) < 0) { + int res; + PyThreadState *tstate = PyThreadState_GET(); + int tracing = tstate->tracing; + + /* frame->f_locals may still hold a reference to an object that was set in + * a previous invocation of PyFrame_FastToLocalsWithError() while the + * Python code being traced may not anymore hold any reference to this + * object. Re-enable temporarily tracing to allow the corresponding + * destructor to be traced/profiled (issue bpo-33446). */ + tstate->tracing = 0; + tstate->use_tracing = 1; + res = PyFrame_FastToLocalsWithError(frame); + tstate->use_tracing = 0; + tstate->tracing = tracing; + if (res < 0) { return NULL; }