Skip to content

Commit ef85a1a

Browse files
committed
Issue python#22836: Keep exception reports sensible despite errors
1 parent f9ce84b commit ef85a1a

5 files changed

Lines changed: 87 additions & 9 deletions

File tree

‎Doc/c-api/exceptions.rst‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@ is a separate error indicator for each thread.
404404
:meth:`__del__` method.
405405
406406
The function is called with a single argument *obj* that identifies the context
407-
in which the unraisable exception occurred. The repr of *obj* will be printed in
408-
the warning message.
407+
in which the unraisable exception occurred. If possible,
408+
the repr of *obj* will be printed in the warning message.
409409
410410
411411
.. _unicodeexceptions:

‎Lib/test/test_exceptions.py‎

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
import unittest
66
import pickle, cPickle
77

8-
from test.test_support import (TESTFN, unlink, run_unittest, captured_output,
8+
from test.test_support import (TESTFN, unlink, run_unittest, captured_stderr,
99
check_warnings, cpython_only)
1010
from test.test_pep352 import ignore_deprecation_warnings
1111

12+
class BrokenStrException(Exception):
13+
def __str__(self):
14+
raise Exception("str() is broken")
15+
__repr__ = __str__ # Python 2's PyErr_WriteUnraisable() uses repr()
16+
1217
# XXX This is not really enough, each *operation* should be tested!
1318

1419
class ExceptionTests(unittest.TestCase):
@@ -375,7 +380,7 @@ def g():
375380
# The test prints an unraisable recursion error when
376381
# doing "except ValueError", this is because subclass
377382
# checking has recursion checking too.
378-
with captured_output("stderr"):
383+
with captured_stderr():
379384
try:
380385
g()
381386
except RuntimeError:
@@ -448,7 +453,7 @@ class MyException(Exception):
448453
__metaclass__ = Meta
449454
pass
450455

451-
with captured_output("stderr") as stderr:
456+
with captured_stderr() as stderr:
452457
try:
453458
raise KeyError()
454459
except MyException, e:
@@ -460,7 +465,7 @@ class MyException(Exception):
460465
else:
461466
self.fail("Should have raised KeyError")
462467

463-
with captured_output("stderr") as stderr:
468+
with captured_stderr() as stderr:
464469
def g():
465470
try:
466471
return g()
@@ -644,6 +649,62 @@ class C(object):
644649
self.assertEqual(error5.a, 1)
645650
self.assertEqual(error5.__doc__, "")
646651

652+
def test_unraisable(self):
653+
# Issue #22836: PyErr_WriteUnraisable() should give sensible reports
654+
class BrokenDel:
655+
def __del__(self):
656+
exc = ValueError("del is broken")
657+
# In Python 3, the following line would be in the report:
658+
raise exc
659+
660+
class BrokenRepr(BrokenDel):
661+
def __repr__(self):
662+
raise AttributeError("repr() is broken")
663+
664+
class BrokenExceptionDel:
665+
def __del__(self):
666+
exc = BrokenStrException()
667+
# In Python 3, the following line would be in the report:
668+
raise exc
669+
670+
for test_class in (BrokenDel, BrokenRepr, BrokenExceptionDel):
671+
obj = test_class()
672+
with captured_stderr() as stderr:
673+
del obj
674+
report = stderr.getvalue()
675+
self.assertRegexpMatches(report, "Exception.* ignored")
676+
if test_class is BrokenRepr:
677+
self.assertIn("<object repr() failed>", report)
678+
else:
679+
self.assertIn("__del__", report)
680+
if test_class is BrokenExceptionDel:
681+
self.assertIn("BrokenStrException", report)
682+
self.assertIn("<exception repr() failed>", report)
683+
else:
684+
self.assertIn("ValueError", report)
685+
self.assertIn("del is broken", report)
686+
self.assertTrue(report.endswith("\n"))
687+
688+
def test_unhandled(self):
689+
# Check for sensible reporting of unhandled exceptions
690+
for exc_type in (ValueError, BrokenStrException):
691+
try:
692+
exc = exc_type("test message")
693+
# The following line is included in the traceback report:
694+
raise exc
695+
except exc_type:
696+
with captured_stderr() as stderr:
697+
sys.__excepthook__(*sys.exc_info())
698+
report = stderr.getvalue()
699+
self.assertIn("test_exceptions.py", report)
700+
self.assertIn("raise exc", report)
701+
self.assertIn(exc_type.__name__, report)
702+
if exc_type is BrokenStrException:
703+
self.assertIn("<exception str() failed>", report)
704+
else:
705+
self.assertIn("test message", report)
706+
self.assertTrue(report.endswith("\n"))
707+
647708

648709
def test_main():
649710
run_unittest(ExceptionTests, TestSameStrAndUnicodeMsg)

‎Misc/NEWS‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 2.7.12?
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 #22847: Improve method cache efficiency.
1419

1520
- Issue #25843: When compiling code, don't merge constants if they are equal

‎Python/errors.c‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,12 +696,18 @@ PyErr_WriteUnraisable(PyObject *obj)
696696
PyFile_WriteString(className, f);
697697
if (v && v != Py_None) {
698698
PyFile_WriteString(": ", f);
699-
PyFile_WriteObject(v, f, 0);
699+
if (PyFile_WriteObject(v, f, 0) < 0) {
700+
PyErr_Clear();
701+
PyFile_WriteString("<exception repr() failed>", f);
702+
}
700703
}
701704
Py_XDECREF(moduleName);
702705
}
703706
PyFile_WriteString(" in ", f);
704-
PyFile_WriteObject(obj, f, 0);
707+
if (PyFile_WriteObject(obj, f, 0) < 0) {
708+
PyErr_Clear();
709+
PyFile_WriteString("<object repr() failed>", f);
710+
}
705711
PyFile_WriteString(" ignored\n", f);
706712
PyErr_Clear(); /* Just in case */
707713
}

‎Python/pythonrun.c‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1299,8 +1299,11 @@ PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
12991299
/* only print colon if the str() of the
13001300
object is not the empty string
13011301
*/
1302-
if (s == NULL)
1302+
if (s == NULL) {
1303+
PyErr_Clear();
13031304
err = -1;
1305+
PyFile_WriteString(": <exception str() failed>", f);
1306+
}
13041307
else if (!PyString_Check(s) ||
13051308
PyString_GET_SIZE(s) != 0)
13061309
err = PyFile_WriteString(": ", f);
@@ -1309,6 +1312,9 @@ PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
13091312
Py_XDECREF(s);
13101313
}
13111314
/* try to write a newline in any case */
1315+
if (err < 0) {
1316+
PyErr_Clear();
1317+
}
13121318
err += PyFile_WriteString("\n", f);
13131319
}
13141320
Py_DECREF(value);

0 commit comments

Comments
 (0)