1"""
2Pdb debugger class.
3
4
5This is an extension to PDB which adds a number of new features.
6Note that there is also the `IPython.terminal.debugger` class which provides UI
7improvements.
8
9We also strongly recommend to use this via the `ipdb` package, which provides
10extra configuration options.
11
12Among other things, this subclass of PDB:
13 - supports many IPython magics like pdef/psource
14 - hide frames in tracebacks based on `__tracebackhide__`
15 - allows to skip frames based on `__debuggerskip__`
16
17
18Global Configuration
19--------------------
20
21The IPython debugger will by read the global ``~/.pdbrc`` file.
22That is to say you can list all commands supported by ipdb in your `~/.pdbrc`
23configuration file, to globally configure pdb.
24
25Example::
26
27 # ~/.pdbrc
28 skip_predicates debuggerskip false
29 skip_hidden false
30 context 25
31
32Features
33--------
34
35The IPython debugger can hide and skip frames when printing or moving through
36the stack. This can have a performance impact, so can be configures.
37
38The skipping and hiding frames are configurable via the `skip_predicates`
39command.
40
41By default, frames from readonly files will be hidden, frames containing
42``__tracebackhide__ = True`` will be hidden.
43
44Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent
45frames value of ``__debuggerskip__`` is ``True`` will also be skipped.
46
47 >>> def helpers_helper():
48 ... pass
49 ...
50 ... def helper_1():
51 ... print("don't step in me")
52 ... helpers_helpers() # will be stepped over unless breakpoint set.
53 ...
54 ...
55 ... def helper_2():
56 ... print("in me neither")
57 ...
58
59One can define a decorator that wraps a function between the two helpers:
60
61 >>> def pdb_skipped_decorator(function):
62 ...
63 ...
64 ... def wrapped_fn(*args, **kwargs):
65 ... __debuggerskip__ = True
66 ... helper_1()
67 ... __debuggerskip__ = False
68 ... result = function(*args, **kwargs)
69 ... __debuggerskip__ = True
70 ... helper_2()
71 ... # setting __debuggerskip__ to False again is not necessary
72 ... return result
73 ...
74 ... return wrapped_fn
75
76When decorating a function, ipdb will directly step into ``bar()`` by
77default:
78
79 >>> @foo_decorator
80 ... def bar(x, y):
81 ... return x * y
82
83
84You can toggle the behavior with
85
86 ipdb> skip_predicates debuggerskip false
87
88or configure it in your ``.pdbrc``
89
90
91
92License
93-------
94
95Modified from the standard pdb.Pdb class to avoid including readline, so that
96the command line completion of other programs which include this isn't
97damaged.
98
99In the future, this class will be expanded with improvements over the standard
100pdb.
101
102The original code in this file is mainly lifted out of cmd.py in Python 2.2,
103with minor changes. Licensing should therefore be under the standard Python
104terms. For details on the PSF (Python Software Foundation) standard license,
105see:
106
107https://docs.python.org/2/license.html
108
109
110All the changes since then are under the same license as IPython.
111
112"""
113
114# *****************************************************************************
115#
116# This file is licensed under the PSF license.
117#
118# Copyright (C) 2001 Python Software Foundation, www.python.org
119# Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
120#
121#
122# *****************************************************************************
123
124from __future__ import annotations
125
126import inspect
127import linecache
128import os
129import re
130import sys
131import warnings
132from contextlib import contextmanager
133
134from IPython.core.getipython import get_ipython
135from IPython.core.debugger_backport import PdbClosureBackport
136from IPython.utils import PyColorize
137from IPython.utils.PyColorize import TokenStream
138
139from typing import TYPE_CHECKING
140from types import FrameType
141
142# We have to check this directly from sys.argv, config struct not yet available
143from pdb import Pdb as _OldPdb
144from pygments.token import Token
145
146
147if sys.version_info < (3, 13):
148
149 class OldPdb(PdbClosureBackport, _OldPdb):
150 pass
151
152else:
153 OldPdb = _OldPdb
154
155if TYPE_CHECKING:
156 # otherwise circular import
157 from IPython.core.interactiveshell import InteractiveShell
158
159# skip module docstests
160__skip_doctest__ = True
161
162prompt = "ipdb> "
163
164
165# Allow the set_trace code to operate outside of an ipython instance, even if
166# it does so with some limitations. The rest of this support is implemented in
167# the Tracer constructor.
168
169DEBUGGERSKIP = "__debuggerskip__"
170
171
172# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
173# on lower python versions, we backported the feature.
174CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
175
176
177def BdbQuit_excepthook(et, ev, tb, excepthook=None):
178 """Exception hook which handles `BdbQuit` exceptions.
179
180 All other exceptions are processed using the `excepthook`
181 parameter.
182 """
183 raise ValueError(
184 "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.",
185 )
186
187
188RGX_EXTRA_INDENT = re.compile(r"(?<=\n)\s+")
189
190
191def strip_indentation(multiline_string):
192 return RGX_EXTRA_INDENT.sub("", multiline_string)
193
194
195def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
196 """Make new_fn have old_fn's doc string. This is particularly useful
197 for the ``do_...`` commands that hook into the help system.
198 Adapted from from a comp.lang.python posting
199 by Duncan Booth."""
200
201 def wrapper(*args, **kw):
202 return new_fn(*args, **kw)
203
204 if old_fn.__doc__:
205 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
206 return wrapper
207
208
209class Pdb(OldPdb):
210 """Modified Pdb class, does not load readline.
211
212 for a standalone version that uses prompt_toolkit, see
213 `IPython.terminal.debugger.TerminalPdb` and
214 `IPython.terminal.debugger.set_trace()`
215
216
217 This debugger can hide and skip frames that are tagged according to some predicates.
218 See the `skip_predicates` commands.
219
220 """
221
222 shell: InteractiveShell
223 _theme_name: str
224 _context: int
225
226 _chained_exceptions: tuple[Exception, ...]
227 _chained_exception_index: int
228
229 if CHAIN_EXCEPTIONS:
230 MAX_CHAINED_EXCEPTION_DEPTH = 999
231
232 default_predicates = {
233 "tbhide": True,
234 "readonly": False,
235 "ipython_internal": True,
236 "debuggerskip": True,
237 }
238
239 def __init__(
240 self,
241 completekey=None,
242 stdin=None,
243 stdout=None,
244 context: int | None | str = 5,
245 *,
246 mode: str | None = None,
247 **kwargs,
248 ):
249 """Create a new IPython debugger.
250
251 Parameters
252 ----------
253 completekey : default None
254 Passed to pdb.Pdb.
255 stdin : default None
256 Passed to pdb.Pdb.
257 stdout : default None
258 Passed to pdb.Pdb.
259 context : int
260 Number of lines of source code context to show when
261 displaying stacktrace information.
262 mode : str, optional
263 How the debugger was invoked, one of ``'inline'`` (used by the
264 ``breakpoint()`` builtin), ``'cli'`` (used by the command line
265 invocation) or ``None`` (backwards compatible behaviour). This
266 argument was added to stdlib's ``pdb.Pdb`` in Python 3.14; it is
267 accepted on every supported Python version here but only forwarded
268 to the underlying ``pdb.Pdb`` when it is actually supported.
269 **kwargs
270 Passed to pdb.Pdb.
271
272 Notes
273 -----
274 The possibilities are python version dependent, see the python
275 docs for more info.
276 """
277 # ipdb issue, see https://github.com/ipython/ipython/issues/14811
278 if context is None:
279 context = 5
280 if isinstance(context, str):
281 context = int(context)
282 self.context = context
283
284 # The `mode` argument was added to `pdb.Pdb` in Python 3.14. We accept
285 # it on every supported Python version so that callers written against
286 # 3.14+ keep working, but only forward it to the underlying `pdb.Pdb`
287 # when it understands it.
288 if sys.version_info >= (3, 14):
289 kwargs["mode"] = mode
290 else:
291 self.mode = mode
292
293 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
294 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
295 # Python 3.15+ should define this, so no need to initialize
296 # this avoids some getattr(self, 'curframe')
297 if sys.version_info < (3, 15):
298 self.curframe = None
299
300 # IPython changes...
301 shell = get_ipython()
302
303 if shell is None:
304 save_main = sys.modules["__main__"]
305 # No IPython instance running, we must create one
306 from IPython.terminal.interactiveshell import TerminalInteractiveShell
307
308 shell = TerminalInteractiveShell.instance()
309 # needed by any code which calls __import__("__main__") after
310 # the debugger was entered. See also #9941.
311 sys.modules["__main__"] = save_main
312 self.shell = shell
313
314 self.aliases = {}
315
316 theme_name = self.shell.colors
317 assert isinstance(theme_name, str)
318 assert theme_name.lower() == theme_name
319
320 # Add a python parser so we can syntax highlight source while
321 # debugging.
322 self.parser = PyColorize.Parser(theme_name=theme_name)
323 self.set_theme_name(theme_name)
324
325 # Set the prompt - the default prompt is '(Pdb)'
326 self.prompt = prompt
327 self.skip_hidden = True
328 self.report_skipped = True
329
330 # list of predicates we use to skip frames
331 self._predicates = self.default_predicates
332
333 # Per-instance caches for the DEBUGGERSKIP frame checks (see
334 # `_cachable_skip`). Keyed by frame objects, so they must not outlive
335 # the debugger stop they were computed for: they are cleared on every
336 # `interaction` (and size-bounded) to avoid pinning frames — and
337 # transitively their locals and whole back-chains — in memory.
338 self._skip_cache: dict[FrameType, bool] = {}
339 self._parent_skip_cache: dict[FrameType, bool | None] = {}
340
341 if CHAIN_EXCEPTIONS:
342 self._chained_exceptions = tuple()
343 self._chained_exception_index = 0
344
345 @property
346 def context(self) -> int:
347 return self._context
348
349 @context.setter
350 def context(self, value: int | str) -> None:
351 # ipdb issue see https://github.com/ipython/ipython/issues/14811
352 if not isinstance(value, int):
353 value = int(value)
354 assert isinstance(value, int)
355 assert value >= 0
356 self._context = value
357
358 def set_theme_name(self, name):
359 assert name.lower() == name
360 assert isinstance(name, str)
361 self._theme_name = name
362 self.parser.theme_name = name
363
364 @property
365 def theme(self):
366 return PyColorize.theme_table[self._theme_name]
367
368 #
369 def set_colors(self, scheme):
370 """Shorthand access to the color table scheme selector method."""
371 warnings.warn(
372 "set_colors is deprecated since IPython 9.0, use set_theme_name instead",
373 DeprecationWarning,
374 stacklevel=2,
375 )
376 assert scheme == scheme.lower()
377 self._theme_name = scheme.lower()
378 self.parser.theme_name = scheme.lower()
379
380 def set_trace(self, frame=None, **kwargs):
381 if frame is None:
382 frame = sys._getframe().f_back
383 self.initial_frame = frame
384 return super().set_trace(frame, **kwargs)
385
386 def get_stack(self, *args, **kwargs):
387 stack, pos = super().get_stack(*args, **kwargs)
388 if len(stack) >= 0 and self._is_internal_frame(stack[0][0]):
389 stack.pop(0)
390 pos -= 1
391 return stack, pos
392
393 def _is_internal_frame(self, frame):
394 """Determine if this frame should be skipped as internal"""
395 filename = frame.f_code.co_filename
396
397 # Skip bdb.py runcall and internal operations
398 if filename.endswith("bdb.py"):
399 func_name = frame.f_code.co_name
400 # Skip internal bdb operations but allow breakpoint hits
401 if func_name in ("runcall", "run", "runeval"):
402 return True
403
404 return False
405
406 def _hidden_predicate(self, frame):
407 """
408 Given a frame return whether it it should be hidden or not by IPython.
409 """
410
411 if self._predicates["readonly"]:
412 fname = frame.f_code.co_filename
413 # we need to check for file existence and interactively define
414 # function would otherwise appear as RO.
415 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
416 return True
417
418 if self._predicates["tbhide"]:
419 if frame in (self.curframe, getattr(self, "initial_frame", None)):
420 return False
421 frame_locals = self._get_frame_locals(frame)
422 if "__tracebackhide__" not in frame_locals:
423 return False
424 return frame_locals["__tracebackhide__"]
425 return False
426
427 def hidden_frames(self, stack):
428 """
429 Given an index in the stack return whether it should be skipped.
430
431 This is used in up/down and where to skip frames.
432 """
433 # The f_locals dictionary is updated from the actual frame
434 # locals whenever the .f_locals accessor is called, so we
435 # avoid calling it here to preserve self._curframe_locals.
436 # Furthermore, there is no good reason to hide the current frame.
437 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
438 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
439 if ip_start and self._predicates["ipython_internal"]:
440 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
441 return ip_hide
442
443 if CHAIN_EXCEPTIONS:
444
445 def _get_tb_and_exceptions(self, tb_or_exc):
446 """
447 Given a tracecack or an exception, return a tuple of chained exceptions
448 and current traceback to inspect.
449 This will deal with selecting the right ``__cause__`` or ``__context__``
450 as well as handling cycles, and return a flattened list of exceptions we
451 can jump to with do_exceptions.
452 """
453 _exceptions = []
454 if isinstance(tb_or_exc, BaseException):
455 traceback, current = tb_or_exc.__traceback__, tb_or_exc
456
457 while current is not None:
458 if current in _exceptions:
459 break
460 _exceptions.append(current)
461 if current.__cause__ is not None:
462 current = current.__cause__
463 elif (
464 current.__context__ is not None
465 and not current.__suppress_context__
466 ):
467 current = current.__context__
468
469 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
470 self.message(
471 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
472 " chained exceptions found, not all exceptions"
473 " will be browsable with `exceptions`."
474 )
475 break
476 else:
477 traceback = tb_or_exc
478 return tuple(reversed(_exceptions)), traceback
479
480 @contextmanager
481 def _hold_exceptions(self, exceptions):
482 """
483 Context manager to ensure proper cleaning of exceptions references
484 When given a chained exception instead of a traceback,
485 pdb may hold references to many objects which may leak memory.
486 We use this context manager to make sure everything is properly cleaned
487 """
488 try:
489 self._chained_exceptions = exceptions
490 self._chained_exception_index = len(exceptions) - 1
491 yield
492 finally:
493 # we can't put those in forget as otherwise they would
494 # be cleared on exception change
495 self._chained_exceptions = tuple()
496 self._chained_exception_index = 0
497
498 def do_exceptions(self, arg):
499 """exceptions [number]
500 List or change current exception in an exception chain.
501 Without arguments, list all the current exception in the exception
502 chain. Exceptions will be numbered, with the current exception indicated
503 with an arrow.
504 If given an integer as argument, switch to the exception at that index.
505 ``exception`` can be used as an alias for this command.
506 """
507 if not self._chained_exceptions:
508 self.message(
509 "Did not find chained exceptions. To move between"
510 " exceptions, pdb/post_mortem must be given an exception"
511 " object rather than a traceback."
512 )
513 return
514 if not arg:
515 for ix, exc in enumerate(self._chained_exceptions):
516 prompt = ">" if ix == self._chained_exception_index else " "
517 rep = repr(exc)
518 if len(rep) > 80:
519 rep = rep[:77] + "..."
520 indicator = (
521 " -"
522 if self._chained_exceptions[ix].__traceback__ is None
523 else f"{ix:>3}"
524 )
525 self.message(f"{prompt} {indicator} {rep}")
526 else:
527 try:
528 number = int(arg)
529 except ValueError:
530 self.error("Argument must be an integer")
531 return
532 if 0 <= number < len(self._chained_exceptions):
533 if self._chained_exceptions[number].__traceback__ is None:
534 self.error(
535 "This exception does not have a traceback, cannot jump to it"
536 )
537 return
538
539 self._chained_exception_index = number
540 self.setup(None, self._chained_exceptions[number].__traceback__)
541 self.print_stack_entry(self.stack[self.curindex])
542 else:
543 self.error("No exception with that number")
544
545 def do_exception(self, arg):
546 """exception [number]
547 Alias for the ``exceptions`` command.
548 """
549 return self.do_exceptions(arg)
550
551 def _cmdloop(self):
552 # Override to bypass Python 3.15's _maybe_use_pyrepl_as_stdin(), which
553 # sets use_rawinput=False and conflicts with IPython's own input handling.
554 while True:
555 try:
556 self.allow_kbdint = True
557 self.cmdloop()
558 self.allow_kbdint = False
559 break
560 except KeyboardInterrupt:
561 self.message("--KeyboardInterrupt--")
562
563 def interaction(self, frame, tb_or_exc):
564 # The DEBUGGERSKIP caches are only valid for a single stop: frame
565 # locals may change while the program runs, and keeping frame keys
566 # alive across stops would leak memory (see `_cachable_skip`).
567 self._skip_cache.clear()
568 self._parent_skip_cache.clear()
569 try:
570 if CHAIN_EXCEPTIONS:
571 # this context manager is part of interaction in 3.13
572 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
573 if isinstance(tb_or_exc, BaseException):
574 assert tb is not None, "main exception must have a traceback"
575 with self._hold_exceptions(_chained_exceptions):
576 OldPdb.interaction(self, frame, tb)
577 else:
578 OldPdb.interaction(self, frame, tb_or_exc)
579
580 except KeyboardInterrupt:
581 self.stdout.write("\n" + self.shell.get_exception_only())
582
583 def precmd(self, line):
584 """Perform useful escapes on the command before it is executed."""
585
586 if line.endswith("??"):
587 line = "pinfo2 " + line[:-2]
588 elif line.endswith("?"):
589 line = "pinfo " + line[:-1]
590
591 line = super().precmd(line)
592
593 return line
594
595 def new_do_quit(self, arg):
596 return OldPdb.do_quit(self, arg)
597
598 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
599
600 def print_stack_trace(self, context: int | None = None):
601 if context is None:
602 context = self.context
603 try:
604 skipped = 0
605 to_print = ""
606 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
607 if hidden and self.skip_hidden:
608 skipped += 1
609 continue
610 if skipped:
611 to_print += self.theme.format(
612 [
613 (
614 Token.ExcName,
615 f" [... skipping {skipped} hidden frame(s)]",
616 ),
617 (Token, "\n"),
618 ]
619 )
620
621 skipped = 0
622 to_print += self.format_stack_entry(frame_lineno)
623 if skipped:
624 to_print += self.theme.format(
625 [
626 (
627 Token.ExcName,
628 f" [... skipping {skipped} hidden frame(s)]",
629 ),
630 (Token, "\n"),
631 ]
632 )
633 print(to_print, file=self.stdout)
634 except KeyboardInterrupt:
635 pass
636
637 def print_stack_entry(
638 self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> "
639 ) -> None:
640 """
641 Overwrite print_stack_entry from superclass (PDB)
642 """
643 print(self.format_stack_entry(frame_lineno, ""), file=self.stdout)
644
645 frame, lineno = frame_lineno
646 filename = frame.f_code.co_filename
647 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
648
649 def _pdbcmd_print_frame_status(self, arg):
650 """Use print_stack_entry to print frames in Python 3.14+."""
651 if sys.version_info[:2] >= (3, 14):
652 # This is the only line changed from the base class.
653 self.print_stack_entry(self.stack[self.curindex])
654
655 # Same as in 3.14
656 self._validate_file_mtime()
657 self._show_display()
658 else:
659 # 3.13 and 3.12 don't need any changes.
660 super()._pdbcmd_print_frame_status(arg) # type: ignore[misc]
661
662 @property
663 def _curframe_locals(self):
664 """Locals of the frame the debugger currently points at.
665
666 On Python 3.13+, ``frame.f_locals`` is a write-through proxy (PEP 667)
667 which pdb no longer caches, and ``Pdb.curframe_locals`` is deprecated
668 in 3.14 in favor of ``curframe.f_locals``. On older versions
669 ``curframe_locals`` is a snapshot which must be reused, see
670 `_get_frame_locals`.
671 """
672 assert self.curframe is not None
673 if sys.version_info >= (3, 13):
674 return self.curframe.f_locals
675 return self.curframe_locals
676
677 def _get_frame_locals(self, frame):
678 """ "
679 Accessing f_local of current frame reset the namespace, so we want to avoid
680 that or the following can happen
681
682 ipdb> foo
683 "old"
684 ipdb> foo = "new"
685 ipdb> foo
686 "new"
687 ipdb> where
688 ipdb> foo
689 "old"
690
691 So if frame is self.current_frame we instead return self._curframe_locals
692
693 """
694 if frame is self.curframe:
695 return self._curframe_locals
696 else:
697 return frame.f_locals
698
699 def format_stack_entry(
700 self,
701 frame_lineno: tuple[FrameType, int],
702 lprefix: str = ": ",
703 ) -> str:
704 """
705 overwrite from super class so must -> str
706 """
707 context = self.context
708 try:
709 context = int(context)
710 if context <= 0:
711 print("Context must be a positive integer", file=self.stdout)
712 except (TypeError, ValueError):
713 print("Context must be a positive integer", file=self.stdout)
714
715 import reprlib
716
717 ret_tok = []
718
719 frame, lineno = frame_lineno
720
721 return_value = ""
722 loc_frame = self._get_frame_locals(frame)
723 if "__return__" in loc_frame:
724 rv = loc_frame["__return__"]
725 # return_value += '->'
726 return_value += reprlib.repr(rv) + "\n"
727 ret_tok.extend([(Token, return_value)])
728
729 # s = filename + '(' + `lineno` + ')'
730 filename = self.canonic(frame.f_code.co_filename)
731 link_tok = (Token.FilenameEm, filename)
732
733 if frame.f_code.co_name:
734 func = frame.f_code.co_name
735 else:
736 func = "<lambda>"
737
738 call_toks = []
739 if func != "?":
740 if "__args__" in loc_frame:
741 args = reprlib.repr(loc_frame["__args__"])
742 else:
743 args = "()"
744 call_toks = [(Token.VName, func), (Token.ValEm, args)]
745
746 # The level info should be generated in the same format pdb uses, to
747 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
748 if frame is self.curframe:
749 ret_tok.append((Token.CurrentFrame, self.theme.make_arrow(2)))
750 else:
751 ret_tok.append((Token, " "))
752
753 ret_tok.extend(
754 [
755 link_tok,
756 (Token, "("),
757 (Token.Lineno, str(lineno)),
758 (Token, ")"),
759 *call_toks,
760 (Token, "\n"),
761 ]
762 )
763
764 start = lineno - 1 - context // 2
765 lines = linecache.getlines(filename)
766 start = min(start, len(lines) - context)
767 start = max(start, 0)
768 lines = lines[start : start + context]
769
770 for i, line in enumerate(lines):
771 show_arrow = start + 1 + i == lineno
772
773 bp, num, colored_line = self.__line_content(
774 filename,
775 start + 1 + i,
776 line,
777 arrow=show_arrow,
778 )
779 if frame is self.curframe or show_arrow:
780 rlt = [
781 bp,
782 (Token.LinenoEm, num),
783 (Token, " "),
784 # TODO: investigate Toke.Line here, likely LineEm,
785 # Token is problematic here as line is already colored, a
786 # and this changes the full style of the colored line.
787 # ideally, __line_content returns the token and we modify the style.
788 (Token, colored_line),
789 ]
790 else:
791 rlt = [
792 bp,
793 (Token.Lineno, num),
794 (Token, " "),
795 # TODO: investigate Toke.Line here, likely Line
796 # Token is problematic here as line is already colored, a
797 # and this changes the full style of the colored line.
798 # ideally, __line_content returns the token and we modify the style.
799 (Token.Line, colored_line),
800 ]
801 ret_tok.extend(rlt)
802
803 return self.theme.format(ret_tok)
804
805 def __line_content(
806 self, filename: str, lineno: int, line: str, arrow: bool = False
807 ):
808 bp_mark = ""
809 BreakpointToken = Token.Breakpoint
810
811 new_line, err = self.parser.format2(line, "str")
812 if not err:
813 assert new_line is not None
814 line = new_line
815
816 bp = None
817 if lineno in self.get_file_breaks(filename):
818 bps = self.get_breaks(filename, lineno)
819 bp = bps[-1]
820
821 if bp:
822 bp_mark = str(bp.number)
823 BreakpointToken = Token.Breakpoint.Enabled
824 if not bp.enabled:
825 BreakpointToken = Token.Breakpoint.Disabled
826 numbers_width = 7
827 if arrow:
828 # This is the line with the error
829 pad = numbers_width - len(str(lineno)) - len(bp_mark)
830 num = "{}{}".format(self.theme.make_arrow(pad), str(lineno))
831 else:
832 num = "%*s" % (numbers_width - len(bp_mark), str(lineno))
833 bp_str = (BreakpointToken, bp_mark)
834 return (bp_str, num, line)
835
836 def print_list_lines(self, filename: str, first: int, last: int) -> None:
837 """The printing (as opposed to the parsing part of a 'list'
838 command."""
839 toks: TokenStream = []
840 try:
841 if filename == "<string>" and hasattr(self, "_exec_filename"):
842 filename = self._exec_filename
843
844 for lineno in range(first, last + 1):
845 line = linecache.getline(filename, lineno)
846 if not line:
847 break
848
849 assert self.curframe is not None
850
851 if lineno == self.curframe.f_lineno:
852 bp, num, colored_line = self.__line_content(
853 filename, lineno, line, arrow=True
854 )
855 toks.extend(
856 [
857 bp,
858 (Token.LinenoEm, num),
859 (Token, " "),
860 # TODO: investigate Token.Line here
861 (Token, colored_line),
862 ]
863 )
864 else:
865 bp, num, colored_line = self.__line_content(
866 filename, lineno, line, arrow=False
867 )
868 toks.extend(
869 [
870 bp,
871 (Token.Lineno, num),
872 (Token, " "),
873 (Token, colored_line),
874 ]
875 )
876
877 self.lineno = lineno
878
879 print(self.theme.format(toks), file=self.stdout)
880
881 except KeyboardInterrupt:
882 pass
883
884 def do_skip_predicates(self, args):
885 """
886 Turn on/off individual predicates as to whether a frame should be hidden/skip.
887
888 The global option to skip (or not) hidden frames is set with skip_hidden
889
890 To change the value of a predicate
891
892 skip_predicates key [true|false]
893
894 Call without arguments to see the current values.
895
896 To permanently change the value of an option add the corresponding
897 command to your ``~/.pdbrc`` file. If you are programmatically using the
898 Pdb instance you can also change the ``default_predicates`` class
899 attribute.
900 """
901 if not args.strip():
902 print("current predicates:")
903 for p, v in self._predicates.items():
904 print(" ", p, ":", v)
905 return
906 type_value = args.strip().split(" ")
907 if len(type_value) != 2:
908 print(
909 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
910 )
911 return
912
913 type_, value = type_value
914 if type_ not in self._predicates:
915 print(f"{type_!r} not in {set(self._predicates.keys())}")
916 return
917 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
918 print(
919 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
920 )
921 return
922
923 self._predicates[type_] = value.lower() in ("true", "yes", "1")
924 if not any(self._predicates.values()):
925 print(
926 "Warning, all predicates set to False, skip_hidden may not have any effects."
927 )
928
929 def do_skip_hidden(self, arg):
930 """
931 Change whether or not we should skip frames with the
932 __tracebackhide__ attribute.
933 """
934 if not arg.strip():
935 print(
936 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
937 )
938 elif arg.strip().lower() in ("true", "yes"):
939 self.skip_hidden = True
940 elif arg.strip().lower() in ("false", "no"):
941 self.skip_hidden = False
942 if not any(self._predicates.values()):
943 print(
944 "Warning, all predicates set to False, skip_hidden may not have any effects."
945 )
946
947 def do_list(self, arg):
948 """Print lines of code from the current stack frame"""
949 self.lastcmd = "list"
950 last = None
951 if arg and arg != ".":
952 try:
953 x = eval(arg, {}, {})
954 if type(x) == type(()):
955 first, last = x # type: ignore[misc]
956 first = int(first)
957 last = int(last) # type: ignore[call-overload]
958 if last < first:
959 # Assume it's a count
960 last = first + last
961 else:
962 first = max(1, int(x) - 5)
963 except ValueError:
964 print("*** Error in argument:", repr(arg), file=self.stdout)
965 return
966 elif self.lineno is None or arg == ".":
967 assert self.curframe is not None
968 first = max(1, self.curframe.f_lineno - 5)
969 else:
970 first = self.lineno + 1
971 if last is None:
972 last = first + 10
973 assert self.curframe is not None
974 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
975
976 lineno = first
977 filename = self.curframe.f_code.co_filename
978 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
979
980 do_l = do_list
981
982 def getsourcelines(self, obj):
983 lines, lineno = inspect.findsource(obj)
984 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
985 # must be a module frame: do not try to cut a block out of it
986 return lines, 1
987 elif inspect.ismodule(obj):
988 return lines, 1
989 return inspect.getblock(lines[lineno:]), lineno + 1
990
991 def do_longlist(self, arg):
992 """Print lines of code from the current stack frame.
993
994 Shows more lines than 'list' does.
995 """
996 self.lastcmd = "longlist"
997 try:
998 lines, lineno = self.getsourcelines(self.curframe)
999 except OSError as err:
1000 self.error(str(err))
1001 return
1002 last = lineno + len(lines)
1003 assert self.curframe is not None
1004 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
1005
1006 do_ll = do_longlist
1007
1008 def do_debug(self, arg):
1009 """debug code
1010 Enter a recursive debugger that steps through the code
1011 argument (which is an arbitrary expression or statement to be
1012 executed in the current environment).
1013 """
1014 trace_function = sys.gettrace()
1015 sys.settrace(None)
1016 assert self.curframe is not None
1017 globals = self.curframe.f_globals
1018 locals = self._curframe_locals
1019 p = self.__class__(
1020 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout
1021 )
1022 p.use_rawinput = self.use_rawinput
1023 p.prompt = "(%s) " % self.prompt.strip()
1024 self.message("ENTERING RECURSIVE DEBUGGER")
1025 sys.call_tracing(p.run, (arg, globals, locals))
1026 self.message("LEAVING RECURSIVE DEBUGGER")
1027 sys.settrace(trace_function)
1028 self.lastcmd = p.lastcmd
1029
1030 def do_pdef(self, arg):
1031 """Print the call signature for any callable object.
1032
1033 The debugger interface to %pdef"""
1034 assert self.curframe is not None
1035 namespaces = [
1036 ("Locals", self._curframe_locals),
1037 ("Globals", self.curframe.f_globals),
1038 ]
1039 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
1040
1041 def do_pdoc(self, arg):
1042 """Print the docstring for an object.
1043
1044 The debugger interface to %pdoc."""
1045 assert self.curframe is not None
1046 namespaces = [
1047 ("Locals", self._curframe_locals),
1048 ("Globals", self.curframe.f_globals),
1049 ]
1050 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
1051
1052 def do_pfile(self, arg):
1053 """Print (or run through pager) the file where an object is defined.
1054
1055 The debugger interface to %pfile.
1056 """
1057 assert self.curframe is not None
1058 namespaces = [
1059 ("Locals", self._curframe_locals),
1060 ("Globals", self.curframe.f_globals),
1061 ]
1062 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
1063
1064 def do_pinfo(self, arg):
1065 """Provide detailed information about an object.
1066
1067 The debugger interface to %pinfo, i.e., obj?."""
1068 assert self.curframe is not None
1069 namespaces = [
1070 ("Locals", self._curframe_locals),
1071 ("Globals", self.curframe.f_globals),
1072 ]
1073 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
1074
1075 def do_pinfo2(self, arg):
1076 """Provide extra detailed information about an object.
1077
1078 The debugger interface to %pinfo2, i.e., obj??."""
1079 assert self.curframe is not None
1080 namespaces = [
1081 ("Locals", self._curframe_locals),
1082 ("Globals", self.curframe.f_globals),
1083 ]
1084 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
1085
1086 def do_psource(self, arg):
1087 """Print (or run through pager) the source code for an object."""
1088 assert self.curframe is not None
1089 namespaces = [
1090 ("Locals", self._curframe_locals),
1091 ("Globals", self.curframe.f_globals),
1092 ]
1093 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
1094
1095 def do_where(self, arg: str):
1096 """w(here)
1097 Print a stack trace, with the most recent frame at the bottom.
1098 An arrow indicates the "current frame", which determines the
1099 context of most commands. 'bt' is an alias for this command.
1100
1101 Take a number as argument as an (optional) number of context line to
1102 print"""
1103 if arg:
1104 try:
1105 context = int(arg)
1106 except ValueError as err:
1107 self.error(str(err))
1108 return
1109 self.print_stack_trace(context)
1110 else:
1111 self.print_stack_trace()
1112
1113 do_w = do_where
1114
1115 def break_anywhere(self, frame):
1116 """
1117 _stop_in_decorator_internals is overly restrictive, as we may still want
1118 to trace function calls, so we need to also update break_anywhere so
1119 that is we don't `stop_here`, because of debugger skip, we may still
1120 stop at any point inside the function
1121
1122 """
1123
1124 sup = super().break_anywhere(frame)
1125 if sup:
1126 return sup
1127 if self._predicates["debuggerskip"]:
1128 if DEBUGGERSKIP in frame.f_code.co_varnames:
1129 return True
1130 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
1131 return True
1132 return False
1133
1134 def _is_in_decorator_internal_and_should_skip(self, frame):
1135 """
1136 Utility to tell us whether we are in a decorator internal and should stop.
1137
1138 """
1139 # if we are disabled don't skip
1140 if not self._predicates["debuggerskip"]:
1141 return False
1142
1143 return self._cachable_skip(frame)
1144
1145 def _cached_one_parent_frame_debuggerskip(self, frame):
1146 """
1147 Cache looking up for DEBUGGERSKIP on parent frame.
1148
1149 This should speedup walking through deep frame when one of the highest
1150 one does have a debugger skip.
1151
1152 This is likely to introduce fake positive though.
1153 """
1154 try:
1155 return self._parent_skip_cache[frame]
1156 except KeyError:
1157 pass
1158 result = None
1159 current = frame
1160 while getattr(current, "f_back", None):
1161 current = current.f_back
1162 if self._get_frame_locals(current).get(DEBUGGERSKIP):
1163 result = True
1164 break
1165 self._parent_skip_cache[frame] = result
1166 return result
1167
1168 def _cachable_skip(self, frame):
1169 # These caches used to be class-level ``lru_cache``\ s, which kept
1170 # every debugger instance and up to 1024 frames (plus their locals and
1171 # back-chains) alive for the lifetime of the process. They are now
1172 # per-instance, size-bounded here, and cleared on each `interaction`.
1173 if len(self._skip_cache) >= 1024:
1174 self._skip_cache.clear()
1175 self._parent_skip_cache.clear()
1176 try:
1177 return self._skip_cache[frame]
1178 except KeyError:
1179 pass
1180
1181 # if frame is tagged, skip by default.
1182 if DEBUGGERSKIP in frame.f_code.co_varnames:
1183 result = True
1184 else:
1185 # if one of the parent frame value set to True skip as well.
1186 result = bool(self._cached_one_parent_frame_debuggerskip(frame))
1187
1188 self._skip_cache[frame] = result
1189 return result
1190
1191 def stop_here(self, frame):
1192 if self._is_in_decorator_internal_and_should_skip(frame) is True:
1193 return False
1194
1195 hidden = False
1196 if self.skip_hidden:
1197 hidden = self._hidden_predicate(frame)
1198 if hidden:
1199 if self.report_skipped:
1200 print(
1201 self.theme.format(
1202 [
1203 (
1204 Token.ExcName,
1205 " [... skipped 1 hidden frame(s)]",
1206 ),
1207 (Token, "\n"),
1208 ]
1209 )
1210 )
1211 if self.skip and self.is_skipped_module(frame.f_globals.get("__name__", "")):
1212 print(
1213 self.theme.format(
1214 [
1215 (
1216 Token.ExcName,
1217 " [... skipped 1 ignored module(s)]",
1218 ),
1219 (Token, "\n"),
1220 ]
1221 )
1222 )
1223
1224 return False
1225
1226 return super().stop_here(frame)
1227
1228 def do_up(self, arg):
1229 """u(p) [count]
1230 Move the current frame count (default one) levels up in the
1231 stack trace (to an older frame).
1232
1233 Will skip hidden frames and ignored modules.
1234 """
1235 # modified version of upstream that skips
1236 # frames with __tracebackhide__ and ignored modules
1237 if self.curindex == 0:
1238 self.error("Oldest frame")
1239 return
1240 try:
1241 count = int(arg or 1)
1242 except ValueError:
1243 self.error("Invalid frame count (%s)" % arg)
1244 return
1245
1246 hidden_skipped = 0
1247 module_skipped = 0
1248
1249 if count < 0:
1250 _newframe = 0
1251 else:
1252 counter = 0
1253 hidden_frames = self.hidden_frames(self.stack)
1254
1255 for i in range(self.curindex - 1, -1, -1):
1256 should_skip_hidden = hidden_frames[i] and self.skip_hidden
1257 should_skip_module = self.skip and self.is_skipped_module(
1258 self.stack[i][0].f_globals.get("__name__", "")
1259 )
1260
1261 if should_skip_hidden or should_skip_module:
1262 if should_skip_hidden:
1263 hidden_skipped += 1
1264 if should_skip_module:
1265 module_skipped += 1
1266 continue
1267 counter += 1
1268 if counter >= count:
1269 break
1270 else:
1271 # if no break occurred.
1272 self.error(
1273 "all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
1274 )
1275 return
1276
1277 _newframe = i
1278 self._select_frame(_newframe)
1279
1280 total_skipped = hidden_skipped + module_skipped
1281 if total_skipped:
1282 print(
1283 self.theme.format(
1284 [
1285 (
1286 Token.ExcName,
1287 f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]",
1288 ),
1289 (Token, "\n"),
1290 ]
1291 )
1292 )
1293
1294 def do_down(self, arg):
1295 """d(own) [count]
1296 Move the current frame count (default one) levels down in the
1297 stack trace (to a newer frame).
1298
1299 Will skip hidden frames and ignored modules.
1300 """
1301 if self.curindex + 1 == len(self.stack):
1302 self.error("Newest frame")
1303 return
1304 try:
1305 count = int(arg or 1)
1306 except ValueError:
1307 self.error("Invalid frame count (%s)" % arg)
1308 return
1309 if count < 0:
1310 _newframe = len(self.stack) - 1
1311 else:
1312 counter = 0
1313 hidden_skipped = 0
1314 module_skipped = 0
1315 hidden_frames = self.hidden_frames(self.stack)
1316
1317 for i in range(self.curindex + 1, len(self.stack)):
1318 should_skip_hidden = hidden_frames[i] and self.skip_hidden
1319 should_skip_module = self.skip and self.is_skipped_module(
1320 self.stack[i][0].f_globals.get("__name__", "")
1321 )
1322
1323 if should_skip_hidden or should_skip_module:
1324 if should_skip_hidden:
1325 hidden_skipped += 1
1326 if should_skip_module:
1327 module_skipped += 1
1328 continue
1329 counter += 1
1330 if counter >= count:
1331 break
1332 else:
1333 self.error(
1334 "all frames below skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
1335 )
1336 return
1337
1338 total_skipped = hidden_skipped + module_skipped
1339 if total_skipped:
1340 print(
1341 self.theme.format(
1342 [
1343 (
1344 Token.ExcName,
1345 f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]",
1346 ),
1347 (Token, "\n"),
1348 ]
1349 )
1350 )
1351 _newframe = i
1352
1353 self._select_frame(_newframe)
1354
1355 do_d = do_down
1356 do_u = do_up
1357
1358 def _show_ignored_modules(self):
1359 """Display currently ignored modules."""
1360 if self.skip:
1361 print(f"Currently ignored modules: {sorted(self.skip)}")
1362 else:
1363 print("No modules are currently ignored.")
1364
1365 def do_ignore_module(self, arg):
1366 """ignore_module <module_name>
1367
1368 Add a module to the list of modules to skip when navigating frames.
1369 When a module is ignored, the debugger will automatically skip over
1370 frames from that module.
1371
1372 Supports wildcard patterns using fnmatch syntax:
1373
1374 Usage:
1375 ignore_module threading # Skip threading module frames
1376 ignore_module asyncio.\\* # Skip all asyncio submodules
1377 ignore_module \\*.tests # Skip all test modules
1378 ignore_module # List currently ignored modules
1379 """
1380
1381 if self.skip is None:
1382 self.skip = set()
1383
1384 module_name = arg.strip()
1385
1386 if not module_name:
1387 self._show_ignored_modules()
1388 return
1389
1390 self.skip.add(module_name)
1391
1392 def do_unignore_module(self, arg):
1393 """unignore_module <module_name>
1394
1395 Remove a module from the list of modules to skip when navigating frames.
1396 This will allow the debugger to step into frames from the specified module.
1397
1398 Usage:
1399 unignore_module threading # Stop ignoring threading module frames
1400 unignore_module asyncio.\\* # Remove asyncio.* pattern
1401 unignore_module # List currently ignored modules
1402 """
1403
1404 if self.skip is None:
1405 self.skip = set()
1406
1407 module_name = arg.strip()
1408
1409 if not module_name:
1410 self._show_ignored_modules()
1411 return
1412
1413 try:
1414 self.skip.remove(module_name)
1415 except KeyError:
1416 print(f"Module {module_name} is not currently ignored")
1417 self._show_ignored_modules()
1418
1419 def do_context(self, context: str):
1420 """context number_of_lines
1421 Set the number of lines of source code to show when displaying
1422 stacktrace information.
1423 """
1424 try:
1425 new_context = int(context)
1426 if new_context <= 0:
1427 raise ValueError()
1428 self.context = new_context
1429 except ValueError:
1430 self.error(
1431 f"The 'context' command requires a positive integer argument (current value {self.context})."
1432 )
1433
1434
1435class InterruptiblePdb(Pdb):
1436 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1437
1438 def cmdloop(self, intro=None):
1439 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1440 try:
1441 return OldPdb.cmdloop(self, intro=intro)
1442 except KeyboardInterrupt:
1443 self.stop_here = lambda frame: False # type: ignore[method-assign]
1444 self.do_quit("")
1445 sys.settrace(None)
1446 self.quitting = False
1447 raise
1448
1449 def _cmdloop(self):
1450 while True:
1451 try:
1452 # keyboard interrupts allow for an easy way to cancel
1453 # the current command, so allow them during interactive input
1454 self.allow_kbdint = True
1455 self.cmdloop()
1456 self.allow_kbdint = False
1457 break
1458 except KeyboardInterrupt:
1459 self.message("--KeyboardInterrupt--")
1460 raise
1461
1462
1463def set_trace(frame=None, header=None):
1464 """
1465 Start debugging from `frame`.
1466
1467 If frame is not specified, debugging starts from caller's frame.
1468 """
1469 pdb = Pdb()
1470 if header is not None:
1471 pdb.message(header)
1472 pdb.set_trace(frame or sys._getframe().f_back)