Skip to content
Open
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
21 changes: 21 additions & 0 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2647,6 +2647,16 @@ def set_trace(*, header=None, commands=None):
just before debugging begins. *commands* is an optional list of
pdb commands to run when the debugger starts.
"""
# gh-138641: Check if we're already in a pdb session.
frame = sys._getframe()
while frame:
if (frame.f_code.co_name == 'interaction'
and frame.f_code.co_filename.endswith('pdb.py')):
print("Nested breakpoint calls are not supported. "
"Already running in the debugger.", file=sys.stderr)
return
frame = frame.f_back

if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
Expand All @@ -2662,6 +2672,17 @@ async def set_trace_async(*, header=None, commands=None):
if they enter the debugger with this function. Otherwise it's the same
as set_trace().
"""
# gh-138641: Check if we're already in a pdb session.
frame = sys._getframe()
while frame:
if (frame.f_code.co_name == 'interaction' and
frame.f_code.co_filename.endswith('pdb.py')):
# We're already in a pdb session, just print a message and return
print("Nested breakpoint calls are not supported. "
"Already running in the debugger.", file=sys.stderr)
return
frame = frame.f_back

if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
Expand Down
16 changes: 14 additions & 2 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
import zipfile

from asyncio.events import _set_event_loop_policy
from contextlib import ExitStack, redirect_stdout
from contextlib import ExitStack, redirect_stdout, redirect_stderr
from io import StringIO
from test import support
from test.support import has_socket_support, os_helper
from test.support.import_helper import import_module
from test.support.pty_helper import run_pty, FakeInput
from test.support.script_helper import kill_python
from unittest.mock import patch
from unittest.mock import Mock, patch

SKIP_CORO_TESTS = False

Expand Down Expand Up @@ -4539,6 +4539,18 @@ def bar():
]))
self.assertIn('break in bar', stdout)

def test_nested_breakpoint_calls(self):
# gh-138641 pdb.set_trace() called when already in the debugger
mock_frame = Mock()
mock_frame.f_code.co_name = 'interaction'
mock_frame.f_code.co_filename = '/path/to/pdb.py'
mock_frame.f_back = None
captured_stderr = io.StringIO()
with redirect_stderr(captured_stderr):
with patch('sys._getframe', return_value=mock_frame):
pdb.set_trace()
stderr_content = captured_stderr.getvalue()
self.assertIn("Nested breakpoint calls are not supported", stderr_content)

class ChecklineTests(unittest.TestCase):
def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Prevent error when calling :func:`breakpoint` inside a :mod:`pdb` debugging session.
Nested breakpoint calls now display a helpful error message instead of causing error.
Loading