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