[Python-3000-checkins] r64965 - in python/branches/py3k: 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

benjamin.peterson python-3000-checkins at python.org
Tue Jul 15 17:32:09 CEST 2008


Author: benjamin.peterson
Date: Tue Jul 15 17:32:09 2008
New Revision: 64965

Log:
implement chained exception tracebacks

patch from Antoine Pitrou #3112


Modified:
   python/branches/py3k/Include/traceback.h
   python/branches/py3k/Lib/test/test_raise.py
   python/branches/py3k/Lib/test/test_traceback.py
   python/branches/py3k/Lib/traceback.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Modules/_testcapimodule.c
   python/branches/py3k/Python/_warnings.c
   python/branches/py3k/Python/errors.c
   python/branches/py3k/Python/pythonrun.c
   python/branches/py3k/Python/traceback.c

Modified: python/branches/py3k/Include/traceback.h
==============================================================================
--- python/branches/py3k/Include/traceback.h	(original)
+++ python/branches/py3k/Include/traceback.h	Tue Jul 15 17:32:09 2008
@@ -19,7 +19,7 @@
 
 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;

Modified: python/branches/py3k/Lib/test/test_raise.py
==============================================================================
--- python/branches/py3k/Lib/test/test_raise.py	(original)
+++ python/branches/py3k/Lib/test/test_raise.py	Tue Jul 15 17:32:09 2008
@@ -278,6 +278,30 @@
         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):

Modified: python/branches/py3k/Lib/test/test_traceback.py
==============================================================================
--- python/branches/py3k/Lib/test/test_traceback.py	(original)
+++ python/branches/py3k/Lib/test/test_traceback.py	Tue Jul 15 17:32:09 2008
@@ -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 @@
     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 @@
         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()

Modified: python/branches/py3k/Lib/traceback.py
==============================================================================
--- python/branches/py3k/Lib/traceback.py	(original)
+++ python/branches/py3k/Lib/traceback.py	Tue Jul 15 17:32:09 2008
@@ -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 @@
     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 @@
     """
     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], '')
+    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):
+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 @@
     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 @@
         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):

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Tue Jul 15 17:32:09 2008
@@ -22,6 +22,8 @@
 
 - Issue #3236: Return small longs from PyLong_FromString.
 
+- Exception tracebacks now support exception chaining.
+
 Library
 -------
 
@@ -35,6 +37,8 @@
 - 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
 -----
 

Modified: python/branches/py3k/Modules/_testcapimodule.c
==============================================================================
--- python/branches/py3k/Modules/_testcapimodule.c	(original)
+++ python/branches/py3k/Modules/_testcapimodule.c	Tue Jul 15 17:32:09 2008
@@ -951,6 +951,26 @@
 	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 @@
 	{"profile_int",		profile_int,			METH_NOARGS},
 #endif
 	{"traceback_print", traceback_print, 	         METH_VARARGS},
+	{"exception_print", exception_print, 	         METH_VARARGS},
 	{NULL, NULL} /* sentinel */
 };
 

Modified: python/branches/py3k/Python/_warnings.c
==============================================================================
--- python/branches/py3k/Python/_warnings.c	(original)
+++ python/branches/py3k/Python/_warnings.c	Tue Jul 15 17:32:09 2008
@@ -256,7 +256,6 @@
     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 @@
         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();
 }
 

Modified: python/branches/py3k/Python/errors.c
==============================================================================
--- python/branches/py3k/Python/errors.c	(original)
+++ python/branches/py3k/Python/errors.c	Tue Jul 15 17:32:09 2008
@@ -84,8 +84,23 @@
 				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 @@
 
 /* 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)

Modified: python/branches/py3k/Python/pythonrun.c
==============================================================================
--- python/branches/py3k/Python/pythonrun.c	(original)
+++ python/branches/py3k/Python/pythonrun.c	Tue Jul 15 17:32:09 2008
@@ -1242,18 +1242,19 @@
 	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 @@
 	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_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)

Modified: python/branches/py3k/Python/traceback.c
==============================================================================
--- python/branches/py3k/Python/traceback.c	(original)
+++ python/branches/py3k/Python/traceback.c	Tue Jul 15 17:32:09 2008
@@ -129,7 +129,7 @@
 }
 
 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 @@
 
 	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 @@
 		} 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 @@
 	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


More information about the Python-3000-checkins mailing list