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