Joe Wells <[email protected]> added the comment:
I would like to request that this bug be repopened and fixed.
I've changed (or at least tried to change, I'm not sure if it will let me) the
title of the bug to point out that the failure happens in
FrameSummary.__init__. It does not happen in StackSummary.format.
This problem makes the capture_locals=True feature of TracebackException and
StackSummary and the "locals" parameter of FrameSummary unreliable. If any one
of the local variables in any frame on the stack is in an inconsistent state
such that repr will raise an exception, the processing of the traceback fails.
This kind of inconsistent state is of course likely to happen during debugging,
which is precisely when you would want the capture_locals feature to actually
work so you can see what is going wrong.
Just one example of an exception traceback being created with an unsafe local
variable value in one of the stack frames is in the following line:
from _pydecimal import Decimal as D; D(None)
The _pydecimal.Decimal.__new__ method raises an exception when it sees a value
it doesn't know how to convert to Decimal. When it does this, the new object
it was creating is left in an inconsistent state missing the _sign attribute.
When you try to inspect the resulting exception traceback with
traceback.TracebackException(..., capture_locals=True), this raises an
exception.
While I was tracking this bug down, I discovered that the
traceback.TracebackException.__init__ method has the same problem: it
initializes the _str attribute used by the __str__ method quite late and when
it raised an exception before this point, the incompletely initialized
TracebackException object caused repr to fail. There's at least one more class
in traceback.py that also has this problem, but I don't remember which one it
is.
The cascade of exceptions causing exceptions causing exceptions makes the
capture_locals feature difficult to use and debug.
Here is a short snippet that reproduces the bug on Python 3.9.7:
import traceback
class TriggerTracebackBug:
def __init__(self):
raise RuntimeError("can't build a TriggerTracebackBug object for some
reason")
self._repr = 'if we reached this line, this object would have a repr
result'
def __repr__(self):
return self._repr
try:
TriggerTracebackBug()
except Exception as e:
# this method call fails because there is a stack frame with a local
# variable (self) such that repr fails on that variable's value:
traceback.TracebackException.from_exception(e, capture_locals=True)
It's clear that it is too much to expect every class to implement a safe
__repr__ method. So it also seems clear to me that
traceback.FrameSummary.__init__ is the place to fix it.
My suggested fix is to replace this line in the latest Lib/traceback.py:
self.locals = {k: repr(v) for k, v in locals.items()} if locals else
None
with something like this code (written in a somewhat awkward way because that
helped while I was debugging it):
if locals:
d = {}
self.locals = d
for k, v in locals.items():
try:
d[k] = repr(v)
except Exception:
d[k] = '''<an exception was raised while trying to format
this local variable's value>'''
else:
self.locals = None
I've tested this code in an older version of Python and it fixed the problem
for me.
----------
nosy: +jbw
title: StackSummary.format fails if repr(value) fails -> TracebackException or
StackSummary.extract with capture_locals=True fail to catch exceptions raised
by repr() on value of frame local variable in FrameSummary.__init__.
_______________________________________
Python tracker <[email protected]>
<https://bugs.python.org/issue43656>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com