summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Peterson <benjamin@python.org>2008-07-15 15:32:09 (GMT)
committerBenjamin Peterson <benjamin@python.org>2008-07-15 15:32:09 (GMT)
commite65282114e96efb9e7eee77c57244943b746f6fe (patch)
tree9daa571b35e287058d54b39bffbc6ca89a843f65
parent9bab65c25928617b2911d2f42d01e5543893ed93 (diff)
downloadcpython-e65282114e96efb9e7eee77c57244943b746f6fe.zip
cpython-e65282114e96efb9e7eee77c57244943b746f6fe.tar.gz
cpython-e65282114e96efb9e7eee77c57244943b746f6fe.tar.bz2
implement chained exception tracebacks
patch from Antoine Pitrou #3112
-rw-r--r--Include/traceback.h2
-rw-r--r--Lib/test/test_raise.py24
-rw-r--r--Lib/test/test_traceback.py136
-rw-r--r--Lib/traceback.py84
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/_testcapimodule.c21
-rw-r--r--Python/_warnings.c3
-rw-r--r--Python/errors.c22
-rw-r--r--Python/pythonrun.c251
-rw-r--r--Python/traceback.c27
10 files changed, 445 insertions, 129 deletions
diff --git a/Include/traceback.h b/Include/traceback.h
index b77cc92..0be3ad5 100644
--- a/Include/traceback.h
+++ b/Include/traceback.h
@@ -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;
diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py
index 3072c14..ba9cfc5 100644
--- a/Lib/test/test_raise.py
+++ b/Lib/test/test_raise.py
@@ -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):
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 3f89e6a..3f69e5e 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -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()
diff --git a/Lib/traceback.py b/Lib/traceback.py
index fb1c5ad..b7130d8 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -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):
diff --git a/Misc/NEWS b/Misc/NEWS
index e4178c0..a6ecc0e 100644
--- 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
-----
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index d8bb835..45494dd 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -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 */
};
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 23223fa..e9384ca 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -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();
}
diff --git a/Python/errors.c b/Python/errors.c
index ac64b6a..3b86c48 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -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)
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index c46e9f4..ad758a6 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -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)
diff --git a/Python/traceback.c b/Python/traceback.c
index 76e22a1..55300fc 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -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