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