]> granicus.if.org Git - python/commitdiff
implement chained exception tracebacks
authorBenjamin Peterson <benjamin@python.org>
Tue, 15 Jul 2008 15:32:09 +0000 (15:32 +0000)
committerBenjamin Peterson <benjamin@python.org>
Tue, 15 Jul 2008 15:32:09 +0000 (15:32 +0000)
patch from Antoine Pitrou #3112

Include/traceback.h
Lib/test/test_raise.py
Lib/test/test_traceback.py
Lib/traceback.py
Misc/NEWS
Modules/_testcapimodule.c
Python/_warnings.c
Python/errors.c
Python/pythonrun.c
Python/traceback.c

index b77cc9202b4a6346d90376d297200dfaa20afd3d..0be3ad53b603998c9bd778dd4a00f7a63ab8b059 100644 (file)
@@ -19,7 +19,7 @@ typedef struct _traceback {
 
 PyAPI_FUNC(int) PyTraceBack_Here(struct _frame *);
 PyAPI_FUNC(int) PyTraceBack_Print(PyObject *, PyObject *);
-PyAPI_FUNC(int) Py_DisplaySourceLine(PyObject *, const char *, int);
+PyAPI_FUNC(int) Py_DisplaySourceLine(PyObject *, const char *, int, int);
 
 /* Reveal traceback type so we can typecheck traceback objects */
 PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
index 3072c14c88949894659925c50ef00b979b99d382..ba9cfc54d665f47e77812489fad6f29e23597953 100644 (file)
@@ -278,6 +278,30 @@ class TestContext(unittest.TestCase):
         else:
             self.fail("No exception raised")
 
+    def test_cycle_broken(self):
+        # Self-cycles (when re-raising a caught exception) are broken
+        try:
+            try:
+                1/0
+            except ZeroDivisionError as e:
+                raise e
+        except ZeroDivisionError as e:
+            self.failUnless(e.__context__ is None, e.__context__)
+
+    def test_reraise_cycle_broken(self):
+        # Non-trivial context cycles (through re-raising a previous exception)
+        # are broken too.
+        try:
+            try:
+                xyzzy
+            except NameError as a:
+                try:
+                    1/0
+                except ZeroDivisionError:
+                    raise a
+        except NameError as e:
+            self.failUnless(e.__context__.__context__ is None)
+
 
 class TestRemovedFunctionality(unittest.TestCase):
     def test_tuples(self):
index 3f89e6a795bb3c77c77a32901552a97f5d0a67ed..3f69e5e89352007e8c8283d8d3ce950e4fe407d9 100644 (file)
@@ -1,10 +1,11 @@
 """Test cases for traceback module"""
 
-from _testcapi import traceback_print
+from _testcapi import traceback_print, exception_print
 from io import StringIO
 import sys
 import unittest
-from test.support import run_unittest, is_jython, Error
+import re
+from test.support import run_unittest, is_jython, Error, captured_output
 
 import traceback
 
@@ -19,7 +20,7 @@ else:
     raise Error("unable to create test traceback string")
 
 
-class TracebackCases(unittest.TestCase):
+class SyntaxTracebackCases(unittest.TestCase):
     # For now, a very minimal set of tests.  I want to be sure that
     # formatting of SyntaxErrors works based on changes for 2.1.
 
@@ -99,12 +100,135 @@ class TracebackFormatTests(unittest.TestCase):
         banner, location, source_line = tb_lines
         self.assert_(banner.startswith('Traceback'))
         self.assert_(location.startswith('  File'))
-        self.assert_(source_line.startswith('raise'))
+        self.assert_(source_line.startswith('    raise'))
 
 
-def test_main():
-    run_unittest(TracebackCases, TracebackFormatTests)
+cause_message = (
+    "\nThe above exception was the direct cause "
+    "of the following exception:\n\n")
+
+context_message = (
+    "\nDuring handling of the above exception, "
+    "another exception occurred:\n\n")
+
+boundaries = re.compile(
+    '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
+
+
+class BaseExceptionReportingTests:
+
+    def get_exception(self, exception_or_callable):
+        if isinstance(exception_or_callable, Exception):
+            return exception_or_callable
+        try:
+            exception_or_callable()
+        except Exception as e:
+            return e
 
+    def zero_div(self):
+        1/0 # In zero_div
+
+    def check_zero_div(self, msg):
+        lines = msg.splitlines()
+        self.assert_(lines[-3].startswith('  File'))
+        self.assert_('1/0 # In zero_div' in lines[-2], lines[-2])
+        self.assert_(lines[-1].startswith('ZeroDivisionError'), lines[-1])
+
+    def test_simple(self):
+        try:
+            1/0 # Marker
+        except ZeroDivisionError as _:
+            e = _
+        lines = self.get_report(e).splitlines()
+        self.assertEquals(len(lines), 4)
+        self.assert_(lines[0].startswith('Traceback'))
+        self.assert_(lines[1].startswith('  File'))
+        self.assert_('1/0 # Marker' in lines[2])
+        self.assert_(lines[3].startswith('ZeroDivisionError'))
+
+    def test_cause(self):
+        def inner_raise():
+            try:
+                self.zero_div()
+            except ZeroDivisionError as e:
+                raise KeyError from e
+        def outer_raise():
+            inner_raise() # Marker
+        blocks = boundaries.split(self.get_report(outer_raise))
+        self.assertEquals(len(blocks), 3)
+        self.assertEquals(blocks[1], cause_message)
+        self.check_zero_div(blocks[0])
+        self.assert_('inner_raise() # Marker' in blocks[2])
+
+    def test_context(self):
+        def inner_raise():
+            try:
+                self.zero_div()
+            except ZeroDivisionError:
+                raise KeyError
+        def outer_raise():
+            inner_raise() # Marker
+        blocks = boundaries.split(self.get_report(outer_raise))
+        self.assertEquals(len(blocks), 3)
+        self.assertEquals(blocks[1], context_message)
+        self.check_zero_div(blocks[0])
+        self.assert_('inner_raise() # Marker' in blocks[2])
+
+    def test_cause_recursive(self):
+        def inner_raise():
+            try:
+                try:
+                    self.zero_div()
+                except ZeroDivisionError as e:
+                    z = e
+                    raise KeyError from e
+            except KeyError as e:
+                raise z from e
+        def outer_raise():
+            inner_raise() # Marker
+        blocks = boundaries.split(self.get_report(outer_raise))
+        self.assertEquals(len(blocks), 3)
+        self.assertEquals(blocks[1], cause_message)
+        # The first block is the KeyError raised from the ZeroDivisionError
+        self.assert_('raise KeyError from e' in blocks[0])
+        self.assert_('1/0' not in blocks[0])
+        # The second block (apart from the boundary) is the ZeroDivisionError
+        # re-raised from the KeyError
+        self.assert_('inner_raise() # Marker' in blocks[2])
+        self.check_zero_div(blocks[2])
+
+
+
+class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
+    #
+    # This checks reporting through the 'traceback' module, with both
+    # format_exception() and print_exception().
+    #
+
+    def get_report(self, e):
+        e = self.get_exception(e)
+        s = ''.join(
+            traceback.format_exception(type(e), e, e.__traceback__))
+        with captured_output("stderr") as sio:
+            traceback.print_exception(type(e), e, e.__traceback__)
+        self.assertEquals(sio.getvalue(), s)
+        return s
+
+
+class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
+    #
+    # This checks built-in reporting by the interpreter.
+    #
+
+    def get_report(self, e):
+        e = self.get_exception(e)
+        with captured_output("stderr") as s:
+            exception_print(e)
+        return s.getvalue()
+
+
+def test_main():
+    run_unittest(__name__)
 
 if __name__ == "__main__":
     test_main()
index fb1c5addd88189f8bd3adf62601f73e580260296..b7130d899f79ea0daee9ebad2fbab868baf6e593 100644 (file)
@@ -3,6 +3,7 @@
 import linecache
 import sys
 import types
+import itertools
 
 __all__ = ['extract_stack', 'extract_tb', 'format_exception',
            'format_exception_only', 'format_list', 'format_stack',
@@ -107,7 +108,32 @@ def extract_tb(tb, limit = None):
     return list
 
 
-def print_exception(etype, value, tb, limit=None, file=None):
+_cause_message = (
+    "\nThe above exception was the direct cause "
+    "of the following exception:\n")
+
+_context_message = (
+    "\nDuring handling of the above exception, "
+    "another exception occurred:\n")
+
+def _iter_chain(exc, custom_tb=None, seen=None):
+    if seen is None:
+        seen = set()
+    seen.add(exc)
+    its = []
+    cause = exc.__cause__
+    context = exc.__context__
+    if cause is not None and cause not in seen:
+        its.append(_iter_chain(cause, None, seen))
+        its.append([(_cause_message, None)])
+    if context is not None and context is not cause and context not in seen:
+        its.append(_iter_chain(context, None, seen))
+        its.append([(_context_message, None)])
+    its.append([(exc, custom_tb or exc.__traceback__)])
+    return itertools.chain(*its)
+
+
+def print_exception(etype, value, tb, limit=None, file=None, chain=True):
     """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
 
     This differs from print_tb() in the following ways: (1) if
@@ -120,15 +146,23 @@ def print_exception(etype, value, tb, limit=None, file=None):
     """
     if file is None:
         file = sys.stderr
-    if tb:
-        _print(file, 'Traceback (most recent call last):')
-        print_tb(tb, limit, file)
-    lines = format_exception_only(etype, value)
-    for line in lines[:-1]:
-        _print(file, line, ' ')
-    _print(file, lines[-1], '')
-
-def format_exception(etype, value, tb, limit = None):
+    if chain:
+        values = _iter_chain(value, tb)
+    else:
+        values = [(value, tb)]
+    for value, tb in values:
+        if isinstance(value, str):
+            _print(file, value)
+            continue
+        if tb:
+            _print(file, 'Traceback (most recent call last):')
+            print_tb(tb, limit, file)
+        lines = format_exception_only(type(value), value)
+        for line in lines[:-1]:
+            _print(file, line, ' ')
+        _print(file, lines[-1], '')
+
+def format_exception(etype, value, tb, limit=None, chain=True):
     """Format a stack trace and the exception information.
 
     The arguments have the same meaning as the corresponding arguments
@@ -137,12 +171,19 @@ def format_exception(etype, value, tb, limit = None):
     these lines are concatenated and printed, exactly the same text is
     printed as does print_exception().
     """
-    if tb:
-        list = ['Traceback (most recent call last):\n']
-        list = list + format_tb(tb, limit)
+    list = []
+    if chain:
+        values = _iter_chain(value, tb)
     else:
-        list = []
-    list = list + format_exception_only(etype, value)
+        values = [(value, tb)]
+    for value, tb in values:
+        if isinstance(value, str):
+            list.append(value + '\n')
+            continue
+        if tb:
+            list.append('Traceback (most recent call last):\n')
+            list.extend(format_tb(tb, limit))
+        list.extend(format_exception_only(type(value), value))
     return list
 
 def format_exception_only(etype, value):
@@ -208,33 +249,34 @@ def _some_str(value):
         return '<unprintable %s object>' % type(value).__name__
 
 
-def print_exc(limit=None, file=None):
+def print_exc(limit=None, file=None, chain=True):
     """Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
     if file is None:
         file = sys.stderr
     try:
         etype, value, tb = sys.exc_info()
-        print_exception(etype, value, tb, limit, file)
+        print_exception(etype, value, tb, limit, file, chain)
     finally:
         etype = value = tb = None
 
 
-def format_exc(limit=None):
+def format_exc(limit=None, chain=True):
     """Like print_exc() but return a string."""
     try:
         etype, value, tb = sys.exc_info()
-        return ''.join(format_exception(etype, value, tb, limit))
+        return ''.join(
+            format_exception(etype, value, tb, limit, chain))
     finally:
         etype = value = tb = None
 
 
-def print_last(limit=None, file=None):
+def print_last(limit=None, file=None, chain=True):
     """This is a shorthand for 'print_exception(sys.last_type,
     sys.last_value, sys.last_traceback, limit, file)'."""
     if file is None:
         file = sys.stderr
     print_exception(sys.last_type, sys.last_value, sys.last_traceback,
-                    limit, file)
+                    limit, file, chain)
 
 
 def print_stack(f=None, limit=None, file=None):
index e4178c098b088cecb07501259db42aa9bfe06209..a6ecc0eb0206ccc4de67c0e1fa8998e8cb19da6b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -22,6 +22,8 @@ Core and Builtins
 
 - Issue #3236: Return small longs from PyLong_FromString.
 
+- Exception tracebacks now support exception chaining.
+
 Library
 -------
 
@@ -35,6 +37,8 @@ Library
 - All the u* variant functions and methods in gettext have been renamed to their
   none u* siblings.
 
+- The traceback module has been expanded to handle chained exceptions.
+
 C API
 -----
 
index d8bb835b4ff5b790bdcbae60917ae176ad5f09e2..45494dd5c68dd27d09e5f2f685978b6c41561d4b 100644 (file)
@@ -951,6 +951,26 @@ traceback_print(PyObject *self, PyObject *args)
        Py_RETURN_NONE;
 }
 
+/* To test the format of exceptions as printed out. */
+static PyObject *
+exception_print(PyObject *self, PyObject *args)
+{
+       PyObject *value;
+       PyObject *tb;
+
+       if (!PyArg_ParseTuple(args, "O:exception_print",
+                               &value))
+               return NULL;
+
+       tb = PyException_GetTraceback(value);
+       PyErr_Display((PyObject *) Py_TYPE(value), value, tb);
+       Py_XDECREF(tb);
+
+       Py_RETURN_NONE;
+}
+
+
+
 static PyMethodDef TestMethods[] = {
        {"raise_exception",     raise_exception,                 METH_VARARGS},
        {"test_config",         (PyCFunction)test_config,        METH_NOARGS},
@@ -995,6 +1015,7 @@ static PyMethodDef TestMethods[] = {
        {"profile_int",         profile_int,                    METH_NOARGS},
 #endif
        {"traceback_print", traceback_print,             METH_VARARGS},
+       {"exception_print", exception_print,             METH_VARARGS},
        {NULL, NULL} /* sentinel */
 };
 
index 23223faa1bd655ab9075208505e642f22b38fc1e..e9384ca1f9cf935665bf436d39154b2a4d0db8ea 100644 (file)
@@ -256,7 +256,6 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyObject
     Py_XDECREF(name);
 
     /* Print "  source_line\n" */
-    PyFile_WriteString("  ", f_stderr);
     if (sourceline) {
         char *source_line_str = PyUnicode_AsString(sourceline);
         while (*source_line_str == ' ' || *source_line_str == '\t' ||
@@ -267,7 +266,7 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyObject
         PyFile_WriteString("\n", f_stderr);
     }
     else
-        Py_DisplaySourceLine(f_stderr, PyUnicode_AsString(filename), lineno);
+        Py_DisplaySourceLine(f_stderr, PyUnicode_AsString(filename), lineno, 2);
     PyErr_Clear();
 }
 
index ac64b6a2bc1edfd68b59346ebbfb763837f75406..3b86c48bfbc5d66e473b144562a8c4b54d282372 100644 (file)
@@ -84,8 +84,23 @@ PyErr_SetObject(PyObject *exception, PyObject *value)
                                return;
                        value = fixed_value;
                }
-               Py_INCREF(tstate->exc_value);
-               PyException_SetContext(value, tstate->exc_value);
+               /* Avoid reference cycles through the context chain.
+                  This is O(chain length) but context chains are
+                  usually very short. Sensitive readers may try
+                  to inline the call to PyException_GetContext. */
+               if (tstate->exc_value != value) {
+                       PyObject *o = tstate->exc_value, *context;
+                       while ((context = PyException_GetContext(o))) {
+                               Py_DECREF(context);
+                               if (context == value) {
+                                       PyException_SetContext(o, NULL);
+                                       break;
+                               }
+                               o = context;
+                       }
+                       Py_INCREF(tstate->exc_value);
+                       PyException_SetContext(value, tstate->exc_value);
+               }
        }
        if (value != NULL && PyExceptionInstance_Check(value))
                tb = PyException_GetTraceback(value);
@@ -160,6 +175,9 @@ PyErr_ExceptionMatches(PyObject *exc)
 
 /* Used in many places to normalize a raised exception, including in
    eval_code2(), do_raise(), and PyErr_Print()
+
+   XXX: should PyErr_NormalizeException() also call
+           PyException_SetTraceback() with the resulting value and tb?
 */
 void
 PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
index c46e9f4cc1713299d0a415105efe00a1581210f4..ad758a6fd973e704ae8c8c3b0949f4617121b321 100644 (file)
@@ -1242,18 +1242,19 @@ PyErr_PrintEx(int set_sys_last_vars)
        if (exception == NULL)
                return;
        PyErr_NormalizeException(&exception, &v, &tb);
+       tb = tb ? tb : Py_None;
+       PyException_SetTraceback(v, tb);
        if (exception == NULL)
                return;
         /* Now we know v != NULL too */
        if (set_sys_last_vars) {
                PySys_SetObject("last_type", exception);
                PySys_SetObject("last_value", v);
-               PySys_SetObject("last_traceback", tb ? tb : Py_None);
+               PySys_SetObject("last_traceback", tb);
        }
        hook = PySys_GetObject("excepthook");
        if (hook) {
-               PyObject *args = PyTuple_Pack(3,
-                   exception, v, tb ? tb : Py_None);
+               PyObject *args = PyTuple_Pack(3, exception, v, tb);
                PyObject *result = PyEval_CallObject(hook, args);
                if (result == NULL) {
                        PyObject *exception2, *v2, *tb2;
@@ -1293,106 +1294,100 @@ PyErr_PrintEx(int set_sys_last_vars)
        Py_XDECREF(tb);
 }
 
-void
-PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
+static void
+print_exception(PyObject *f, PyObject *value)
 {
        int err = 0;
-       PyObject *f = PySys_GetObject("stderr");
+       PyObject *type, *tb;
+
        Py_INCREF(value);
-       if (f == Py_None) {
-               /* pass */
+       fflush(stdout);
+       type = (PyObject *) Py_TYPE(value);
+       tb = PyException_GetTraceback(value);
+       if (tb && tb != Py_None)
+               err = PyTraceBack_Print(tb, f);
+       if (err == 0 &&
+               PyObject_HasAttrString(value, "print_file_and_line"))
+       {
+               PyObject *message;
+               const char *filename, *text;
+               int lineno, offset;
+               if (!parse_syntax_error(value, &message, &filename,
+                                       &lineno, &offset, &text))
+                       PyErr_Clear();
+               else {
+                       char buf[10];
+                       PyFile_WriteString("  File \"", f);
+                       if (filename == NULL)
+                               PyFile_WriteString("<string>", f);
+                       else
+                               PyFile_WriteString(filename, f);
+                       PyFile_WriteString("\", line ", f);
+                       PyOS_snprintf(buf, sizeof(buf), "%d", lineno);
+                       PyFile_WriteString(buf, f);
+                       PyFile_WriteString("\n", f);
+                       if (text != NULL)
+                               print_error_text(f, offset, text);
+                       Py_DECREF(value);
+                       value = message;
+                       /* Can't be bothered to check all those
+                          PyFile_WriteString() calls */
+                       if (PyErr_Occurred())
+                               err = -1;
+               }
        }
-       else if (f == NULL) {
-               _PyObject_Dump(value);
-               fprintf(stderr, "lost sys.stderr\n");
+       if (err) {
+               /* Don't do anything else */
        }
        else {
-               fflush(stdout);
-               if (tb && tb != Py_None)
-                       err = PyTraceBack_Print(tb, f);
-               if (err == 0 &&
-                   PyObject_HasAttrString(value, "print_file_and_line"))
-               {
-                       PyObject *message;
-                       const char *filename, *text;
-                       int lineno, offset;
-                       if (!parse_syntax_error(value, &message, &filename,
-                                               &lineno, &offset, &text))
-                               PyErr_Clear();
-                       else {
-                               char buf[10];
-                               PyFile_WriteString("  File \"", f);
-                               if (filename == NULL)
-                                       PyFile_WriteString("<string>", f);
-                               else
-                                       PyFile_WriteString(filename, f);
-                               PyFile_WriteString("\", line ", f);
-                               PyOS_snprintf(buf, sizeof(buf), "%d", lineno);
-                               PyFile_WriteString(buf, f);
-                               PyFile_WriteString("\n", f);
-                               if (text != NULL)
-                                       print_error_text(f, offset, text);
-                               Py_DECREF(value);
-                               value = message;
-                               /* Can't be bothered to check all those
-                                  PyFile_WriteString() calls */
-                               if (PyErr_Occurred())
-                                       err = -1;
-                       }
+               assert(PyExceptionClass_Check(type));
+               PyObject* moduleName;
+               char* className = PyExceptionClass_Name(type);
+               if (className != NULL) {
+                       char *dot = strrchr(className, '.');
+                       if (dot != NULL)
+                               className = dot+1;
                }
-               if (err) {
-                       /* Don't do anything else */
-               }
-               else if (PyExceptionClass_Check(exception)) {
-                       PyObject* moduleName;
-                       char* className = PyExceptionClass_Name(exception);
-                       if (className != NULL) {
-                               char *dot = strrchr(className, '.');
-                               if (dot != NULL)
-                                       className = dot+1;
-                       }
 
-                       moduleName = PyObject_GetAttrString(exception, "__module__");
-                       if (moduleName == NULL || !PyUnicode_Check(moduleName))
+               moduleName = PyObject_GetAttrString(type, "__module__");
+               if (moduleName == NULL || !PyUnicode_Check(moduleName))
+               {
+                       Py_DECREF(moduleName);
+                       err = PyFile_WriteString("<unknown>", f);
+               }
+               else {
+                       char* modstr = PyUnicode_AsString(moduleName);
+                       if (modstr && strcmp(modstr, "builtins"))
                        {
-                               Py_DECREF(moduleName);
-                               err = PyFile_WriteString("<unknown>", f);
-                       }
-                       else {
-                               char* modstr = PyUnicode_AsString(moduleName);
-                               if (modstr && strcmp(modstr, "builtins"))
-                               {
-                                       err = PyFile_WriteString(modstr, f);
-                                       err += PyFile_WriteString(".", f);
-                               }
-                               Py_DECREF(moduleName);
-                       }
-                       if (err == 0) {
-                               if (className == NULL)
-                                     err = PyFile_WriteString("<unknown>", f);
-                               else
-                                     err = PyFile_WriteString(className, f);
+                               err = PyFile_WriteString(modstr, f);
+                               err += PyFile_WriteString(".", f);
                        }
+                       Py_DECREF(moduleName);
                }
-               else
-                       err = PyFile_WriteObject(exception, f, Py_PRINT_RAW);
-               if (err == 0 && (value != Py_None)) {
-                       PyObject *s = PyObject_Str(value);
-                       /* only print colon if the str() of the
-                          object is not the empty string
-                       */
-                       if (s == NULL)
-                               err = -1;
-                       else if (!PyUnicode_Check(s) ||
-                                PyUnicode_GetSize(s) != 0)
-                               err = PyFile_WriteString(": ", f);
-                       if (err == 0)
-                         err = PyFile_WriteObject(s, f, Py_PRINT_RAW);
-                       Py_XDECREF(s);
+               if (err == 0) {
+                       if (className == NULL)
+                                 err = PyFile_WriteString("<unknown>", f);
+                       else
+                                 err = PyFile_WriteString(className, f);
                }
-               /* try to write a newline in any case */
-               err += PyFile_WriteString("\n", f);
        }
+       if (err == 0 && (value != Py_None)) {
+               PyObject *s = PyObject_Str(value);
+               /* only print colon if the str() of the
+                  object is not the empty string
+               */
+               if (s == NULL)
+                       err = -1;
+               else if (!PyUnicode_Check(s) ||
+                       PyUnicode_GetSize(s) != 0)
+                       err = PyFile_WriteString(": ", f);
+               if (err == 0)
+                 err = PyFile_WriteObject(s, f, Py_PRINT_RAW);
+               Py_XDECREF(s);
+       }
+       /* try to write a newline in any case */
+       err += PyFile_WriteString("\n", f);
+       Py_XDECREF(tb);
        Py_DECREF(value);
        /* If an error happened here, don't show it.
           XXX This is wrong, but too many callers rely on this behavior. */
@@ -1400,6 +1395,82 @@ PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
                PyErr_Clear();
 }
 
+static const char *cause_message =
+       "\nThe above exception was the direct cause "
+       "of the following exception:\n\n";
+
+static const char *context_message =
+       "\nDuring handling of the above exception, "
+       "another exception occurred:\n\n";
+
+static void
+print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
+{
+       int err = 0, res;
+       PyObject *cause, *context;
+
+       if (seen != NULL) {
+               /* Exception chaining */
+               if (PySet_Add(seen, value) == -1)
+                       PyErr_Clear();
+               else if (PyExceptionInstance_Check(value)) {
+                       cause = PyException_GetCause(value);
+                       context = PyException_GetContext(value);
+                       if (cause) {
+                               res = PySet_Contains(seen, cause);
+                               if (res == -1)
+                                       PyErr_Clear();
+                               if (res == 0) {
+                                       print_exception_recursive(
+                                               f, cause, seen);
+                                       err |= PyFile_WriteString(
+                                               cause_message, f);
+                               }
+                       }
+                       if (context) {
+                               res = PySet_Contains(seen, context);
+                               if (res == -1)
+                                       PyErr_Clear();
+                               if (res == 0) {
+                                       print_exception_recursive(
+                                               f, context, seen);
+                                       err |= PyFile_WriteString(
+                                               context_message, f);
+                               }
+                       }
+                       Py_XDECREF(context);
+                       Py_XDECREF(cause);
+               }
+       }
+       print_exception(f, value);
+       if (err != 0)
+               PyErr_Clear();
+}
+
+void
+PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
+{
+       PyObject *seen;
+       PyObject *f = PySys_GetObject("stderr");
+       if (f == Py_None) {
+               /* pass */
+       }
+       else if (f == NULL) {
+               _PyObject_Dump(value);
+               fprintf(stderr, "lost sys.stderr\n");
+       }
+       else {
+               /* We choose to ignore seen being possibly NULL, and report
+                  at least the main exception (it could be a MemoryError).
+               */
+               seen = PySet_New(NULL);
+               if (seen == NULL)
+                       PyErr_Clear();
+               print_exception_recursive(f, value, seen);
+               Py_XDECREF(seen);
+       }
+}
+
 PyObject *
 PyRun_StringFlags(const char *str, int start, PyObject *globals,
                  PyObject *locals, PyCompilerFlags *flags)
index 76e22a188da0cce6c211f49e4a85f80620f029e8..55300fc505eaef9a1f0f4447c0e3bc47efc552a4 100644 (file)
@@ -129,7 +129,7 @@ PyTraceBack_Here(PyFrameObject *frame)
 }
 
 int
-Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno)
+Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno, int indent)
 {
        int err = 0;
        FILE *xfp = NULL;
@@ -139,8 +139,6 @@ Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno)
 
        if (filename == NULL)
                return -1;
-       /* This is needed by Emacs' compile command */
-#define FMT "  File \"%.500s\", line %d, in %.500s\n"
        xfp = fopen(filename, "r" PY_STDIOTEXTMODE);
        if (xfp == NULL) {
                /* Search tail of filename in sys.path before giving up */
@@ -203,12 +201,27 @@ Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno)
                } while (*pLastChar != '\0' && *pLastChar != '\n');
        }
        if (i == lineno) {
+               char buf[11];
                char *p = linebuf;
                while (*p == ' ' || *p == '\t' || *p == '\014')
                        p++;
-                    err = PyFile_WriteString(p, f);
-                    if (err == 0 && strchr(p, '\n') == NULL)
-                            err = PyFile_WriteString("\n", f);
+
+               /* Write some spaces before the line */
+               strcpy(buf, "          ");
+               assert (strlen(buf) == 10);
+               while (indent > 0) {
+                       if(indent < 10)
+                               buf[indent] = '\0';
+                       err = PyFile_WriteString(buf, f);
+                       if (err != 0)
+                               break;
+                       indent -= 10;
+               }
+
+               if (err == 0)
+                       err = PyFile_WriteString(p, f);
+               if (err == 0 && strchr(p, '\n') == NULL)
+                       err = PyFile_WriteString("\n", f);
        }
        fclose(xfp);
        return err;
@@ -228,7 +241,7 @@ tb_displayline(PyObject *f, const char *filename, int lineno, const char *name)
        err = PyFile_WriteString(linebuf, f);
        if (err != 0)
                return err;
-        return Py_DisplaySourceLine(f, filename, lineno);
+        return Py_DisplaySourceLine(f, filename, lineno, 4);
 }
 
 static int