Commit de86073a authored by Zane Bitter's avatar Zane Bitter Committed by Serhiy Storchaka

bpo-28603: Fix formatting tracebacks for unhashable exceptions (#4014)

parent 191e3138
import unittest
from unittest import mock
from test.support import captured_stderr
import idlelib.run as idlerun
class RunTest(unittest.TestCase):
def test_print_exception_unhashable(self):
class UnhashableException(Exception):
def __eq__(self, other):
return True
ex1 = UnhashableException('ex1')
ex2 = UnhashableException('ex2')
try:
raise ex2 from ex1
except UnhashableException:
try:
raise ex1
except UnhashableException:
with captured_stderr() as output:
with mock.patch.object(idlerun,
'cleanup_traceback') as ct:
ct.side_effect = lambda t, e: t
idlerun.print_exception()
tb = output.getvalue().strip().splitlines()
self.assertEqual(11, len(tb))
self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
if __name__ == '__main__':
unittest.main(verbosity=2)
...@@ -203,16 +203,16 @@ def print_exception(): ...@@ -203,16 +203,16 @@ def print_exception():
seen = set() seen = set()
def print_exc(typ, exc, tb): def print_exc(typ, exc, tb):
seen.add(exc) seen.add(id(exc))
context = exc.__context__ context = exc.__context__
cause = exc.__cause__ cause = exc.__cause__
if cause is not None and cause not in seen: if cause is not None and id(cause) not in seen:
print_exc(type(cause), cause, cause.__traceback__) print_exc(type(cause), cause, cause.__traceback__)
print("\nThe above exception was the direct cause " print("\nThe above exception was the direct cause "
"of the following exception:\n", file=efile) "of the following exception:\n", file=efile)
elif (context is not None and elif (context is not None and
not exc.__suppress_context__ and not exc.__suppress_context__ and
context not in seen): id(context) not in seen):
print_exc(type(context), context, context.__traceback__) print_exc(type(context), context, context.__traceback__)
print("\nDuring handling of the above exception, " print("\nDuring handling of the above exception, "
"another exception occurred:\n", file=efile) "another exception occurred:\n", file=efile)
......
...@@ -443,6 +443,33 @@ class TracebackFormatTests(unittest.TestCase): ...@@ -443,6 +443,33 @@ class TracebackFormatTests(unittest.TestCase):
' return traceback.format_stack()\n' % (__file__, lineno+1), ' return traceback.format_stack()\n' % (__file__, lineno+1),
]) ])
@cpython_only
def test_unhashable(self):
from _testcapi import exception_print
class UnhashableException(Exception):
def __eq__(self, other):
return True
ex1 = UnhashableException('ex1')
ex2 = UnhashableException('ex2')
try:
raise ex2 from ex1
except UnhashableException:
try:
raise ex1
except UnhashableException:
exc_type, exc_val, exc_tb = sys.exc_info()
with captured_output("stderr") as stderr_f:
exception_print(exc_val)
tb = stderr_f.getvalue().strip().splitlines()
self.assertEqual(11, len(tb))
self.assertEqual(context_message.strip(), tb[5])
self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
cause_message = ( cause_message = (
"\nThe above exception was the direct cause " "\nThe above exception was the direct cause "
...@@ -994,6 +1021,25 @@ class TestTracebackException(unittest.TestCase): ...@@ -994,6 +1021,25 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(exc_info[0], exc.exc_type) self.assertEqual(exc_info[0], exc.exc_type)
self.assertEqual(str(exc_info[1]), str(exc)) self.assertEqual(str(exc_info[1]), str(exc))
def test_unhashable(self):
class UnhashableException(Exception):
def __eq__(self, other):
return True
ex1 = UnhashableException('ex1')
ex2 = UnhashableException('ex2')
try:
raise ex2 from ex1
except UnhashableException:
try:
raise ex1
except UnhashableException:
exc_info = sys.exc_info()
exc = traceback.TracebackException(*exc_info)
formatted = list(exc.format())
self.assertIn('UnhashableException: ex2\n', formatted[2])
self.assertIn('UnhashableException: ex1\n', formatted[6])
def test_limit(self): def test_limit(self):
def recurse(n): def recurse(n):
if n: if n:
......
...@@ -458,11 +458,11 @@ class TracebackException: ...@@ -458,11 +458,11 @@ class TracebackException:
# Handle loops in __cause__ or __context__. # Handle loops in __cause__ or __context__.
if _seen is None: if _seen is None:
_seen = set() _seen = set()
_seen.add(exc_value) _seen.add(id(exc_value))
# Gracefully handle (the way Python 2.4 and earlier did) the case of # Gracefully handle (the way Python 2.4 and earlier did) the case of
# being called with no type or value (None, None, None). # being called with no type or value (None, None, None).
if (exc_value and exc_value.__cause__ is not None if (exc_value and exc_value.__cause__ is not None
and exc_value.__cause__ not in _seen): and id(exc_value.__cause__) not in _seen):
cause = TracebackException( cause = TracebackException(
type(exc_value.__cause__), type(exc_value.__cause__),
exc_value.__cause__, exc_value.__cause__,
...@@ -474,7 +474,7 @@ class TracebackException: ...@@ -474,7 +474,7 @@ class TracebackException:
else: else:
cause = None cause = None
if (exc_value and exc_value.__context__ is not None if (exc_value and exc_value.__context__ is not None
and exc_value.__context__ not in _seen): and id(exc_value.__context__) not in _seen):
context = TracebackException( context = TracebackException(
type(exc_value.__context__), type(exc_value.__context__),
exc_value.__context__, exc_value.__context__,
......
...@@ -147,6 +147,7 @@ Dominic Binks ...@@ -147,6 +147,7 @@ Dominic Binks
Philippe Biondi Philippe Biondi
Michael Birtwell Michael Birtwell
Stuart Bishop Stuart Bishop
Zane Bitter
Roy Bixler Roy Bixler
Daniel Black Daniel Black
Jonathan Black Jonathan Black
......
Print the full context/cause chain of exceptions on interpreter exit, even
if an exception in the chain is unhashable or compares equal to later ones.
Patch by Zane Bitter.
Fix a TypeError that caused a shell restart when printing a traceback that
includes an exception that is unhashable. Patch by Zane Bitter.
traceback: Fix a TypeError that occurred during printing of exception
tracebacks when either the current exception or an exception in its
context/cause chain is unhashable. Patch by Zane Bitter.
...@@ -817,13 +817,21 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen) ...@@ -817,13 +817,21 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
if (seen != NULL) { if (seen != NULL) {
/* Exception chaining */ /* Exception chaining */
if (PySet_Add(seen, value) == -1) PyObject *value_id = PyLong_FromVoidPtr(value);
if (value_id == NULL || PySet_Add(seen, value_id) == -1)
PyErr_Clear(); PyErr_Clear();
else if (PyExceptionInstance_Check(value)) { else if (PyExceptionInstance_Check(value)) {
PyObject *check_id = NULL;
cause = PyException_GetCause(value); cause = PyException_GetCause(value);
context = PyException_GetContext(value); context = PyException_GetContext(value);
if (cause) { if (cause) {
res = PySet_Contains(seen, cause); check_id = PyLong_FromVoidPtr(cause);
if (check_id == NULL) {
res = -1;
} else {
res = PySet_Contains(seen, check_id);
Py_DECREF(check_id);
}
if (res == -1) if (res == -1)
PyErr_Clear(); PyErr_Clear();
if (res == 0) { if (res == 0) {
...@@ -835,7 +843,13 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen) ...@@ -835,7 +843,13 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
} }
else if (context && else if (context &&
!((PyBaseExceptionObject *)value)->suppress_context) { !((PyBaseExceptionObject *)value)->suppress_context) {
res = PySet_Contains(seen, context); check_id = PyLong_FromVoidPtr(context);
if (check_id == NULL) {
res = -1;
} else {
res = PySet_Contains(seen, check_id);
Py_DECREF(check_id);
}
if (res == -1) if (res == -1)
PyErr_Clear(); PyErr_Clear();
if (res == 0) { if (res == 0) {
...@@ -848,6 +862,7 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen) ...@@ -848,6 +862,7 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
Py_XDECREF(context); Py_XDECREF(context);
Py_XDECREF(cause); Py_XDECREF(cause);
} }
Py_XDECREF(value_id);
} }
print_exception(f, value); print_exception(f, value);
if (err != 0) if (err != 0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment