OLD | NEW |
(Empty) | |
| 1 from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF, Py_XDECREF, Py_XINCREF |
| 2 from cpython.exc cimport PyErr_Fetch, PyErr_Restore |
| 3 from cpython.pystate cimport PyThreadState_Get |
| 4 |
| 5 cimport cython |
| 6 |
| 7 loglevel = 0 |
| 8 reflog = [] |
| 9 |
| 10 cdef log(level, action, obj, lineno): |
| 11 if loglevel >= level: |
| 12 reflog.append((lineno, action, id(obj))) |
| 13 |
| 14 LOG_NONE, LOG_ALL = range(2) |
| 15 |
| 16 @cython.final |
| 17 cdef 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 |
| 70 cdef 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 |
| 83 cdef PyObject* SetupContext(char* funcname, int lineno, char* filename) except N
ULL: |
| 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 |
| 101 cdef 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 |
| 118 cdef 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 |
| 137 cdef void GIVEREF(PyObject* ctx, PyObject* p_obj, int lineno): |
| 138 GIVEREF_and_report(ctx, p_obj, lineno) |
| 139 |
| 140 cdef void INCREF(PyObject* ctx, PyObject* obj, int lineno): |
| 141 Py_XINCREF(obj) |
| 142 PyThreadState_Get() |
| 143 GOTREF(ctx, obj, lineno) |
| 144 |
| 145 cdef void DECREF(PyObject* ctx, PyObject* obj, int lineno): |
| 146 if GIVEREF_and_report(ctx, obj, lineno): |
| 147 Py_XDECREF(obj) |
| 148 PyThreadState_Get() |
| 149 |
| 150 cdef 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 |
| 175 ctypedef 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 |
| 183 cdef RefNannyAPIStruct api |
| 184 api.INCREF = INCREF |
| 185 api.DECREF = DECREF |
| 186 api.GOTREF = GOTREF |
| 187 api.GIVEREF = GIVEREF |
| 188 api.SetupContext = SetupContext |
| 189 api.FinishContext = FinishContext |
| 190 |
| 191 cdef extern from "Python.h": |
| 192 object PyLong_FromVoidPtr(void*) |
| 193 |
| 194 RefNannyAPI = PyLong_FromVoidPtr(<void*>&api) |
OLD | NEW |