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;
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):
"""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
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.
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()
import linecache
import sys
import types
+import itertools
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
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
"""
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
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):
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):
- Issue #3236: Return small longs from PyLong_FromString.
+- Exception tracebacks now support exception chaining.
+
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
-----
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},
{"profile_int", profile_int, METH_NOARGS},
#endif
{"traceback_print", traceback_print, METH_VARARGS},
+ {"exception_print", exception_print, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
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' ||
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();
}
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);
/* 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)
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;
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. */
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)
}
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;
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 */
} 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;
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