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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
#include "Python.h"
#include "pycore_object.h"
#include "pycore_stackref.h"
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
#if SIZEOF_VOID_P < 8
#error "Py_STACKREF_DEBUG requires 64 bit machine"
#endif
#include "pycore_interp.h"
#include "pycore_hashtable.h"
typedef struct _table_entry {
PyObject *obj;
const char *classname;
const char *filename;
int linenumber;
const char *filename_borrow;
int linenumber_borrow;
int borrows;
_PyStackRef borrowed_from;
} TableEntry;
TableEntry *
make_table_entry(PyObject *obj, const char *filename, int linenumber)
{
TableEntry *result = malloc(sizeof(TableEntry));
if (result == NULL) {
return NULL;
}
result->obj = obj;
result->classname = Py_TYPE(obj)->tp_name;
result->filename = filename;
result->linenumber = linenumber;
result->filename_borrow = NULL;
result->linenumber_borrow = 0;
result->borrows = 0;
result->borrowed_from = PyStackRef_NULL;
return result;
}
PyObject *
_Py_stackref_get_object(_PyStackRef ref)
{
assert(!PyStackRef_IsError(ref));
if (ref.index == 0) {
return NULL;
}
PyInterpreterState *interp = PyInterpreterState_Get();
assert(interp != NULL);
if (ref.index >= interp->next_stackref) {
_Py_FatalErrorFormat(__func__,
"Garbled stack ref with ID %" PRIu64 "\n", ref.index);
}
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
if (entry == NULL) {
_Py_FatalErrorFormat(__func__,
"Accessing closed stack ref with ID %" PRIu64 "\n", ref.index);
}
return entry->obj;
}
int
PyStackRef_Is(_PyStackRef a, _PyStackRef b)
{
return _Py_stackref_get_object(a) == _Py_stackref_get_object(b);
}
PyObject *
_Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber)
{
assert(!PyStackRef_IsError(ref));
PyInterpreterState *interp = PyInterpreterState_Get();
if (ref.index >= interp->next_stackref) {
_Py_FatalErrorFormat(__func__,
"Invalid StackRef with ID %" PRIu64 " at %s:%d\n",
ref.index, filename, linenumber);
}
PyObject *obj;
if (ref.index < INITIAL_STACKREF_INDEX) {
if (ref.index == 0) {
_Py_FatalErrorFormat(__func__,
"Passing NULL to _Py_stackref_close at %s:%d\n",
filename, linenumber);
}
// Pre-allocated reference to None, False or True -- Do not clear
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
obj = entry->obj;
}
else {
TableEntry *entry = _Py_hashtable_steal(interp->open_stackrefs_table, (void *)ref.index);
if (entry == NULL) {
#ifdef Py_STACKREF_CLOSE_DEBUG
entry = _Py_hashtable_get(interp->closed_stackrefs_table, (void *)ref.index);
if (entry != NULL) {
_Py_FatalErrorFormat(__func__,
"Double close of ref ID %" PRIu64 " at %s:%d. Referred to instance of %s at %p. Closed at %s:%d\n",
ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber);
}
#endif
_Py_FatalErrorFormat(__func__,
"Invalid StackRef with ID %" PRIu64 " at %s:%d\n",
ref.index, filename, linenumber);
}
if (!PyStackRef_IsNull(entry->borrowed_from)) {
_PyStackRef borrowed_from = entry->borrowed_from;
TableEntry *entry_borrowed = _Py_hashtable_get(interp->open_stackrefs_table, (void *)borrowed_from.index);
if (entry_borrowed == NULL) {
_Py_FatalErrorFormat(__func__,
"Invalid borrowed StackRef with ID %" PRIu64 " at %s:%d\n",
borrowed_from.index, filename, linenumber);
}
entry_borrowed->borrows--;
}
if (entry->borrows > 0) {
_Py_FatalErrorFormat(__func__,
"StackRef with ID %" PRIu64 " closed with %d borrowed refs at %s:%d. Opened at %s:%d\n",
ref.index, entry->borrows, filename, linenumber, entry->filename, entry->linenumber);
}
obj = entry->obj;
free(entry);
#ifdef Py_STACKREF_CLOSE_DEBUG
TableEntry *close_entry = make_table_entry(obj, filename, linenumber);
if (close_entry == NULL) {
Py_FatalError("No memory left for stackref debug table");
}
if (_Py_hashtable_set(interp->closed_stackrefs_table, (void *)ref.index, close_entry) < 0) {
Py_FatalError("No memory left for stackref debug table");
}
#endif
}
return obj;
}
_PyStackRef
_Py_stackref_create(PyObject *obj, uint16_t flags, const char *filename, int linenumber)
{
if (obj == NULL) {
Py_FatalError("Cannot create a stackref for NULL");
}
PyInterpreterState *interp = PyInterpreterState_Get();
uint64_t new_id = interp->next_stackref;
interp->next_stackref = new_id + (1 << Py_TAGGED_SHIFT);
TableEntry *entry = make_table_entry(obj, filename, linenumber);
if (entry == NULL) {
Py_FatalError("No memory left for stackref debug table");
}
new_id |= flags;
if (_Py_hashtable_set(interp->open_stackrefs_table, (void *)new_id, entry) < 0) {
Py_FatalError("No memory left for stackref debug table");
}
return (_PyStackRef){ .index = new_id };
}
void
_Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber)
{
assert(!PyStackRef_IsError(ref));
if (ref.index < INITIAL_STACKREF_INDEX) {
return;
}
PyInterpreterState *interp = PyInterpreterState_Get();
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
if (entry == NULL) {
#ifdef Py_STACKREF_CLOSE_DEBUG
entry = _Py_hashtable_get(interp->closed_stackrefs_table, (void *)ref.index);
if (entry != NULL) {
_Py_FatalErrorFormat(__func__,
"Borrow of closed ref ID %" PRIu64 " at %s:%d. Referred to instance of %s at %p. Closed at %s:%d\n",
ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber);
}
#endif
_Py_FatalErrorFormat(__func__,
"Invalid StackRef with ID %" PRIu64 " at %s:%d\n",
ref.index, filename, linenumber);
}
entry->filename_borrow = filename;
entry->linenumber_borrow = linenumber;
}
_PyStackRef
_Py_stackref_get_borrowed_from(_PyStackRef ref, const char *filename, int linenumber)
{
assert(!PyStackRef_IsError(ref));
PyInterpreterState *interp = PyInterpreterState_Get();
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
if (entry == NULL) {
_Py_FatalErrorFormat(__func__,
"Invalid StackRef with ID %" PRIu64 " at %s:%d\n",
ref.index, filename, linenumber);
}
return entry->borrowed_from;
}
// This function should be used no more than once per ref.
void
_Py_stackref_set_borrowed_from(_PyStackRef ref, _PyStackRef borrowed_from, const char *filename, int linenumber)
{
assert(!PyStackRef_IsError(ref));
PyInterpreterState *interp = PyInterpreterState_Get();
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
if (entry == NULL) {
_Py_FatalErrorFormat(__func__,
"Invalid StackRef (ref) with ID %" PRIu64 " at %s:%d\n",
ref.index, filename, linenumber);
}
assert(PyStackRef_IsNull(entry->borrowed_from));
if (PyStackRef_IsNull(borrowed_from)) {
return;
}
TableEntry *entry_borrowed = _Py_hashtable_get(interp->open_stackrefs_table, (void *)borrowed_from.index);
if (entry_borrowed == NULL) {
_Py_FatalErrorFormat(__func__,
"Invalid StackRef (borrowed_from) with ID %" PRIu64 " at %s:%d\n",
borrowed_from.index, filename, linenumber);
}
entry->borrowed_from = borrowed_from;
entry_borrowed->borrows++;
}
void
_Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref)
{
assert(!PyStackRef_IsError(ref));
assert(ref.index < INITIAL_STACKREF_INDEX);
TableEntry *entry = make_table_entry(obj, "builtin-object", 0);
if (entry == NULL) {
Py_FatalError("No memory left for stackref debug table");
}
if (_Py_hashtable_set(interp->open_stackrefs_table, (void *)ref.index, (void *)entry) < 0) {
Py_FatalError("No memory left for stackref debug table");
}
}
static int
report_leak(_Py_hashtable_t *ht, const void *key, const void *value, void *leak)
{
TableEntry *entry = (TableEntry *)value;
if (!_Py_IsStaticImmortal(entry->obj)) {
*(int *)leak = 1;
printf("Stackref leak. Refers to instance of %s at %p. Created at %s:%d",
entry->classname, entry->obj, entry->filename, entry->linenumber);
if (entry->filename_borrow != NULL) {
printf(". Last borrow at %s:%d",entry->filename_borrow, entry->linenumber_borrow);
}
printf("\n");
}
return 0;
}
void
_Py_stackref_report_leaks(PyInterpreterState *interp)
{
int leak = 0;
_Py_hashtable_foreach(interp->open_stackrefs_table, report_leak, &leak);
if (leak) {
fflush(stdout);
Py_FatalError("Stackrefs leaked.");
}
}
_PyStackRef PyStackRef_TagInt(intptr_t i)
{
assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << Py_TAGGED_SHIFT), Py_TAGGED_SHIFT) == i);
return (_PyStackRef){ .index = (i << Py_TAGGED_SHIFT) | Py_INT_TAG };
}
intptr_t
PyStackRef_UntagInt(_PyStackRef i)
{
assert(PyStackRef_IsTaggedInt(i));
intptr_t val = (intptr_t)i.index;
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, Py_TAGGED_SHIFT);
}
bool
PyStackRef_IsNullOrInt(_PyStackRef ref)
{
return PyStackRef_IsNull(ref) || PyStackRef_IsTaggedInt(ref);
}
_PyStackRef
PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
{
assert(PyStackRef_IsTaggedInt(ref));
assert((ref.index & (~Py_TAG_BITS)) != (INTPTR_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
return (_PyStackRef){ .index = ref.index + (1 << Py_TAGGED_SHIFT) };
}
#endif
|