| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,35 @@ | |||
| 1 | + import unittest | ||
| 2 | + from unittest import mock | ||
| 3 | + | ||
| 4 | + from test.support import captured_stderr | ||
| 5 | + import idlelib.run as idlerun | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + class RunTest(unittest.TestCase): | ||
| 9 | + def test_print_exception_unhashable(self): | ||
| 10 | + class UnhashableException(Exception): | ||
| 11 | + def __eq__(self, other): | ||
| 12 | + return True | ||
| 13 | + | ||
| 14 | + ex1 = UnhashableException('ex1') | ||
| 15 | + ex2 = UnhashableException('ex2') | ||
| 16 | + try: | ||
| 17 | + raise ex2 from ex1 | ||
| 18 | + except UnhashableException: | ||
| 19 | + try: | ||
| 20 | + raise ex1 | ||
| 21 | + except UnhashableException: | ||
| 22 | + with captured_stderr() as output: | ||
| 23 | + with mock.patch.object(idlerun, | ||
| 24 | + 'cleanup_traceback') as ct: | ||
| 25 | + ct.side_effect = lambda t, e: t | ||
| 26 | + idlerun.print_exception() | ||
| 27 | + | ||
| 28 | + tb = output.getvalue().strip().splitlines() | ||
| 29 | + self.assertEqual(11, len(tb)) | ||
| 30 | + self.assertIn('UnhashableException: ex2', tb[3]) | ||
| 31 | + self.assertIn('UnhashableException: ex1', tb[10]) | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + if __name__ == '__main__': | ||
| 35 | + unittest.main(verbosity=2) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -203,16 +203,16 @@ def print_exception(): | |||
| 203 | 203 | seen = set() | |
| 204 | 204 | ||
| 205 | 205 | def print_exc(typ, exc, tb): | |
| 206 | - seen.add(exc) | ||
| 206 | + seen.add(id(exc)) | ||
| 207 | 207 | context = exc.__context__ | |
| 208 | 208 | cause = exc.__cause__ | |
| 209 | - if cause is not None and cause not in seen: | ||
| 209 | + if cause is not None and id(cause) not in seen: | ||
| 210 | 210 | print_exc(type(cause), cause, cause.__traceback__) | |
| 211 | 211 | print("\nThe above exception was the direct cause " | |
| 212 | 212 | "of the following exception:\n", file=efile) | |
| 213 | 213 | elif (context is not None and | |
| 214 | 214 | not exc.__suppress_context__ and | |
| 215 | - context not in seen): | ||
| 215 | + id(context) not in seen): | ||
| 216 | 216 | print_exc(type(context), context, context.__traceback__) | |
| 217 | 217 | print("\nDuring handling of the above exception, " | |
| 218 | 218 | "another exception occurred:\n", file=efile) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -443,6 +443,33 @@ def fmt(): | |||
| 443 | 443 | ' return traceback.format_stack()\n' % (__file__, lineno+1), | |
| 444 | 444 | ]) | |
| 445 | 445 | ||
| 446 | + @cpython_only | ||
| 447 | + def test_unhashable(self): | ||
| 448 | + from _testcapi import exception_print | ||
| 449 | + | ||
| 450 | + class UnhashableException(Exception): | ||
| 451 | + def __eq__(self, other): | ||
| 452 | + return True | ||
| 453 | + | ||
| 454 | + ex1 = UnhashableException('ex1') | ||
| 455 | + ex2 = UnhashableException('ex2') | ||
| 456 | + try: | ||
| 457 | + raise ex2 from ex1 | ||
| 458 | + except UnhashableException: | ||
| 459 | + try: | ||
| 460 | + raise ex1 | ||
| 461 | + except UnhashableException: | ||
| 462 | + exc_type, exc_val, exc_tb = sys.exc_info() | ||
| 463 | + | ||
| 464 | + with captured_output("stderr") as stderr_f: | ||
| 465 | + exception_print(exc_val) | ||
| 466 | + | ||
| 467 | + tb = stderr_f.getvalue().strip().splitlines() | ||
| 468 | + self.assertEqual(11, len(tb)) | ||
| 469 | + self.assertEqual(context_message.strip(), tb[5]) | ||
| 470 | + self.assertIn('UnhashableException: ex2', tb[3]) | ||
| 471 | + self.assertIn('UnhashableException: ex1', tb[10]) | ||
| 472 | + | ||
| 446 | 473 | ||
| 447 | 474 | cause_message = ( | |
| 448 | 475 | "\nThe above exception was the direct cause " | |
@@ -994,6 +1021,25 @@ def test_context(self): | |||
| 994 | 1021 | self.assertEqual(exc_info[0], exc.exc_type) | |
| 995 | 1022 | self.assertEqual(str(exc_info[1]), str(exc)) | |
| 996 | 1023 | ||
| 1024 | + def test_unhashable(self): | ||
| 1025 | + class UnhashableException(Exception): | ||
| 1026 | + def __eq__(self, other): | ||
| 1027 | + return True | ||
| 1028 | + | ||
| 1029 | + ex1 = UnhashableException('ex1') | ||
| 1030 | + ex2 = UnhashableException('ex2') | ||
| 1031 | + try: | ||
| 1032 | + raise ex2 from ex1 | ||
| 1033 | + except UnhashableException: | ||
| 1034 | + try: | ||
| 1035 | + raise ex1 | ||
| 1036 | + except UnhashableException: | ||
| 1037 | + exc_info = sys.exc_info() | ||
| 1038 | + exc = traceback.TracebackException(*exc_info) | ||
| 1039 | + formatted = list(exc.format()) | ||
| 1040 | + self.assertIn('UnhashableException: ex2\n', formatted[2]) | ||
| 1041 | + self.assertIn('UnhashableException: ex1\n', formatted[6]) | ||
| 1042 | + | ||
| 997 | 1043 | def test_limit(self): | |
| 998 | 1044 | def recurse(n): | |
| 999 | 1045 | if n: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -458,11 +458,11 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, | |||
| 458 | 458 | # Handle loops in __cause__ or __context__. | |
| 459 | 459 | if _seen is None: | |
| 460 | 460 | _seen = set() | |
| 461 | - _seen.add(exc_value) | ||
| 461 | + _seen.add(id(exc_value)) | ||
| 462 | 462 | # Gracefully handle (the way Python 2.4 and earlier did) the case of | |
| 463 | 463 | # being called with no type or value (None, None, None). | |
| 464 | 464 | if (exc_value and exc_value.__cause__ is not None | |
| 465 | - and exc_value.__cause__ not in _seen): | ||
| 465 | + and id(exc_value.__cause__) not in _seen): | ||
| 466 | 466 | cause = TracebackException( | |
| 467 | 467 | type(exc_value.__cause__), | |
| 468 | 468 | exc_value.__cause__, | |
@@ -474,7 +474,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, | |||
| 474 | 474 | else: | |
| 475 | 475 | cause = None | |
| 476 | 476 | if (exc_value and exc_value.__context__ is not None | |
| 477 | - and exc_value.__context__ not in _seen): | ||
| 477 | + and id(exc_value.__context__) not in _seen): | ||
| 478 | 478 | context = TracebackException( | |
| 479 | 479 | type(exc_value.__context__), | |
| 480 | 480 | exc_value.__context__, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -147,6 +147,7 @@ Dominic Binks | |||
| 147 | 147 | Philippe Biondi | |
| 148 | 148 | Michael Birtwell | |
| 149 | 149 | Stuart Bishop | |
| 150 | + Zane Bitter | ||
| 150 | 151 | Roy Bixler | |
| 151 | 152 | Daniel Black | |
| 152 | 153 | Jonathan Black | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + Print the full context/cause chain of exceptions on interpreter exit, even | ||
| 2 | + if an exception in the chain is unhashable or compares equal to later ones. | ||
| 3 | + Patch by Zane Bitter. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Fix a TypeError that caused a shell restart when printing a traceback that | ||
| 2 | + includes an exception that is unhashable. Patch by Zane Bitter. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + traceback: Fix a TypeError that occurred during printing of exception | ||
| 2 | + tracebacks when either the current exception or an exception in its | ||
| 3 | + context/cause chain is unhashable. Patch by Zane Bitter. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -817,13 +817,21 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen) | |||
| 817 | 817 | ||
| 818 | 818 | if (seen != NULL) { | |
| 819 | 819 | /* Exception chaining */ | |
| 820 | - if (PySet_Add(seen, value) == -1) | ||
| 820 | + PyObject *value_id = PyLong_FromVoidPtr(value); | ||
| 821 | + if (value_id == NULL || PySet_Add(seen, value_id) == -1) | ||
| 821 | 822 | PyErr_Clear(); | |
| 822 | 823 | else if (PyExceptionInstance_Check(value)) { | |
| 824 | + PyObject *check_id = NULL; | ||
| 823 | 825 | cause = PyException_GetCause(value); | |
| 824 | 826 | context = PyException_GetContext(value); | |
| 825 | 827 | if (cause) { | |
| 826 | - res = PySet_Contains(seen, cause); | ||
| 828 | + check_id = PyLong_FromVoidPtr(cause); | ||
| 829 | + if (check_id == NULL) { | ||
| 830 | + res = -1; | ||
| 831 | + } else { | ||
| 832 | + res = PySet_Contains(seen, check_id); | ||
| 833 | + Py_DECREF(check_id); | ||
| 834 | + } | ||
| 827 | 835 | if (res == -1) | |
| 828 | 836 | PyErr_Clear(); | |
| 829 | 837 | if (res == 0) { | |
@@ -835,7 +843,13 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen) | |||
| 835 | 843 | } | |
| 836 | 844 | else if (context && | |
| 837 | 845 | !((PyBaseExceptionObject *)value)->suppress_context) { | |
| 838 | - res = PySet_Contains(seen, context); | ||
| 846 | + check_id = PyLong_FromVoidPtr(context); | ||
| 847 | + if (check_id == NULL) { | ||
| 848 | + res = -1; | ||
| 849 | + } else { | ||
| 850 | + res = PySet_Contains(seen, check_id); | ||
| 851 | + Py_DECREF(check_id); | ||
| 852 | + } | ||
| 839 | 853 | if (res == -1) | |
| 840 | 854 | PyErr_Clear(); | |
| 841 | 855 | if (res == 0) { | |
@@ -848,6 +862,7 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen) | |||
| 848 | 862 | Py_XDECREF(context); | |
| 849 | 863 | Py_XDECREF(cause); | |
| 850 | 864 | } | |
| 865 | + Py_XDECREF(value_id); | ||
| 851 | 866 | } | |
| 852 | 867 | print_exception(f, value); | |
| 853 | 868 | if (err != 0) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments