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