1from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF, Py_XDECREF, Py_XINCREF
2from cpython.exc cimport PyErr_Fetch, PyErr_Restore
3from cpython.pystate cimport PyThreadState_Get
4
5cimport cython
6
7loglevel = 0
8reflog = []
9
10cdef log(level, action, obj, lineno):
11    if loglevel >= level:
12        reflog.append((lineno, action, id(obj)))
13
14LOG_NONE, LOG_ALL = range(2)
15
16@cython.final
17cdef class Context(object):
18    cdef readonly object name, filename
19    cdef readonly dict refs
20    cdef readonly list errors
21    cdef readonly Py_ssize_t start
22
23    def __cinit__(self, name, line=0, filename=None):
24        self.name = name
25        self.start = line
26        self.filename = filename
27        self.refs = {} # id -> (count, [lineno])
28        self.errors = []
29
30    cdef regref(self, obj, lineno, bint is_null):
31        log(LOG_ALL, u'regref', u"<NULL>" if is_null else obj, lineno)
32        if is_null:
33            self.errors.append(u"NULL argument on line %d" % lineno)
34            return
35        id_ = id(obj)
36        count, linenumbers = self.refs.get(id_, (0, []))
37        self.refs[id_] = (count + 1, linenumbers)
38        linenumbers.append(lineno)
39
40    cdef bint delref(self, obj, lineno, bint is_null) except -1:
41        # returns whether it is ok to do the decref operation
42        log(LOG_ALL, u'delref', u"<NULL>" if is_null else obj, lineno)
43        if is_null:
44            self.errors.append(u"NULL argument on line %d" % lineno)
45            return False
46        id_ = id(obj)
47        count, linenumbers = self.refs.get(id_, (0, []))
48        if count == 0:
49            self.errors.append(u"Too many decrefs on line %d, reference acquired on lines %r" %
50                (lineno, linenumbers))
51            return False
52        elif count == 1:
53            del self.refs[id_]
54            return True
55        else:
56            self.refs[id_] = (count - 1, linenumbers)
57            return True
58
59    cdef end(self):
60        if self.refs:
61            msg = u"References leaked:"
62            for count, linenos in self.refs.itervalues():
63                msg += u"\n  (%d) acquired on lines: %s" % (count, u", ".join([u"%d" % x for x in linenos]))
64            self.errors.append(msg)
65        if self.errors:
66            return u"\n".join([u'REFNANNY: '+error for error in self.errors])
67        else:
68            return None
69
70cdef void report_unraisable(object e=None):
71    try:
72        if e is None:
73            import sys
74            e = sys.exc_info()[1]
75        print u"refnanny raised an exception: %s" % e
76    except:
77        pass # We absolutely cannot exit with an exception
78
79# All Python operations must happen after any existing
80# exception has been fetched, in case we are called from
81# exception-handling code.
82
83cdef PyObject* SetupContext(char* funcname, int lineno, char* filename) except NULL:
84    if Context is None:
85        # Context may be None during finalize phase.
86        # In that case, we don't want to be doing anything fancy
87        # like caching and resetting exceptions.
88        return NULL
89    cdef (PyObject*) type = NULL, value = NULL, tb = NULL, result = NULL
90    PyThreadState_Get()
91    PyErr_Fetch(&type, &value, &tb)
92    try:
93        ctx = Context(funcname, lineno, filename)
94        Py_INCREF(ctx)
95        result = <PyObject*>ctx
96    except Exception, e:
97        report_unraisable(e)
98    PyErr_Restore(type, value, tb)
99    return result
100
101cdef void GOTREF(PyObject* ctx, PyObject* p_obj, int lineno):
102    if ctx == NULL: return
103    cdef (PyObject*) type = NULL, value = NULL, tb = NULL
104    PyErr_Fetch(&type, &value, &tb)
105    try:
106        try:
107            if p_obj is NULL:
108                (<Context>ctx).regref(None, lineno, True)
109            else:
110                (<Context>ctx).regref(<object>p_obj, lineno, False)
111        except:
112            report_unraisable()
113    except:
114        # __Pyx_GetException may itself raise errors
115        pass
116    PyErr_Restore(type, value, tb)
117
118cdef int GIVEREF_and_report(PyObject* ctx, PyObject* p_obj, int lineno):
119    if ctx == NULL: return 1
120    cdef (PyObject*) type = NULL, value = NULL, tb = NULL
121    cdef bint decref_ok = False
122    PyErr_Fetch(&type, &value, &tb)
123    try:
124        try:
125            if p_obj is NULL:
126                decref_ok = (<Context>ctx).delref(None, lineno, True)
127            else:
128                decref_ok = (<Context>ctx).delref(<object>p_obj, lineno, False)
129        except:
130            report_unraisable()
131    except:
132        # __Pyx_GetException may itself raise errors
133        pass
134    PyErr_Restore(type, value, tb)
135    return decref_ok
136
137cdef void GIVEREF(PyObject* ctx, PyObject* p_obj, int lineno):
138    GIVEREF_and_report(ctx, p_obj, lineno)
139
140cdef void INCREF(PyObject* ctx, PyObject* obj, int lineno):
141    Py_XINCREF(obj)
142    PyThreadState_Get()
143    GOTREF(ctx, obj, lineno)
144
145cdef void DECREF(PyObject* ctx, PyObject* obj, int lineno):
146    if GIVEREF_and_report(ctx, obj, lineno):
147        Py_XDECREF(obj)
148    PyThreadState_Get()
149
150cdef void FinishContext(PyObject** ctx):
151    if ctx == NULL or ctx[0] == NULL: return
152    cdef (PyObject*) type = NULL, value = NULL, tb = NULL
153    cdef object errors = None
154    cdef Context context
155    PyThreadState_Get()
156    PyErr_Fetch(&type, &value, &tb)
157    try:
158        try:
159            context = <Context>ctx[0]
160            errors = context.end()
161            if errors:
162                print u"%s: %s()" % (context.filename.decode('latin1'),
163                                     context.name.decode('latin1'))
164                print errors
165            context = None
166        except:
167            report_unraisable()
168    except:
169        # __Pyx_GetException may itself raise errors
170        pass
171    Py_XDECREF(ctx[0])
172    ctx[0] = NULL
173    PyErr_Restore(type, value, tb)
174
175ctypedef struct RefNannyAPIStruct:
176  void (*INCREF)(PyObject*, PyObject*, int)
177  void (*DECREF)(PyObject*, PyObject*, int)
178  void (*GOTREF)(PyObject*, PyObject*, int)
179  void (*GIVEREF)(PyObject*, PyObject*, int)
180  PyObject* (*SetupContext)(char*, int, char*) except NULL
181  void (*FinishContext)(PyObject**)
182
183cdef RefNannyAPIStruct api
184api.INCREF = INCREF
185api.DECREF =  DECREF
186api.GOTREF =  GOTREF
187api.GIVEREF = GIVEREF
188api.SetupContext = SetupContext
189api.FinishContext = FinishContext
190
191cdef extern from "Python.h":
192    object PyLong_FromVoidPtr(void*)
193
194RefNannyAPI = PyLong_FromVoidPtr(<void*>&api)
195