Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,39 @@ def test_pdb_issue_20766():
pdb 2: <built-in function default_int_handler>
"""

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()
> <doctest test.test_pdb.test_pdb_issue_33446[0]>(8)test_function()
-> pass
(Pdb) step
--Return--
> <doctest test.test_pdb.test_pdb_issue_33446[0]>(8)test_function()->None
-> pass
(Pdb) step
--Call--
> <doctest test.test_pdb.test_pdb_issue_33446[0]>(3)__del__()
-> def __del__(self):
(Pdb) step
> <doctest test.test_pdb.test_pdb_issue_33446[0]>(4)__del__()
-> within_destructor = True
(Pdb) continue
"""


class PdbTestCase(unittest.TestCase):
def tearDown(self):
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Destructors of local variables are now traced.
17 changes: 15 additions & 2 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down