refnanny.pyx 5.39 KB
Newer Older
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
1
from python_ref cimport Py_INCREF, Py_DECREF, Py_XDECREF
2
from python_exc cimport PyObject, PyErr_Fetch, PyErr_Restore
3

4 5 6 7 8 9 10 11 12 13

loglevel = 0
reflog = []

cdef log(level, action, obj, lineno):
    if loglevel >= level:
        reflog.append((lineno, action, id(obj)))

LOG_NONE, LOG_ALL = range(2)

14
class Context(object):
15 16 17 18
    def __init__(self, name, line=0, filename=None):
        self.name = name
        self.start = line
        self.filename = filename
19 20 21
        self.refs = {} # id -> (count, [lineno])
        self.errors = []

22
    def regref(self, obj, lineno, is_null):
Stefan Behnel's avatar
Stefan Behnel committed
23
        log(LOG_ALL, u'regref', u"<NULL>" if is_null else obj, lineno)
24
        if is_null:
Stefan Behnel's avatar
Stefan Behnel committed
25
            self.errors.append(u"NULL argument on line %d" % lineno)
26
            return
27 28 29 30 31
        id_ = id(obj)
        count, linenumbers = self.refs.get(id_, (0, []))
        self.refs[id_] = (count + 1, linenumbers)
        linenumbers.append(lineno)

32
    def delref(self, obj, lineno, is_null):
33
        # returns whether it is ok to do the decref operation
Stefan Behnel's avatar
Stefan Behnel committed
34
        log(LOG_ALL, u'delref', u"<NULL>" if is_null else obj, lineno)
35
        if is_null:
Stefan Behnel's avatar
Stefan Behnel committed
36
            self.errors.append(u"NULL argument on line %d" % lineno)
37
            return False
38 39 40
        id_ = id(obj)
        count, linenumbers = self.refs.get(id_, (0, []))
        if count == 0:
Stefan Behnel's avatar
Stefan Behnel committed
41
            self.errors.append(u"Too many decrefs on line %d, reference acquired on lines %r" %
42
                (lineno, linenumbers))
43
            return False
44 45
        elif count == 1:
            del self.refs[id_]
46
            return True
47 48
        else:
            self.refs[id_] = (count - 1, linenumbers)
49
            return True
50 51 52

    def end(self):
        if len(self.refs) > 0:
Stefan Behnel's avatar
Stefan Behnel committed
53
            msg = u""
54
            for count, linenos in self.refs.itervalues():
Stefan Behnel's avatar
Stefan Behnel committed
55 56
                msg += u"\n  Acquired on lines: " + u", ".join([u"%d" % x for x in linenos])
            self.errors.append(u"References leaked: %s" % msg)
57
        if self.errors:
Stefan Behnel's avatar
Stefan Behnel committed
58
            return u"\n".join(self.errors)
59 60 61
        else:
            return None

62
cdef void report_unraisable(object e):
63
    try:
64
        print u"refnanny raised an exception: %s" % e
65
    except:
66
        pass # We absolutely cannot exit with an exception
67

68 69 70
# All Python operations must happen after any existing
# exception has been fetched, in case we are called from
# exception-handling code.
71

72
cdef PyObject* NewContext(char* funcname, int lineno, char* filename) except NULL:
73 74 75
    if Context is None:
        # Context may be None during finalize phase.
        # In that case, we don't want to be doing anything fancy
76
        # like caching and resetting exceptions.
77
        return NULL
78
    cdef PyObject* type = NULL, *value = NULL, *tb = NULL
79
    cdef PyObject* result = NULL
80 81
    PyErr_Fetch(&type, &value, &tb)
    try:
82
        ctx = Context(funcname, lineno, filename)
83
        Py_INCREF(ctx)
84 85 86
        result = <PyObject*>ctx
    except Exception, e:
        report_unraisable(e)
87
    PyErr_Restore(type, value, tb)
88
    return result
89 90

cdef void GOTREF(PyObject* ctx, PyObject* p_obj, int lineno):
91
    if ctx == NULL: return
92
    cdef PyObject* type = NULL, *value = NULL, *tb = NULL
93
    PyErr_Fetch(&type, &value, &tb)
94
    try:
95 96 97 98
        if p_obj is NULL:
            (<object>ctx).regref(None, lineno, True)
        else:
            (<object>ctx).regref(<object>p_obj, lineno, False)
99 100
    except Exception, e:
        report_unraisable(e)
101
    PyErr_Restore(type, value, tb)
102

103
cdef int GIVEREF_and_report(PyObject* ctx, PyObject* p_obj, int lineno):
104
    if ctx == NULL: return 1
105
    cdef PyObject* type = NULL, *value = NULL, *tb = NULL
106
    cdef bint decref_ok = False
107
    PyErr_Fetch(&type, &value, &tb)
108
    try:
109
        if p_obj is NULL:
110
            decref_ok = (<object>ctx).delref(None, lineno, True)
111
        else:
112 113 114
            decref_ok = (<object>ctx).delref(<object>p_obj, lineno, False)
    except Exception, e:
        report_unraisable(e)
115
    PyErr_Restore(type, value, tb)
116 117 118 119
    return decref_ok

cdef void GIVEREF(PyObject* ctx, PyObject* p_obj, int lineno):
    GIVEREF_and_report(ctx, p_obj, lineno)
120

121
cdef void INCREF(PyObject* ctx, PyObject* obj, int lineno):
122
    if obj is not NULL: Py_INCREF(<object>obj)
123
    GOTREF(ctx, obj, lineno)
124

125
cdef void DECREF(PyObject* ctx, PyObject* obj, int lineno):
126 127
    if GIVEREF_and_report(ctx, obj, lineno):
        if obj is not NULL: Py_DECREF(<object>obj)
128

129
cdef void FinishContext(PyObject** ctx):
130
    if ctx == NULL or ctx[0] == NULL: return
131 132 133
    cdef PyObject* type = NULL, *value = NULL, *tb = NULL
    cdef object errors = None
    PyErr_Fetch(&type, &value, &tb)
134
    try:
135
        errors = (<object>ctx[0]).end()
136
        pos = (<object>ctx[0]).filename, (<object>ctx[0]).name
137 138
        if errors:
            print u"%s: %s()" % pos
139
            print errors
140 141
    except Exception, e:
        report_unraisable(e)
142
    Py_DECREF(<object>ctx[0])
143
    ctx[0] = NULL
144
    PyErr_Restore(type, value, tb)
145 146

cdef extern from "Python.h":
147
    object PyCObject_FromVoidPtr(void*, void (*)(void*))
148 149

ctypedef struct RefnannyAPIStruct:
150 151 152 153
  void (*INCREF)(PyObject*, PyObject*, int)
  void (*DECREF)(PyObject*, PyObject*, int)
  void (*GOTREF)(PyObject*, PyObject*, int)
  void (*GIVEREF)(PyObject*, PyObject*, int)
154
  PyObject* (*NewContext)(char*, int, char*) except NULL
155
  void (*FinishContext)(PyObject**)
156 157

cdef RefnannyAPIStruct api
158 159 160 161 162 163
api.INCREF = INCREF
api.DECREF =  DECREF
api.GOTREF =  GOTREF
api.GIVEREF = GIVEREF
api.NewContext = NewContext
api.FinishContext = FinishContext
164

165
RefnannyAPI = PyCObject_FromVoidPtr(<void*>&api, NULL)