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