debugger.py 46 KB

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