changeset: 100359:fca9f02e10e5 branch: 2.7 user: Martin Panter date: Sun Feb 28 00:18:43 2016 +0000 files: Doc/c-api/exceptions.rst Lib/test/test_exceptions.py Misc/NEWS Python/errors.c Python/pythonrun.c description: Issue #22836: Keep exception reports sensible despite errors diff -r 20b5d153b894 -r fca9f02e10e5 Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Sat Feb 27 08:39:36 2016 +0200 +++ b/Doc/c-api/exceptions.rst Sun Feb 28 00:18:43 2016 +0000 @@ -404,8 +404,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. .. _unicodeexceptions: diff -r 20b5d153b894 -r fca9f02e10e5 Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Sat Feb 27 08:39:36 2016 +0200 +++ b/Lib/test/test_exceptions.py Sun Feb 28 00:18:43 2016 +0000 @@ -5,10 +5,15 @@ import unittest import pickle, cPickle -from test.test_support import (TESTFN, unlink, run_unittest, captured_output, +from test.test_support import (TESTFN, unlink, run_unittest, captured_stderr, check_warnings, cpython_only) from test.test_pep352 import ignore_deprecation_warnings +class BrokenStrException(Exception): + def __str__(self): + raise Exception("str() is broken") + __repr__ = __str__ # Python 2's PyErr_WriteUnraisable() uses repr() + # XXX This is not really enough, each *operation* should be tested! class ExceptionTests(unittest.TestCase): @@ -375,7 +380,7 @@ # The test prints an unraisable recursion error when # doing "except ValueError", this is because subclass # checking has recursion checking too. - with captured_output("stderr"): + with captured_stderr(): try: g() except RuntimeError: @@ -448,7 +453,7 @@ __metaclass__ = Meta pass - with captured_output("stderr") as stderr: + with captured_stderr() as stderr: try: raise KeyError() except MyException, e: @@ -460,7 +465,7 @@ else: self.fail("Should have raised KeyError") - with captured_output("stderr") as stderr: + with captured_stderr() as stderr: def g(): try: return g() @@ -644,6 +649,62 @@ self.assertEqual(error5.a, 1) self.assertEqual(error5.__doc__, "") + def test_unraisable(self): + # Issue #22836: PyErr_WriteUnraisable() should give sensible reports + class BrokenDel: + def __del__(self): + exc = ValueError("del is broken") + # In Python 3, the following line would be in the report: + raise exc + + class BrokenRepr(BrokenDel): + def __repr__(self): + raise AttributeError("repr() is broken") + + class BrokenExceptionDel: + def __del__(self): + exc = BrokenStrException() + # In Python 3, the following line would be in the report: + raise exc + + for test_class in (BrokenDel, BrokenRepr, BrokenExceptionDel): + obj = test_class() + with captured_stderr() as stderr: + del obj + report = stderr.getvalue() + self.assertRegexpMatches(report, "Exception.* ignored") + if test_class is BrokenRepr: + self.assertIn("", report) + else: + self.assertIn("__del__", report) + if test_class is BrokenExceptionDel: + self.assertIn("BrokenStrException", report) + self.assertIn("", report) + else: + self.assertIn("ValueError", report) + self.assertIn("del is broken", report) + self.assertTrue(report.endswith("\n")) + + def test_unhandled(self): + # Check for sensible reporting of unhandled exceptions + for exc_type in (ValueError, BrokenStrException): + try: + exc = exc_type("test message") + # The following line is included in the traceback report: + raise exc + except exc_type: + with captured_stderr() as stderr: + sys.__excepthook__(*sys.exc_info()) + report = stderr.getvalue() + self.assertIn("test_exceptions.py", report) + self.assertIn("raise exc", report) + self.assertIn(exc_type.__name__, report) + if exc_type is BrokenStrException: + self.assertIn("", report) + else: + self.assertIn("test message", report) + self.assertTrue(report.endswith("\n")) + def test_main(): run_unittest(ExceptionTests, TestSameStrAndUnicodeMsg) diff -r 20b5d153b894 -r fca9f02e10e5 Misc/NEWS --- a/Misc/NEWS Sat Feb 27 08:39:36 2016 +0200 +++ b/Misc/NEWS Sun Feb 28 00:18:43 2016 +0000 @@ -10,6 +10,11 @@ Core and Builtins ----------------- +- Issue #22836: Ensure exception reports from PyErr_Display() and + PyErr_WriteUnraisable() are sensible even when formatting them produces + secondary errors. This affects the reports produced by + sys.__excepthook__() and when __del__() raises an exception. + - Issue #22847: Improve method cache efficiency. - Issue #25843: When compiling code, don't merge constants if they are equal diff -r 20b5d153b894 -r fca9f02e10e5 Python/errors.c --- a/Python/errors.c Sat Feb 27 08:39:36 2016 +0200 +++ b/Python/errors.c Sun Feb 28 00:18:43 2016 +0000 @@ -696,12 +696,18 @@ PyFile_WriteString(className, f); if (v && v != Py_None) { PyFile_WriteString(": ", f); - PyFile_WriteObject(v, f, 0); + if (PyFile_WriteObject(v, f, 0) < 0) { + PyErr_Clear(); + PyFile_WriteString("", f); + } } Py_XDECREF(moduleName); } PyFile_WriteString(" in ", f); - PyFile_WriteObject(obj, f, 0); + if (PyFile_WriteObject(obj, f, 0) < 0) { + PyErr_Clear(); + PyFile_WriteString("", f); + } PyFile_WriteString(" ignored\n", f); PyErr_Clear(); /* Just in case */ } diff -r 20b5d153b894 -r fca9f02e10e5 Python/pythonrun.c --- a/Python/pythonrun.c Sat Feb 27 08:39:36 2016 +0200 +++ b/Python/pythonrun.c Sun Feb 28 00:18:43 2016 +0000 @@ -1299,8 +1299,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; + PyFile_WriteString(": ", f); + } else if (!PyString_Check(s) || PyString_GET_SIZE(s) != 0) err = PyFile_WriteString(": ", f); @@ -1309,6 +1312,9 @@ Py_XDECREF(s); } /* try to write a newline in any case */ + if (err < 0) { + PyErr_Clear(); + } err += PyFile_WriteString("\n", f); } Py_DECREF(value);