GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-106670: Allow Pdb to move between chained exceptions by Carreau · Pull Request #106676 · python/cpython

gh-106670: Allow Pdb to move between chained exceptions #106676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
iritkatriel merged 12 commits into python:main from Carreau:gh-106670
Aug 28, 2023

Uh oh!

There was an error while loading. Please reload this page.

Merged

gh-106670: Allow Pdb to move between chained exceptions #106676

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
d39fbce
Allow Pdb to move between chained exception.
Carreau Jul 12, 2023
920af94
Move the exception logic to its own function.
Carreau Aug 16, 2023
d24ed3c
Make variable instance variable.
Carreau Aug 23, 2023
63a7347
Merge branch 'main' into gh-106670
AlexWaygood Aug 23, 2023
66a3944
Move back MAX_CHAINED_EXCEPTION_DEPTH to class variable
Carreau Aug 24, 2023
b4f79ad
Apply suggestions from code review
Carreau Aug 26, 2023
e3b9e4a
Adress some of the reviews
Carreau Aug 26, 2023
948551c
add versionadded
Carreau Aug 26, 2023
9ba7bb8
Fix doc
Carreau Aug 26, 2023
3514edc
add whatsnew
Carreau Aug 26, 2023
4e57c32
Update Doc/whatsnew/3.13.rst
Carreau Aug 28, 2023
9e7354b
move paragraph
Carreau Aug 28, 2023
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

53 changes: 51 additions & 2 deletions 53 Doc/library/pdb.rst
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ slightly different way:

.. function:: pm()

Enter post-mortem debugging of the traceback found in
:data:`sys.last_traceback`.
Enter post-mortem debugging of the exception found in
:data:`sys.last_exc`.


The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
Expand Down Expand Up @@ -639,6 +639,55 @@ can be overridden by the local file.

Print the return value for the last return of the current function.

.. pdbcommand:: exceptions [excnumber]
Comment thread
iritkatriel marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.


List or jump between chained exceptions.

When using ``pdb.pm()`` or ``Pdb.post_mortem(...)`` with a chained exception
instead of a traceback, it allows the user to move between the
chained exceptions using ``exceptions`` command to list exceptions, and
``exception <number>`` to switch to that exception.
Comment thread
iritkatriel marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.



Example::

def out():
try:
middle()
except Exception as e:
raise ValueError("reraise middle() error") from e

def middle():
try:
return inner(0)
except Exception as e:
raise ValueError("Middle fail")

def inner(x):
1 / x

out()

calling ``pdb.pm()`` will allow to move between exceptions::

> example.py(5)out()
-> raise ValueError("reraise middle() error") from e

(Pdb) exceptions
0 ZeroDivisionError('division by zero')
1 ValueError('Middle fail')
> 2 ValueError('reraise middle() error')

(Pdb) exceptions 0
> example.py(16)inner()
-> 1 / x

(Pdb) up
> example.py(10)middle()
-> return inner(0)

.. versionadded:: 3.13

Comment thread
iritkatriel marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

.. rubric:: Footnotes

.. [1] Whether a frame is considered to originate in a certain module
Expand Down
7 changes: 7 additions & 0 deletions 7 Doc/whatsnew/3.13.rst
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ pathlib
:meth:`~pathlib.Path.is_dir`.
(Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.)

pdb
---

* Add ability to move between chained exceptions during post mortem debugging in :func:`~pdb.pm` using
the new ``exceptions [exc_number]`` command for Pdb. (Contributed by Matthias
Bussonnier in :gh:`106676`.)

sqlite3
-------

Expand Down
142 changes: 124 additions & 18 deletions 142 Lib/pdb.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import traceback
import linecache

from contextlib import contextmanager
from typing import Union


Expand Down Expand Up @@ -205,10 +206,15 @@ def namespace(self):
# line_prefix = ': ' # Use this to get the old situation back
line_prefix = '\n-> ' # Probably a better default

class Pdb(bdb.Bdb, cmd.Cmd):


class Pdb(bdb.Bdb, cmd.Cmd):
_previous_sigint_handler = None

# Limit the maximum depth of chained exceptions, we should be handling cycles,
# but in case there are recursions, we stop at 999.
MAX_CHAINED_EXCEPTION_DEPTH = 999

def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
nosigint=False, readrc=True):
bdb.Bdb.__init__(self, skip=skip)
Expand Down Expand Up @@ -256,6 +262,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
self.commands_bnum = None # The breakpoint number for which we are
# defining a list

self._chained_exceptions = tuple()
self._chained_exception_index = 0

def sigint_handler(self, signum, frame):
if self.allow_kbdint:
raise KeyboardInterrupt
Expand Down Expand Up @@ -414,7 +423,64 @@ def preloop(self):
self.message('display %s: %r [old: %r]' %
(expr, newvalue, oldvalue))

def interaction(self, frame, traceback):
def _get_tb_and_exceptions(self, tb_or_exc):
"""
Given a tracecack or an exception, return a tuple of chained exceptions
and current traceback to inspect.

This will deal with selecting the right ``__cause__`` or ``__context__``
as well as handling cycles, and return a flattened list of exceptions we
can jump to with do_exceptions.

"""
_exceptions = []
if isinstance(tb_or_exc, BaseException):
traceback, current = tb_or_exc.__traceback__, tb_or_exc

while current is not None:
if current in _exceptions:
break
_exceptions.append(current)
if current.__cause__ is not None:
current = current.__cause__
elif (
current.__context__ is not None and not current.__suppress_context__
):
current = current.__context__

if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
self.message(
f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
" chained exceptions found, not all exceptions"
"will be browsable with `exceptions`."
)
break
else:
traceback = tb_or_exc
return tuple(reversed(_exceptions)), traceback

@contextmanager
def _hold_exceptions(self, exceptions):
"""
Context manager to ensure proper cleaning of exceptions references

When given a chained exception instead of a traceback,
pdb may hold references to many objects which may leak memory.

We use this context manager to make sure everything is properly cleaned

"""
try:
self._chained_exceptions = exceptions
self._chained_exception_index = len(exceptions) - 1
yield
finally:
# we can't put those in forget as otherwise they would
# be cleared on exception change
self._chained_exceptions = tuple()
self._chained_exception_index = 0

def interaction(self, frame, tb_or_exc):
# Restore the previous signal handler at the Pdb prompt.
if Pdb._previous_sigint_handler:
try:
Expand All @@ -423,14 +489,17 @@ def interaction(self, frame, traceback):
pass
else:
Pdb._previous_sigint_handler = None
if self.setup(frame, traceback):
# no interaction desired at this time (happens if .pdbrc contains
# a command like "continue")

_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
with self._hold_exceptions(_chained_exceptions):
if self.setup(frame, tb):
# no interaction desired at this time (happens if .pdbrc contains
# a command like "continue")
self.forget()
return
self.print_stack_entry(self.stack[self.curindex])
self._cmdloop()
self.forget()
return
self.print_stack_entry(self.stack[self.curindex])
self._cmdloop()
self.forget()

def displayhook(self, obj):
"""Custom displayhook for the exec in default(), which prevents
Expand Down Expand Up @@ -1073,6 +1142,44 @@ def _select_frame(self, number):
self.print_stack_entry(self.stack[self.curindex])
self.lineno = None

def do_exceptions(self, arg):
"""exceptions [number]

List or change current exception in an exception chain.

Without arguments, list all the current exception in the exception
chain. Exceptions will be numbered, with the current exception indicated
with an arrow.

If given an integer as argument, switch to the exception at that index.
"""
if not self._chained_exceptions:
self.message(
"Did not find chained exceptions. To move between"
" exceptions, pdb/post_mortem must be given an exception"
" object rather than a traceback."
)
return
if not arg:
Comment thread
Carreau marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

for ix, exc in enumerate(self._chained_exceptions):
prompt = ">" if ix == self._chained_exception_index else " "
rep = repr(exc)
if len(rep) > 80:
rep = rep[:77] + "..."
self.message(f"{prompt} {ix:>3} {rep}")
else:
try:
number = int(arg)
except ValueError:
self.error("Argument must be an integer")
return
if 0 <= number < len(self._chained_exceptions):
self._chained_exception_index = number
self.setup(None, self._chained_exceptions[number].__traceback__)
self.print_stack_entry(self.stack[self.curindex])
else:
Comment thread
Carreau marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

self.error("No exception with that number")

def do_up(self, arg):
"""u(p) [count]

Expand Down Expand Up @@ -1890,11 +1997,15 @@ def set_trace(*, header=None):
# Post-Mortem interface

def post_mortem(t=None):
"""Enter post-mortem debugging of the given *traceback* object.
"""Enter post-mortem debugging of the given *traceback*, or *exception*
Comment thread
iritkatriel marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

object.

If no traceback is given, it uses the one of the exception that is
currently being handled (an exception must be being handled if the
default is to be used).

Comment thread
Carreau marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

If `t` is an exception object, the `exceptions` command makes it possible to
list and inspect its chained exceptions (if any).
"""
# handling the default
if t is None:
Expand All @@ -1911,12 +2022,8 @@ def post_mortem(t=None):
p.interaction(None, t)

def pm():
"""Enter post-mortem debugging of the traceback found in sys.last_traceback."""
if hasattr(sys, 'last_exc'):
tb = sys.last_exc.__traceback__
else:
tb = sys.last_traceback
post_mortem(tb)
"""Enter post-mortem debugging of the traceback found in sys.last_exc."""
post_mortem(sys.last_exc)


# Main program for testing
Expand Down Expand Up @@ -1996,8 +2103,7 @@ def main():
traceback.print_exc()
print("Uncaught exception. Entering post mortem debugging")
print("Running 'cont' or 'step' will restart the program")
t = e.__traceback__
pdb.interaction(None, t)
pdb.interaction(None, e)
print("Post mortem debugger finished. The " + target +
" will be restarted")

Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.