refnanny.pyx 6.1 KB

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