mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
bpo-30579: Allow TracebackType creation and tb_next mutation from Python (GH-4793)
Third party projects may wish to hide their own internal machinery in order to present more comprehensible tracebacks to end users (e.g. Jinja2 and Trio both do this). Previously such projects have had to rely on ctypes to do so:fe3dadacdf/jinja2/debug.py (L345)
1e86b1aee8/trio/_core/_multierror.py (L296)
This provides a Python level API for creating and modifying real Traceback objects, allowing tracebacks to be edited at runtime. Patch by Nathaniel Smith.
This commit is contained in:
parent
d327ae6ba1
commit
e46a8af450
4 changed files with 226 additions and 26 deletions
|
@ -27,6 +27,65 @@ _Py_IDENTIFIER(close);
|
|||
_Py_IDENTIFIER(open);
|
||||
_Py_IDENTIFIER(path);
|
||||
|
||||
/*[clinic input]
|
||||
class TracebackType "PyTracebackObject *" "&PyTraceback_Type"
|
||||
[clinic start generated code]*/
|
||||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=928fa06c10151120]*/
|
||||
|
||||
#include "clinic/traceback.c.h"
|
||||
|
||||
static PyObject *
|
||||
tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti,
|
||||
int lineno)
|
||||
{
|
||||
PyTracebackObject *tb;
|
||||
if ((next != NULL && !PyTraceBack_Check(next)) ||
|
||||
frame == NULL || !PyFrame_Check(frame)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type);
|
||||
if (tb != NULL) {
|
||||
Py_XINCREF(next);
|
||||
tb->tb_next = next;
|
||||
Py_XINCREF(frame);
|
||||
tb->tb_frame = frame;
|
||||
tb->tb_lasti = lasti;
|
||||
tb->tb_lineno = lineno;
|
||||
PyObject_GC_Track(tb);
|
||||
}
|
||||
return (PyObject *)tb;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
@classmethod
|
||||
TracebackType.__new__ as tb_new
|
||||
|
||||
tb_next: object
|
||||
tb_frame: object(type='PyFrameObject *', subclass_of='&PyFrame_Type')
|
||||
tb_lasti: int
|
||||
tb_lineno: int
|
||||
|
||||
Create a new traceback object.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
tb_new_impl(PyTypeObject *type, PyObject *tb_next, PyFrameObject *tb_frame,
|
||||
int tb_lasti, int tb_lineno)
|
||||
/*[clinic end generated code: output=fa077debd72d861a input=01cbe8ec8783fca7]*/
|
||||
{
|
||||
if (tb_next == Py_None) {
|
||||
tb_next = NULL;
|
||||
} else if (!PyTraceBack_Check(tb_next)) {
|
||||
return PyErr_Format(PyExc_TypeError,
|
||||
"expected traceback object or None, got '%s'",
|
||||
Py_TYPE(tb_next)->tp_name);
|
||||
}
|
||||
|
||||
return tb_create_raw((PyTracebackObject *)tb_next, tb_frame, tb_lasti,
|
||||
tb_lineno);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
tb_dir(PyTracebackObject *self)
|
||||
{
|
||||
|
@ -34,19 +93,72 @@ tb_dir(PyTracebackObject *self)
|
|||
"tb_lasti", "tb_lineno");
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
tb_next_get(PyTracebackObject *self, void *Py_UNUSED(_))
|
||||
{
|
||||
PyObject* ret = (PyObject*)self->tb_next;
|
||||
if (!ret) {
|
||||
ret = Py_None;
|
||||
}
|
||||
Py_INCREF(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
tb_next_set(PyTracebackObject *self, PyObject *new_next, void *Py_UNUSED(_))
|
||||
{
|
||||
if (!new_next) {
|
||||
PyErr_Format(PyExc_TypeError, "can't delete tb_next attribute");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We accept None or a traceback object, and map None -> NULL (inverse of
|
||||
tb_next_get) */
|
||||
if (new_next == Py_None) {
|
||||
new_next = NULL;
|
||||
} else if (!PyTraceBack_Check(new_next)) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"expected traceback object, got '%s'",
|
||||
Py_TYPE(new_next)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check for loops */
|
||||
PyTracebackObject *cursor = (PyTracebackObject *)new_next;
|
||||
while (cursor) {
|
||||
if (cursor == self) {
|
||||
PyErr_Format(PyExc_ValueError, "traceback loop detected");
|
||||
return -1;
|
||||
}
|
||||
cursor = cursor->tb_next;
|
||||
}
|
||||
|
||||
PyObject *old_next = (PyObject*)self->tb_next;
|
||||
Py_XINCREF(new_next);
|
||||
self->tb_next = (PyTracebackObject *)new_next;
|
||||
Py_XDECREF(old_next);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef tb_methods[] = {
|
||||
{"__dir__", (PyCFunction)tb_dir, METH_NOARGS},
|
||||
{NULL, NULL, 0, NULL},
|
||||
};
|
||||
|
||||
static PyMemberDef tb_memberlist[] = {
|
||||
{"tb_next", T_OBJECT, OFF(tb_next), READONLY},
|
||||
{"tb_frame", T_OBJECT, OFF(tb_frame), READONLY},
|
||||
{"tb_lasti", T_INT, OFF(tb_lasti), READONLY},
|
||||
{"tb_lineno", T_INT, OFF(tb_lineno), READONLY},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyGetSetDef tb_getsetters[] = {
|
||||
{"tb_next", (getter)tb_next_get, (setter)tb_next_set, NULL, NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
static void
|
||||
tb_dealloc(PyTracebackObject *tb)
|
||||
{
|
||||
|
@ -94,7 +206,7 @@ PyTypeObject PyTraceBack_Type = {
|
|||
0, /* tp_setattro */
|
||||
0, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||
0, /* tp_doc */
|
||||
tb_new__doc__, /* tp_doc */
|
||||
(traverseproc)tb_traverse, /* tp_traverse */
|
||||
(inquiry)tb_clear, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
|
@ -103,39 +215,24 @@ PyTypeObject PyTraceBack_Type = {
|
|||
0, /* tp_iternext */
|
||||
tb_methods, /* tp_methods */
|
||||
tb_memberlist, /* tp_members */
|
||||
0, /* tp_getset */
|
||||
tb_getsetters, /* tp_getset */
|
||||
0, /* tp_base */
|
||||
0, /* tp_dict */
|
||||
0, /* tp_descr_get */
|
||||
0, /* tp_descr_set */
|
||||
0, /* tp_dictoffset */
|
||||
0, /* tp_init */
|
||||
0, /* tp_alloc */
|
||||
tb_new, /* tp_new */
|
||||
};
|
||||
|
||||
static PyTracebackObject *
|
||||
newtracebackobject(PyTracebackObject *next, PyFrameObject *frame)
|
||||
{
|
||||
PyTracebackObject *tb;
|
||||
if ((next != NULL && !PyTraceBack_Check(next)) ||
|
||||
frame == NULL || !PyFrame_Check(frame)) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type);
|
||||
if (tb != NULL) {
|
||||
Py_XINCREF(next);
|
||||
tb->tb_next = next;
|
||||
Py_XINCREF(frame);
|
||||
tb->tb_frame = frame;
|
||||
tb->tb_lasti = frame->f_lasti;
|
||||
tb->tb_lineno = PyFrame_GetLineNumber(frame);
|
||||
PyObject_GC_Track(tb);
|
||||
}
|
||||
return tb;
|
||||
}
|
||||
|
||||
int
|
||||
PyTraceBack_Here(PyFrameObject *frame)
|
||||
{
|
||||
PyObject *exc, *val, *tb, *newtb;
|
||||
PyErr_Fetch(&exc, &val, &tb);
|
||||
newtb = (PyObject *)newtracebackobject((PyTracebackObject *)tb, frame);
|
||||
newtb = tb_create_raw((PyTracebackObject *)tb, frame, frame->f_lasti,
|
||||
PyFrame_GetLineNumber(frame));
|
||||
if (newtb == NULL) {
|
||||
_PyErr_ChainExceptions(exc, val, tb);
|
||||
return -1;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue