gh-87822: Make traceback module robust to exceptions from repr() of local values (GH-94691)

This commit is contained in:
Simon-Martin Schröder 2022-07-11 11:14:15 +02:00 committed by GitHub
parent da717519ec
commit 46fc584b00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 14 additions and 3 deletions

View file

@ -341,6 +341,10 @@ capture data for later printing in a lightweight fashion.
local variables in each :class:`FrameSummary` are captured as object local variables in each :class:`FrameSummary` are captured as object
representations. representations.
.. versionchanged:: 3.12
Exceptions raised from :func:`repr` on a local variable (when
*capture_locals* is ``True``) are no longer propagated to the caller.
.. classmethod:: from_list(a_list) .. classmethod:: from_list(a_list)
Construct a :class:`StackSummary` object from a supplied list of Construct a :class:`StackSummary` object from a supplied list of

View file

@ -2279,6 +2279,9 @@ def format_frame_summary(self, frame_summary):
f' File "{__file__}", line {lno}, in f\n 1/0\n' f' File "{__file__}", line {lno}, in f\n 1/0\n'
) )
class Unrepresentable:
def __repr__(self) -> str:
raise Exception("Unrepresentable")
class TestTracebackException(unittest.TestCase): class TestTracebackException(unittest.TestCase):
@ -2546,12 +2549,13 @@ def test_locals(self):
linecache.updatecache('/foo.py', globals()) linecache.updatecache('/foo.py', globals())
e = Exception("uh oh") e = Exception("uh oh")
c = test_code('/foo.py', 'method') c = test_code('/foo.py', 'method')
f = test_frame(c, globals(), {'something': 1, 'other': 'string'}) f = test_frame(c, globals(), {'something': 1, 'other': 'string', 'unrepresentable': Unrepresentable()})
tb = test_tb(f, 6, None, 0) tb = test_tb(f, 6, None, 0)
exc = traceback.TracebackException( exc = traceback.TracebackException(
Exception, e, tb, capture_locals=True) Exception, e, tb, capture_locals=True)
self.assertEqual( self.assertEqual(
exc.stack[0].locals, {'something': '1', 'other': "'string'"}) exc.stack[0].locals,
{'something': '1', 'other': "'string'", 'unrepresentable': '<local repr() failed>'})
def test_no_locals(self): def test_no_locals(self):
linecache.updatecache('/foo.py', globals()) linecache.updatecache('/foo.py', globals())

View file

@ -279,7 +279,8 @@ def __init__(self, filename, lineno, name, *, lookup_line=True,
self._line = line self._line = line
if lookup_line: if lookup_line:
self.line self.line
self.locals = {k: repr(v) for k, v in locals.items()} if locals else None self.locals = {k: _safe_string(v, 'local', func=repr)
for k, v in locals.items()} if locals else None
self.end_lineno = end_lineno self.end_lineno = end_lineno
self.colno = colno self.colno = colno
self.end_colno = end_colno self.end_colno = end_colno

View file

@ -1590,6 +1590,7 @@ Ed Schouten
Scott Schram Scott Schram
Robin Schreiber Robin Schreiber
Chad J. Schroeder Chad J. Schroeder
Simon-Martin Schroeder
Christian Schubert Christian Schubert
Sam Schulenburg Sam Schulenburg
Andreas Schwab Andreas Schwab

View file

@ -0,0 +1 @@
When called with ``capture_locals=True``, the :mod:`traceback` module functions swallow exceptions raised from calls to ``repr()`` on local variables of frames. This is in order to prioritize the original exception over rendering errors. An indication of the failure is printed in place of the missing value. (Patch by Simon-Martin Schroeder).