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