Home | History | Annotate | Download | only in Runtime
      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 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 
    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)
    195