benjamin.peterson
2008-07-15 15:32:09 UTC
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
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