view Python/traceback.c @ 78855:5a2ef447b80d 2.7

Issue #13992: The trashcan mechanism is now thread-safe. This eliminates sporadic crashes in multi-thread programs when several long deallocator chains ran concurrently and involved subclasses of built-in container types. Because of this change, a couple extension modules compiled for 2.7.4 (those which use the trashcan mechanism, despite it being undocumented) will not be loadable by 2.7.3 and earlier. However, extension modules compiled for 2.7.3 and earlier will be loadable by 2.7.4.
author Antoine Pitrou <solipsis@pitrou.net>
date Thu, 06 Sep 2012 00:59:49 +0200
parents b36cb4602e21
children
line wrap: on
line source


/* Traceback implementation */

#include "Python.h"

#include "code.h"
#include "frameobject.h"
#include "structmember.h"
#include "osdefs.h"
#include "traceback.h"

#define OFF(x) offsetof(PyTracebackObject, x)

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 void
tb_dealloc(PyTracebackObject *tb)
{
    PyObject_GC_UnTrack(tb);
    Py_TRASHCAN_SAFE_BEGIN(tb)
    Py_XDECREF(tb->tb_next);
    Py_XDECREF(tb->tb_frame);
    PyObject_GC_Del(tb);
    Py_TRASHCAN_SAFE_END(tb)
}

static int
tb_traverse(PyTracebackObject *tb, visitproc visit, void *arg)
{
    Py_VISIT(tb->tb_next);
    Py_VISIT(tb->tb_frame);
    return 0;
}

static void
tb_clear(PyTracebackObject *tb)
{
    Py_CLEAR(tb->tb_next);
    Py_CLEAR(tb->tb_frame);
}

PyTypeObject PyTraceBack_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "traceback",
    sizeof(PyTracebackObject),
    0,
    (destructor)tb_dealloc, /*tp_dealloc*/
    0,                  /*tp_print*/
    0,              /*tp_getattr*/
    0,                  /*tp_setattr*/
    0,                  /*tp_compare*/
    0,                  /*tp_repr*/
    0,                  /*tp_as_number*/
    0,                  /*tp_as_sequence*/
    0,                  /*tp_as_mapping*/
    0,                  /* tp_hash */
    0,                  /* tp_call */
    0,                  /* tp_str */
    0,                  /* tp_getattro */
    0,                  /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
    0,                                          /* tp_doc */
    (traverseproc)tb_traverse,                  /* tp_traverse */
    (inquiry)tb_clear,                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                                          /* tp_methods */
    tb_memberlist,                              /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
};

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)
{
    PyThreadState *tstate = PyThreadState_GET();
    PyTracebackObject *oldtb = (PyTracebackObject *) tstate->curexc_traceback;
    PyTracebackObject *tb = newtracebackobject(oldtb, frame);
    if (tb == NULL)
        return -1;
    tstate->curexc_traceback = (PyObject *)tb;
    Py_XDECREF(oldtb);
    return 0;
}

int
_Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno, int indent)
{
    int err = 0;
    FILE *xfp = NULL;
    char linebuf[2000];
    int i;
    char namebuf[MAXPATHLEN+1];

    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 */
        PyObject *path;
        const char *tail = strrchr(filename, SEP);
        if (tail == NULL)
            tail = filename;
        else
            tail++;
        path = PySys_GetObject("path");
        if (path != NULL && PyList_Check(path)) {
            Py_ssize_t _npath = PyList_Size(path);
            int npath = Py_SAFE_DOWNCAST(_npath, Py_ssize_t, int);
            size_t taillen = strlen(tail);
            for (i = 0; i < npath; i++) {
                PyObject *v = PyList_GetItem(path, i);
                if (v == NULL) {
                    PyErr_Clear();
                    break;
                }
                if (PyString_Check(v)) {
                    size_t len;
                    len = PyString_GET_SIZE(v);
                    if (len + 1 + taillen >= MAXPATHLEN)
                        continue; /* Too long */
                    strcpy(namebuf, PyString_AsString(v));
                    if (strlen(namebuf) != len)
                        continue; /* v contains '\0' */
                    if (len > 0 && namebuf[len-1] != SEP)
                        namebuf[len++] = SEP;
                    strcpy(namebuf+len, tail);
                    xfp = fopen(namebuf, "r" PY_STDIOTEXTMODE);
                    if (xfp != NULL) {
                        break;
                    }
                }
            }
        }
    }

    if (xfp == NULL)
        return err;
    if (err != 0) {
        fclose(xfp);
        return err;
    }

    for (i = 0; i < lineno; i++) {
        char* pLastChar = &linebuf[sizeof(linebuf)-2];
        do {
            *pLastChar = '\0';
            if (Py_UniversalNewlineFgets(linebuf, sizeof linebuf, xfp, NULL) == NULL)
                break;
            /* fgets read *something*; if it didn't get as
               far as pLastChar, it must have found a newline
               or hit the end of the file;              if pLastChar is \n,
               it obviously found a newline; else we haven't
               yet seen a newline, so must continue */
        } while (*pLastChar != '\0' && *pLastChar != '\n');
    }
    if (i == lineno) {
        char buf[11];
        char *p = linebuf;
        while (*p == ' ' || *p == '\t' || *p == '\014')
            p++;

        /* 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;
}

static int
tb_displayline(PyObject *f, const char *filename, int lineno, const char *name)
{
    int err = 0;
    char linebuf[2000];

    if (filename == NULL || name == NULL)
        return -1;
    /* This is needed by Emacs' compile command */
#define FMT "  File \"%.500s\", line %d, in %.500s\n"
    PyOS_snprintf(linebuf, sizeof(linebuf), FMT, filename, lineno, name);
    err = PyFile_WriteString(linebuf, f);
    if (err != 0)
        return err;
    return _Py_DisplaySourceLine(f, filename, lineno, 4);
}

static int
tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit)
{
    int err = 0;
    long depth = 0;
    PyTracebackObject *tb1 = tb;
    while (tb1 != NULL) {
        depth++;
        tb1 = tb1->tb_next;
    }
    while (tb != NULL && err == 0) {
        if (depth <= limit) {
            err = tb_displayline(f,
                PyString_AsString(
                    tb->tb_frame->f_code->co_filename),
                tb->tb_lineno,
                PyString_AsString(tb->tb_frame->f_code->co_name));
        }
        depth--;
        tb = tb->tb_next;
        if (err == 0)
            err = PyErr_CheckSignals();
    }
    return err;
}

int
PyTraceBack_Print(PyObject *v, PyObject *f)
{
    int err;
    PyObject *limitv;
    long limit = 1000;
    if (v == NULL)
        return 0;
    if (!PyTraceBack_Check(v)) {
        PyErr_BadInternalCall();
        return -1;
    }
    limitv = PySys_GetObject("tracebacklimit");
    if (limitv && PyInt_Check(limitv)) {
        limit = PyInt_AsLong(limitv);
        if (limit <= 0)
            return 0;
    }
    err = PyFile_WriteString("Traceback (most recent call last):\n", f);
    if (!err)
        err = tb_printinternal((PyTracebackObject *)v, f, limit);
    return err;
}