123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- # cython: language_level=3, auto_pickle=False
- from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF, Py_XDECREF, Py_XINCREF
- from cpython.exc cimport PyErr_Fetch, PyErr_Restore
- from cpython.pystate cimport PyThreadState_Get
- cimport cython
- loglevel = 0
- reflog = []
- cdef log(level, action, obj, lineno):
- if loglevel >= level:
- reflog.append((lineno, action, id(obj)))
- LOG_NONE, LOG_ALL = range(2)
- @cython.final
- cdef class Context(object):
- cdef readonly object name, filename
- cdef readonly dict refs
- cdef readonly list errors
- cdef readonly Py_ssize_t start
- def __cinit__(self, name, line=0, filename=None):
- self.name = name
- self.start = line
- self.filename = filename
- self.refs = {} # id -> (count, [lineno])
- self.errors = []
- cdef regref(self, obj, lineno, bint is_null):
- log(LOG_ALL, u'regref', u"<NULL>" if is_null else obj, lineno)
- if is_null:
- self.errors.append(f"NULL argument on line {lineno}")
- return
- id_ = id(obj)
- count, linenumbers = self.refs.get(id_, (0, []))
- self.refs[id_] = (count + 1, linenumbers)
- linenumbers.append(lineno)
- cdef bint delref(self, obj, lineno, bint is_null) except -1:
- # returns whether it is ok to do the decref operation
- log(LOG_ALL, u'delref', u"<NULL>" if is_null else obj, lineno)
- if is_null:
- self.errors.append(f"NULL argument on line {lineno}")
- return False
- id_ = id(obj)
- count, linenumbers = self.refs.get(id_, (0, []))
- if count == 0:
- self.errors.append(f"Too many decrefs on line {lineno}, reference acquired on lines {linenumbers!r}")
- return False
- elif count == 1:
- del self.refs[id_]
- return True
- else:
- self.refs[id_] = (count - 1, linenumbers)
- return True
- cdef end(self):
- if self.refs:
- msg = u"References leaked:"
- for count, linenos in self.refs.itervalues():
- msg += f"\n ({count}) acquired on lines: {u', '.join([f'{x}' for x in linenos])}"
- self.errors.append(msg)
- if self.errors:
- return u"\n".join([u'REFNANNY: '+error for error in self.errors])
- else:
- return None
- cdef void report_unraisable(object e=None):
- try:
- if e is None:
- import sys
- e = sys.exc_info()[1]
- print(f"refnanny raised an exception: {e}")
- except:
- pass # We absolutely cannot exit with an exception
- # All Python operations must happen after any existing
- # exception has been fetched, in case we are called from
- # exception-handling code.
- cdef PyObject* SetupContext(char* funcname, int lineno, char* filename) except NULL:
- if Context is None:
- # Context may be None during finalize phase.
- # In that case, we don't want to be doing anything fancy
- # like caching and resetting exceptions.
- return NULL
- cdef (PyObject*) type = NULL, value = NULL, tb = NULL, result = NULL
- PyThreadState_Get()
- PyErr_Fetch(&type, &value, &tb)
- try:
- ctx = Context(funcname, lineno, filename)
- Py_INCREF(ctx)
- result = <PyObject*>ctx
- except Exception, e:
- report_unraisable(e)
- PyErr_Restore(type, value, tb)
- return result
- cdef void GOTREF(PyObject* ctx, PyObject* p_obj, int lineno):
- if ctx == NULL: return
- cdef (PyObject*) type = NULL, value = NULL, tb = NULL
- PyErr_Fetch(&type, &value, &tb)
- try:
- try:
- if p_obj is NULL:
- (<Context>ctx).regref(None, lineno, True)
- else:
- (<Context>ctx).regref(<object>p_obj, lineno, False)
- except:
- report_unraisable()
- except:
- # __Pyx_GetException may itself raise errors
- pass
- PyErr_Restore(type, value, tb)
- cdef int GIVEREF_and_report(PyObject* ctx, PyObject* p_obj, int lineno):
- if ctx == NULL: return 1
- cdef (PyObject*) type = NULL, value = NULL, tb = NULL
- cdef bint decref_ok = False
- PyErr_Fetch(&type, &value, &tb)
- try:
- try:
- if p_obj is NULL:
- decref_ok = (<Context>ctx).delref(None, lineno, True)
- else:
- decref_ok = (<Context>ctx).delref(<object>p_obj, lineno, False)
- except:
- report_unraisable()
- except:
- # __Pyx_GetException may itself raise errors
- pass
- PyErr_Restore(type, value, tb)
- return decref_ok
- cdef void GIVEREF(PyObject* ctx, PyObject* p_obj, int lineno):
- GIVEREF_and_report(ctx, p_obj, lineno)
- cdef void INCREF(PyObject* ctx, PyObject* obj, int lineno):
- Py_XINCREF(obj)
- PyThreadState_Get()
- GOTREF(ctx, obj, lineno)
- cdef void DECREF(PyObject* ctx, PyObject* obj, int lineno):
- if GIVEREF_and_report(ctx, obj, lineno):
- Py_XDECREF(obj)
- PyThreadState_Get()
- cdef void FinishContext(PyObject** ctx):
- if ctx == NULL or ctx[0] == NULL: return
- cdef (PyObject*) type = NULL, value = NULL, tb = NULL
- cdef object errors = None
- cdef Context context
- PyThreadState_Get()
- PyErr_Fetch(&type, &value, &tb)
- try:
- try:
- context = <Context>ctx[0]
- errors = context.end()
- if errors:
- print(f"{context.filename.decode('latin1')}: {context.name.decode('latin1')}()")
- print(errors)
- context = None
- except:
- report_unraisable()
- except:
- # __Pyx_GetException may itself raise errors
- pass
- Py_XDECREF(ctx[0])
- ctx[0] = NULL
- PyErr_Restore(type, value, tb)
- ctypedef struct RefNannyAPIStruct:
- void (*INCREF)(PyObject*, PyObject*, int)
- void (*DECREF)(PyObject*, PyObject*, int)
- void (*GOTREF)(PyObject*, PyObject*, int)
- void (*GIVEREF)(PyObject*, PyObject*, int)
- PyObject* (*SetupContext)(char*, int, char*) except NULL
- void (*FinishContext)(PyObject**)
- cdef RefNannyAPIStruct api
- api.INCREF = INCREF
- api.DECREF = DECREF
- api.GOTREF = GOTREF
- api.GIVEREF = GIVEREF
- api.SetupContext = SetupContext
- api.FinishContext = FinishContext
- cdef extern from "Python.h":
- object PyLong_FromVoidPtr(void*)
- RefNannyAPI = PyLong_FromVoidPtr(<void*>&api)
|