# HG changeset patch # Parent 4e84e45e191b3b063387fdd68075f47f4576e215 Issue #22836: Keep exception reports sensible despite errors diff -r 4e84e45e191b Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Fri Dec 19 11:21:56 2014 -0500 +++ b/Doc/c-api/exceptions.rst Sat Dec 20 04:17:36 2014 +0000 @@ -74,8 +74,8 @@ :meth:`__del__` method. The function is called with a single argument *obj* that identifies the context - in which the unraisable exception occurred. The repr of *obj* will be printed in - the warning message. + in which the unraisable exception occurred. If possible, + the repr of *obj* will be printed in the warning message. Raising exceptions diff -r 4e84e45e191b Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Fri Dec 19 11:21:56 2014 -0500 +++ b/Lib/test/test_exceptions.py Sat Dec 20 04:17:36 2014 +0000 @@ -7,9 +7,10 @@ import weakref import errno -from test.support import (TESTFN, captured_output, check_impl_detail, +from test.support import (TESTFN, captured_stderr, check_impl_detail, check_warnings, cpython_only, gc_collect, run_unittest, no_tracing, unlink) +from test.script_helper import assert_python_failure class NaiveException(Exception): def __init__(self, x): @@ -816,7 +817,7 @@ class MyException(Exception, metaclass=Meta): pass - with captured_output("stderr") as stderr: + with captured_stderr() as stderr: try: raise KeyError() except MyException as e: @@ -945,6 +946,66 @@ os.listdir(__file__) self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception) + def test_unraisable(self): + '''Check for sensible reports from PyErr_WriteUnraisable() (Issue + 22836)''' + class BrokenDel: + def __del__(self): + raise Exception("del is broken") + class BrokenRepr(BrokenDel): + def __repr__(self): + raise Exception("repr() is broken") + class BrokenException(Exception): + def __str__(self): + raise BrokenException("Exception is broken") + class BrokenExceptionDel: + def __del__(self): + raise BrokenException() + for test_class in (BrokenDel, BrokenRepr, BrokenExceptionDel): + with self.subTest(test_class): + obj = test_class() + with captured_stderr() as stderr: + del obj + stderr = stderr.getvalue() + self.assertIn("Exception ignored", stderr) + if test_class is not BrokenRepr: + self.assertIn(test_class.__del__.__qualname__, stderr) + self.assertIn("raise", stderr) + if test_class is BrokenExceptionDel: + self.assertIn("BrokenException:", stderr) + else: + self.assertIn("Exception: del is broken", stderr) + self.assertTrue(stderr.endswith("\n")) + + def test_unhandled(self): + '''Check for sensible reporting of unhandled exceptions''' + tests = ( + dict( + desc="Working exception", + script=r'''raise Exception("dummy message")''', + details=(b"line", b"Exception: dummy message"), + ), + dict( + desc="Broken exception", + script= + '''class BrokenException(Exception):\n''' + ''' def __str__(self):\n''' + ''' raise Exception("Exception is broken")\n''' + '''raise BrokenException()\n''', + details=(b"line", b"BrokenException:"), + ), + ) + for test in tests: + with self.subTest(test["desc"]): + # Avoid conflicting with potential debug output on stderr + script = '''import sys; sys.stderr = sys.stdout\n''' + script += test["script"] + [rc, report, _] = assert_python_failure("-c", script) + self.assertEqual(1, rc) + for detail in test["details"]: + self.assertIn(detail, report) + self.assertTrue(report.endswith(b"\n")) + class ImportErrorTests(unittest.TestCase): diff -r 4e84e45e191b Python/errors.c --- a/Python/errors.c Fri Dec 19 11:21:56 2014 -0500 +++ b/Python/errors.c Sat Dec 20 04:17:36 2014 +0000 @@ -910,8 +910,12 @@ if (obj) { if (PyFile_WriteString("Exception ignored in: ", f) < 0) goto done; - if (PyFile_WriteObject(obj, f, 0) < 0) - goto done; + if (PyFile_WriteObject(obj, f, 0) < 0) { + PyErr_Clear(); + if (PyFile_WriteString("", f) < 0) { + goto done; + } + } if (PyFile_WriteString("\n", f) < 0) goto done; } @@ -956,8 +960,12 @@ if (v && v != Py_None) { if (PyFile_WriteString(": ", f) < 0) goto done; - if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0) - goto done; + if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0) { + PyErr_Clear(); + if (PyFile_WriteString("", f) < 0) { + goto done; + } + } } if (PyFile_WriteString("\n", f) < 0) goto done; diff -r 4e84e45e191b Python/pythonrun.c --- a/Python/pythonrun.c Fri Dec 19 11:21:56 2014 -0500 +++ b/Python/pythonrun.c Sat Dec 20 04:17:36 2014 +0000 @@ -764,8 +764,11 @@ /* only print colon if the str() of the object is not the empty string */ - if (s == NULL) + if (s == NULL) { + PyErr_Clear(); err = -1; + err += PyFile_WriteString(": ", f); + } else if (!PyUnicode_Check(s) || PyUnicode_GetLength(s) != 0) err = PyFile_WriteString(": ", f); @@ -774,6 +777,9 @@ Py_XDECREF(s); } /* try to write a newline in any case */ + if (err < 0) { + PyErr_Clear(); + } err += PyFile_WriteString("\n", f); Py_XDECREF(tb); Py_DECREF(value);