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