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