aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/cython/Cython/Runtime/refnanny.pyx
blob: 7342f7c0ac17cefa6db7d082ce5ef25ba8ea89dd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# 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)