debugger.py 38 KB

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