Skip to content

Commit 8cacc63

Browse files
committed
Issue #22836: Merge exception reporting from 3.5
2 parents b8d7503 + 3263f68 commit 8cacc63

File tree

5 files changed

+92
-9
lines changed

5 files changed

+92
-9
lines changed

Doc/c-api/exceptions.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ Printing and clearing
7474
:meth:`__del__` method.
7575
7676
The function is called with a single argument *obj* that identifies the context
77-
in which the unraisable exception occurred. The repr of *obj* will be printed in
78-
the warning message.
77+
in which the unraisable exception occurred. If possible,
78+
the repr of *obj* will be printed in the warning message.
7979
8080
8181
Raising exceptions

Lib/test/test_exceptions.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import weakref
88
import errno
99

10-
from test.support import (TESTFN, captured_output, check_impl_detail,
10+
from test.support import (TESTFN, captured_stderr, check_impl_detail,
1111
check_warnings, cpython_only, gc_collect, run_unittest,
1212
no_tracing, unlink, import_module)
1313

@@ -20,6 +20,10 @@ class SlottedNaiveException(Exception):
2020
def __init__(self, x):
2121
self.x = x
2222

23+
class BrokenStrException(Exception):
24+
def __str__(self):
25+
raise Exception("str() is broken")
26+
2327
# XXX This is not really enough, each *operation* should be tested!
2428

2529
class ExceptionTests(unittest.TestCase):
@@ -882,7 +886,7 @@ def __subclasscheck__(cls, subclass):
882886
class MyException(Exception, metaclass=Meta):
883887
pass
884888

885-
with captured_output("stderr") as stderr:
889+
with captured_stderr() as stderr:
886890
try:
887891
raise KeyError()
888892
except MyException as e:
@@ -1011,6 +1015,66 @@ def test_errno_ENOTDIR(self):
10111015
os.listdir(__file__)
10121016
self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception)
10131017

1018+
def test_unraisable(self):
1019+
# Issue #22836: PyErr_WriteUnraisable() should give sensible reports
1020+
class BrokenDel:
1021+
def __del__(self):
1022+
exc = ValueError("del is broken")
1023+
# The following line is included in the traceback report:
1024+
raise exc
1025+
1026+
class BrokenRepr(BrokenDel):
1027+
def __repr__(self):
1028+
raise AttributeError("repr() is broken")
1029+
1030+
class BrokenExceptionDel:
1031+
def __del__(self):
1032+
exc = BrokenStrException()
1033+
# The following line is included in the traceback report:
1034+
raise exc
1035+
1036+
for test_class in (BrokenDel, BrokenRepr, BrokenExceptionDel):
1037+
with self.subTest(test_class):
1038+
obj = test_class()
1039+
with captured_stderr() as stderr:
1040+
del obj
1041+
report = stderr.getvalue()
1042+
self.assertIn("Exception ignored", report)
1043+
if test_class is BrokenRepr:
1044+
self.assertIn("<object repr() failed>", report)
1045+
else:
1046+
self.assertIn(test_class.__del__.__qualname__, report)
1047+
self.assertIn("test_exceptions.py", report)
1048+
self.assertIn("raise exc", report)
1049+
if test_class is BrokenExceptionDel:
1050+
self.assertIn("BrokenStrException", report)
1051+
self.assertIn("<exception str() failed>", report)
1052+
else:
1053+
self.assertIn("ValueError", report)
1054+
self.assertIn("del is broken", report)
1055+
self.assertTrue(report.endswith("\n"))
1056+
1057+
def test_unhandled(self):
1058+
# Check for sensible reporting of unhandled exceptions
1059+
for exc_type in (ValueError, BrokenStrException):
1060+
with self.subTest(exc_type):
1061+
try:
1062+
exc = exc_type("test message")
1063+
# The following line is included in the traceback report:
1064+
raise exc
1065+
except exc_type:
1066+
with captured_stderr() as stderr:
1067+
sys.__excepthook__(*sys.exc_info())
1068+
report = stderr.getvalue()
1069+
self.assertIn("test_exceptions.py", report)
1070+
self.assertIn("raise exc", report)
1071+
self.assertIn(exc_type.__name__, report)
1072+
if exc_type is BrokenStrException:
1073+
self.assertIn("<exception str() failed>", report)
1074+
else:
1075+
self.assertIn("test message", report)
1076+
self.assertTrue(report.endswith("\n"))
1077+
10141078

10151079
class ImportErrorTests(unittest.TestCase):
10161080

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ Release date: tba
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #22836: Ensure exception reports from PyErr_Display() and
14+
PyErr_WriteUnraisable() are sensible even when formatting them produces
15+
secondary errors. This affects the reports produced by
16+
sys.__excepthook__() and when __del__() raises an exception.
17+
1318
- Issue #26302: Correct behavior to reject comma as a legal character for
1419
cookie names.
1520

Python/errors.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -900,8 +900,12 @@ PyErr_WriteUnraisable(PyObject *obj)
900900
if (obj) {
901901
if (PyFile_WriteString("Exception ignored in: ", f) < 0)
902902
goto done;
903-
if (PyFile_WriteObject(obj, f, 0) < 0)
904-
goto done;
903+
if (PyFile_WriteObject(obj, f, 0) < 0) {
904+
PyErr_Clear();
905+
if (PyFile_WriteString("<object repr() failed>", f) < 0) {
906+
goto done;
907+
}
908+
}
905909
if (PyFile_WriteString("\n", f) < 0)
906910
goto done;
907911
}
@@ -946,8 +950,12 @@ PyErr_WriteUnraisable(PyObject *obj)
946950
if (v && v != Py_None) {
947951
if (PyFile_WriteString(": ", f) < 0)
948952
goto done;
949-
if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0)
950-
goto done;
953+
if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0) {
954+
PyErr_Clear();
955+
if (PyFile_WriteString("<exception str() failed>", f) < 0) {
956+
goto done;
957+
}
958+
}
951959
}
952960
if (PyFile_WriteString("\n", f) < 0)
953961
goto done;

Python/pythonrun.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,11 @@ print_exception(PyObject *f, PyObject *value)
766766
/* only print colon if the str() of the
767767
object is not the empty string
768768
*/
769-
if (s == NULL)
769+
if (s == NULL) {
770+
PyErr_Clear();
770771
err = -1;
772+
PyFile_WriteString(": <exception str() failed>", f);
773+
}
771774
else if (!PyUnicode_Check(s) ||
772775
PyUnicode_GetLength(s) != 0)
773776
err = PyFile_WriteString(": ", f);
@@ -776,6 +779,9 @@ print_exception(PyObject *f, PyObject *value)
776779
Py_XDECREF(s);
777780
}
778781
/* try to write a newline in any case */
782+
if (err < 0) {
783+
PyErr_Clear();
784+
}
779785
err += PyFile_WriteString("\n", f);
780786
Py_XDECREF(tb);
781787
Py_DECREF(value);

0 commit comments

Comments
 (0)