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