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