mirror of
https://github.com/python/cpython.git
synced 2025-11-02 19:12:55 +00:00
parent
9bab65c259
commit
e65282114e
10 changed files with 449 additions and 133 deletions
|
|
@ -19,7 +19,7 @@ typedef struct _traceback {
|
||||||
|
|
||||||
PyAPI_FUNC(int) PyTraceBack_Here(struct _frame *);
|
PyAPI_FUNC(int) PyTraceBack_Here(struct _frame *);
|
||||||
PyAPI_FUNC(int) PyTraceBack_Print(PyObject *, PyObject *);
|
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 */
|
/* Reveal traceback type so we can typecheck traceback objects */
|
||||||
PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
|
PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,30 @@ class TestContext(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.fail("No exception raised")
|
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):
|
class TestRemovedFunctionality(unittest.TestCase):
|
||||||
def test_tuples(self):
|
def test_tuples(self):
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
"""Test cases for traceback module"""
|
"""Test cases for traceback module"""
|
||||||
|
|
||||||
from _testcapi import traceback_print
|
from _testcapi import traceback_print, exception_print
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
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
|
import traceback
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ else:
|
||||||
raise Error("unable to create test traceback string")
|
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
|
# For now, a very minimal set of tests. I want to be sure that
|
||||||
# formatting of SyntaxErrors works based on changes for 2.1.
|
# formatting of SyntaxErrors works based on changes for 2.1.
|
||||||
|
|
||||||
|
|
@ -99,12 +100,135 @@ class TracebackFormatTests(unittest.TestCase):
|
||||||
banner, location, source_line = tb_lines
|
banner, location, source_line = tb_lines
|
||||||
self.assert_(banner.startswith('Traceback'))
|
self.assert_(banner.startswith('Traceback'))
|
||||||
self.assert_(location.startswith(' File'))
|
self.assert_(location.startswith(' File'))
|
||||||
self.assert_(source_line.startswith('raise'))
|
self.assert_(source_line.startswith(' raise'))
|
||||||
|
|
||||||
|
|
||||||
|
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():
|
def test_main():
|
||||||
run_unittest(TracebackCases, TracebackFormatTests)
|
run_unittest(__name__)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import linecache
|
import linecache
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
import itertools
|
||||||
|
|
||||||
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
||||||
'format_exception_only', 'format_list', 'format_stack',
|
'format_exception_only', 'format_list', 'format_stack',
|
||||||
|
|
@ -107,7 +108,32 @@ def extract_tb(tb, limit = None):
|
||||||
return list
|
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'.
|
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
|
||||||
|
|
||||||
This differs from print_tb() in the following ways: (1) if
|
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:
|
if file is None:
|
||||||
file = sys.stderr
|
file = sys.stderr
|
||||||
if tb:
|
if chain:
|
||||||
_print(file, 'Traceback (most recent call last):')
|
values = _iter_chain(value, tb)
|
||||||
print_tb(tb, limit, file)
|
else:
|
||||||
lines = format_exception_only(etype, value)
|
values = [(value, tb)]
|
||||||
for line in lines[:-1]:
|
for value, tb in values:
|
||||||
_print(file, line, ' ')
|
if isinstance(value, str):
|
||||||
_print(file, lines[-1], '')
|
_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.
|
"""Format a stack trace and the exception information.
|
||||||
|
|
||||||
The arguments have the same meaning as the corresponding arguments
|
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
|
these lines are concatenated and printed, exactly the same text is
|
||||||
printed as does print_exception().
|
printed as does print_exception().
|
||||||
"""
|
"""
|
||||||
if tb:
|
list = []
|
||||||
list = ['Traceback (most recent call last):\n']
|
if chain:
|
||||||
list = list + format_tb(tb, limit)
|
values = _iter_chain(value, tb)
|
||||||
else:
|
else:
|
||||||
list = []
|
values = [(value, tb)]
|
||||||
list = list + format_exception_only(etype, value)
|
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
|
return list
|
||||||
|
|
||||||
def format_exception_only(etype, value):
|
def format_exception_only(etype, value):
|
||||||
|
|
@ -208,33 +249,34 @@ def _some_str(value):
|
||||||
return '<unprintable %s object>' % type(value).__name__
|
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)'."""
|
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
|
||||||
if file is None:
|
if file is None:
|
||||||
file = sys.stderr
|
file = sys.stderr
|
||||||
try:
|
try:
|
||||||
etype, value, tb = sys.exc_info()
|
etype, value, tb = sys.exc_info()
|
||||||
print_exception(etype, value, tb, limit, file)
|
print_exception(etype, value, tb, limit, file, chain)
|
||||||
finally:
|
finally:
|
||||||
etype = value = tb = None
|
etype = value = tb = None
|
||||||
|
|
||||||
|
|
||||||
def format_exc(limit=None):
|
def format_exc(limit=None, chain=True):
|
||||||
"""Like print_exc() but return a string."""
|
"""Like print_exc() but return a string."""
|
||||||
try:
|
try:
|
||||||
etype, value, tb = sys.exc_info()
|
etype, value, tb = sys.exc_info()
|
||||||
return ''.join(format_exception(etype, value, tb, limit))
|
return ''.join(
|
||||||
|
format_exception(etype, value, tb, limit, chain))
|
||||||
finally:
|
finally:
|
||||||
etype = value = tb = None
|
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,
|
"""This is a shorthand for 'print_exception(sys.last_type,
|
||||||
sys.last_value, sys.last_traceback, limit, file)'."""
|
sys.last_value, sys.last_traceback, limit, file)'."""
|
||||||
if file is None:
|
if file is None:
|
||||||
file = sys.stderr
|
file = sys.stderr
|
||||||
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
|
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):
|
def print_stack(f=None, limit=None, file=None):
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ Core and Builtins
|
||||||
|
|
||||||
- Issue #3236: Return small longs from PyLong_FromString.
|
- Issue #3236: Return small longs from PyLong_FromString.
|
||||||
|
|
||||||
|
- Exception tracebacks now support exception chaining.
|
||||||
|
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
@ -35,6 +37,8 @@ Library
|
||||||
- All the u* variant functions and methods in gettext have been renamed to their
|
- All the u* variant functions and methods in gettext have been renamed to their
|
||||||
none u* siblings.
|
none u* siblings.
|
||||||
|
|
||||||
|
- The traceback module has been expanded to handle chained exceptions.
|
||||||
|
|
||||||
C API
|
C API
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -951,6 +951,26 @@ traceback_print(PyObject *self, PyObject *args)
|
||||||
Py_RETURN_NONE;
|
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[] = {
|
static PyMethodDef TestMethods[] = {
|
||||||
{"raise_exception", raise_exception, METH_VARARGS},
|
{"raise_exception", raise_exception, METH_VARARGS},
|
||||||
{"test_config", (PyCFunction)test_config, METH_NOARGS},
|
{"test_config", (PyCFunction)test_config, METH_NOARGS},
|
||||||
|
|
@ -995,6 +1015,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"profile_int", profile_int, METH_NOARGS},
|
{"profile_int", profile_int, METH_NOARGS},
|
||||||
#endif
|
#endif
|
||||||
{"traceback_print", traceback_print, METH_VARARGS},
|
{"traceback_print", traceback_print, METH_VARARGS},
|
||||||
|
{"exception_print", exception_print, METH_VARARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,6 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyObject
|
||||||
Py_XDECREF(name);
|
Py_XDECREF(name);
|
||||||
|
|
||||||
/* Print " source_line\n" */
|
/* Print " source_line\n" */
|
||||||
PyFile_WriteString(" ", f_stderr);
|
|
||||||
if (sourceline) {
|
if (sourceline) {
|
||||||
char *source_line_str = PyUnicode_AsString(sourceline);
|
char *source_line_str = PyUnicode_AsString(sourceline);
|
||||||
while (*source_line_str == ' ' || *source_line_str == '\t' ||
|
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);
|
PyFile_WriteString("\n", f_stderr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Py_DisplaySourceLine(f_stderr, PyUnicode_AsString(filename), lineno);
|
Py_DisplaySourceLine(f_stderr, PyUnicode_AsString(filename), lineno, 2);
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,23 @@ PyErr_SetObject(PyObject *exception, PyObject *value)
|
||||||
return;
|
return;
|
||||||
value = fixed_value;
|
value = fixed_value;
|
||||||
}
|
}
|
||||||
Py_INCREF(tstate->exc_value);
|
/* Avoid reference cycles through the context chain.
|
||||||
PyException_SetContext(value, tstate->exc_value);
|
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))
|
if (value != NULL && PyExceptionInstance_Check(value))
|
||||||
tb = PyException_GetTraceback(value);
|
tb = PyException_GetTraceback(value);
|
||||||
|
|
@ -160,6 +175,9 @@ PyErr_ExceptionMatches(PyObject *exc)
|
||||||
|
|
||||||
/* Used in many places to normalize a raised exception, including in
|
/* Used in many places to normalize a raised exception, including in
|
||||||
eval_code2(), do_raise(), and PyErr_Print()
|
eval_code2(), do_raise(), and PyErr_Print()
|
||||||
|
|
||||||
|
XXX: should PyErr_NormalizeException() also call
|
||||||
|
PyException_SetTraceback() with the resulting value and tb?
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
|
PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
|
||||||
|
|
|
||||||
|
|
@ -1242,18 +1242,19 @@ PyErr_PrintEx(int set_sys_last_vars)
|
||||||
if (exception == NULL)
|
if (exception == NULL)
|
||||||
return;
|
return;
|
||||||
PyErr_NormalizeException(&exception, &v, &tb);
|
PyErr_NormalizeException(&exception, &v, &tb);
|
||||||
|
tb = tb ? tb : Py_None;
|
||||||
|
PyException_SetTraceback(v, tb);
|
||||||
if (exception == NULL)
|
if (exception == NULL)
|
||||||
return;
|
return;
|
||||||
/* Now we know v != NULL too */
|
/* Now we know v != NULL too */
|
||||||
if (set_sys_last_vars) {
|
if (set_sys_last_vars) {
|
||||||
PySys_SetObject("last_type", exception);
|
PySys_SetObject("last_type", exception);
|
||||||
PySys_SetObject("last_value", v);
|
PySys_SetObject("last_value", v);
|
||||||
PySys_SetObject("last_traceback", tb ? tb : Py_None);
|
PySys_SetObject("last_traceback", tb);
|
||||||
}
|
}
|
||||||
hook = PySys_GetObject("excepthook");
|
hook = PySys_GetObject("excepthook");
|
||||||
if (hook) {
|
if (hook) {
|
||||||
PyObject *args = PyTuple_Pack(3,
|
PyObject *args = PyTuple_Pack(3, exception, v, tb);
|
||||||
exception, v, tb ? tb : Py_None);
|
|
||||||
PyObject *result = PyEval_CallObject(hook, args);
|
PyObject *result = PyEval_CallObject(hook, args);
|
||||||
if (result == NULL) {
|
if (result == NULL) {
|
||||||
PyObject *exception2, *v2, *tb2;
|
PyObject *exception2, *v2, *tb2;
|
||||||
|
|
@ -1293,12 +1294,164 @@ PyErr_PrintEx(int set_sys_last_vars)
|
||||||
Py_XDECREF(tb);
|
Py_XDECREF(tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_exception(PyObject *f, PyObject *value)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
PyObject *type, *tb;
|
||||||
|
|
||||||
|
Py_INCREF(value);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
/* Don't do anything else */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(PyExceptionClass_Check(type));
|
||||||
|
PyObject* moduleName;
|
||||||
|
char* className = PyExceptionClass_Name(type);
|
||||||
|
if (className != NULL) {
|
||||||
|
char *dot = strrchr(className, '.');
|
||||||
|
if (dot != NULL)
|
||||||
|
className = dot+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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. */
|
||||||
|
if (err != 0)
|
||||||
|
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
|
void
|
||||||
PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
|
PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
|
||||||
{
|
{
|
||||||
int err = 0;
|
PyObject *seen;
|
||||||
PyObject *f = PySys_GetObject("stderr");
|
PyObject *f = PySys_GetObject("stderr");
|
||||||
Py_INCREF(value);
|
|
||||||
if (f == Py_None) {
|
if (f == Py_None) {
|
||||||
/* pass */
|
/* pass */
|
||||||
}
|
}
|
||||||
|
|
@ -1307,97 +1460,15 @@ PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
|
||||||
fprintf(stderr, "lost sys.stderr\n");
|
fprintf(stderr, "lost sys.stderr\n");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fflush(stdout);
|
/* We choose to ignore seen being possibly NULL, and report
|
||||||
if (tb && tb != Py_None)
|
at least the main exception (it could be a MemoryError).
|
||||||
err = PyTraceBack_Print(tb, f);
|
*/
|
||||||
if (err == 0 &&
|
seen = PySet_New(NULL);
|
||||||
PyObject_HasAttrString(value, "print_file_and_line"))
|
if (seen == NULL)
|
||||||
{
|
PyErr_Clear();
|
||||||
PyObject *message;
|
print_exception_recursive(f, value, seen);
|
||||||
const char *filename, *text;
|
Py_XDECREF(seen);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
/* try to write a newline in any case */
|
|
||||||
err += PyFile_WriteString("\n", f);
|
|
||||||
}
|
}
|
||||||
Py_DECREF(value);
|
|
||||||
/* If an error happened here, don't show it.
|
|
||||||
XXX This is wrong, but too many callers rely on this behavior. */
|
|
||||||
if (err != 0)
|
|
||||||
PyErr_Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ PyTraceBack_Here(PyFrameObject *frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno)
|
Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno, int indent)
|
||||||
{
|
{
|
||||||
int err = 0;
|
int err = 0;
|
||||||
FILE *xfp = NULL;
|
FILE *xfp = NULL;
|
||||||
|
|
@ -139,8 +139,6 @@ Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno)
|
||||||
|
|
||||||
if (filename == NULL)
|
if (filename == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
/* This is needed by Emacs' compile command */
|
|
||||||
#define FMT " File \"%.500s\", line %d, in %.500s\n"
|
|
||||||
xfp = fopen(filename, "r" PY_STDIOTEXTMODE);
|
xfp = fopen(filename, "r" PY_STDIOTEXTMODE);
|
||||||
if (xfp == NULL) {
|
if (xfp == NULL) {
|
||||||
/* Search tail of filename in sys.path before giving up */
|
/* 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');
|
} while (*pLastChar != '\0' && *pLastChar != '\n');
|
||||||
}
|
}
|
||||||
if (i == lineno) {
|
if (i == lineno) {
|
||||||
|
char buf[11];
|
||||||
char *p = linebuf;
|
char *p = linebuf;
|
||||||
while (*p == ' ' || *p == '\t' || *p == '\014')
|
while (*p == ' ' || *p == '\t' || *p == '\014')
|
||||||
p++;
|
p++;
|
||||||
err = PyFile_WriteString(p, f);
|
|
||||||
if (err == 0 && strchr(p, '\n') == NULL)
|
/* Write some spaces before the line */
|
||||||
err = PyFile_WriteString("\n", f);
|
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);
|
fclose(xfp);
|
||||||
return err;
|
return err;
|
||||||
|
|
@ -228,7 +241,7 @@ tb_displayline(PyObject *f, const char *filename, int lineno, const char *name)
|
||||||
err = PyFile_WriteString(linebuf, f);
|
err = PyFile_WriteString(linebuf, f);
|
||||||
if (err != 0)
|
if (err != 0)
|
||||||
return err;
|
return err;
|
||||||
return Py_DisplaySourceLine(f, filename, lineno);
|
return Py_DisplaySourceLine(f, filename, lineno, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue