Skip to content

Commit 918a9ac

Browse files
gh-135125: Fix Py_STACKREF_DEBUG build (GH-139475)
* Use the same pattern of refcounting for stackrefs as in production build
1 parent 61e759c commit 918a9ac

File tree

3 files changed

+142
-59
lines changed

3 files changed

+142
-59
lines changed

Include/internal/pycore_stackref.h

Lines changed: 131 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -50,27 +50,58 @@ extern "C" {
5050
CPython refcounting operations on it!
5151
*/
5252

53+
#define Py_INT_TAG 3
54+
#define Py_TAG_INVALID 2
55+
#define Py_TAG_REFCNT 1
56+
#define Py_TAG_BITS 3
5357

54-
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
58+
#define Py_TAGGED_SHIFT 2
5559

56-
#define Py_TAG_BITS 0
60+
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
5761

5862
PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref);
5963
PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber);
60-
PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, const char *filename, int linenumber);
64+
PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, uint16_t flags, const char *filename, int linenumber);
6165
PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber);
6266
extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref);
6367

6468
static const _PyStackRef PyStackRef_NULL = { .index = 0 };
65-
static const _PyStackRef PyStackRef_ERROR = { .index = 2 };
69+
static const _PyStackRef PyStackRef_ERROR = { .index = (1 << Py_TAGGED_SHIFT) };
6670

67-
// Use the first 3 even numbers for None, True and False.
68-
// Odd numbers are reserved for (tagged) integers
69-
#define PyStackRef_None ((_PyStackRef){ .index = 4 } )
70-
#define PyStackRef_False ((_PyStackRef){ .index = 6 })
71-
#define PyStackRef_True ((_PyStackRef){ .index = 8 })
71+
#define PyStackRef_None ((_PyStackRef){ .index = (2 << Py_TAGGED_SHIFT) } )
72+
#define PyStackRef_False ((_PyStackRef){ .index = (3 << Py_TAGGED_SHIFT) })
73+
#define PyStackRef_True ((_PyStackRef){ .index = (4 << Py_TAGGED_SHIFT) })
74+
75+
#define INITIAL_STACKREF_INDEX (5 << Py_TAGGED_SHIFT)
76+
77+
static inline _PyStackRef
78+
PyStackRef_Wrap(void *ptr)
79+
{
80+
assert(ptr != NULL);
81+
#ifdef Py_DEBUG
82+
assert(((uint64_t)ptr & Py_TAG_BITS) == 0);
83+
return (_PyStackRef){ .index = ((uint64_t)ptr) | Py_TAG_INVALID };
84+
#else
85+
return (_PyStackRef){ .index = (uint64_t)ptr };
86+
#endif
87+
}
88+
89+
static inline void *
90+
PyStackRef_Unwrap(_PyStackRef ref)
91+
{
92+
#ifdef Py_DEBUG
93+
assert ((ref.index & Py_TAG_BITS) == Py_TAG_INVALID);
94+
return (void *)(ref.index & ~Py_TAG_BITS);
95+
#else
96+
return (void *)(ref.index);
97+
#endif
98+
}
7299

73-
#define INITIAL_STACKREF_INDEX 10
100+
static inline int
101+
PyStackRef_RefcountOnObject(_PyStackRef ref)
102+
{
103+
return (ref.index & Py_TAG_REFCNT) == 0;
104+
}
74105

75106
static inline int
76107
PyStackRef_IsNull(_PyStackRef ref)
@@ -81,7 +112,13 @@ PyStackRef_IsNull(_PyStackRef ref)
81112
static inline bool
82113
PyStackRef_IsError(_PyStackRef ref)
83114
{
84-
return ref.index == 2;
115+
return ref.index == (1 << Py_TAGGED_SHIFT);
116+
}
117+
118+
static inline bool
119+
PyStackRef_IsMalformed(_PyStackRef ref)
120+
{
121+
return (ref.index & Py_TAG_BITS) == Py_TAG_INVALID;
85122
}
86123

87124
static inline bool
@@ -112,7 +149,7 @@ PyStackRef_IsNone(_PyStackRef ref)
112149
static inline bool
113150
PyStackRef_IsTaggedInt(_PyStackRef ref)
114151
{
115-
return (ref.index & 1) == 1;
152+
return (ref.index & Py_TAG_BITS) == Py_INT_TAG;
116153
}
117154

118155
static inline PyObject *
@@ -123,50 +160,68 @@ _PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumb
123160
_Py_stackref_record_borrow(ref, filename, linenumber);
124161
return _Py_stackref_get_object(ref);
125162
}
126-
127163
#define PyStackRef_AsPyObjectBorrow(REF) _PyStackRef_AsPyObjectBorrow((REF), __FILE__, __LINE__)
128164

129165
static inline PyObject *
130166
_PyStackRef_AsPyObjectSteal(_PyStackRef ref, const char *filename, int linenumber)
131167
{
132-
return _Py_stackref_close(ref, filename, linenumber);
168+
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
169+
if (PyStackRef_RefcountOnObject(ref)) {
170+
return obj;
171+
}
172+
return Py_NewRef(obj);
133173
}
134174
#define PyStackRef_AsPyObjectSteal(REF) _PyStackRef_AsPyObjectSteal((REF), __FILE__, __LINE__)
135175

136176
static inline _PyStackRef
137177
_PyStackRef_FromPyObjectNew(PyObject *obj, const char *filename, int linenumber)
138178
{
139-
Py_INCREF(obj);
140-
return _Py_stackref_create(obj, filename, linenumber);
179+
assert(obj != NULL);
180+
uint16_t flags = 0;
181+
if (!_Py_IsImmortal(obj)) {
182+
_Py_INCREF_MORTAL(obj);
183+
} else {
184+
flags = Py_TAG_REFCNT;
185+
}
186+
return _Py_stackref_create(obj, flags, filename, linenumber);
141187
}
142188
#define PyStackRef_FromPyObjectNew(obj) _PyStackRef_FromPyObjectNew(_PyObject_CAST(obj), __FILE__, __LINE__)
143189

144190
static inline _PyStackRef
145191
_PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumber)
146192
{
147-
return _Py_stackref_create(obj, filename, linenumber);
193+
assert(obj != NULL);
194+
uint16_t flags = 0;
195+
if (_Py_IsImmortal(obj)) {
196+
flags = Py_TAG_REFCNT;
197+
}
198+
return _Py_stackref_create(obj, flags, filename, linenumber);
148199
}
149200
#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__)
150201

151202
static inline _PyStackRef
152203
_PyStackRef_FromPyObjectBorrow(PyObject *obj, const char *filename, int linenumber)
153204
{
154-
return _Py_stackref_create(obj, filename, linenumber);
205+
return _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber);
155206
}
156207
#define PyStackRef_FromPyObjectBorrow(obj) _PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj), __FILE__, __LINE__)
157208

158209
static inline void
159210
_PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber)
160211
{
212+
assert(!PyStackRef_IsError(ref));
213+
assert(!PyStackRef_IsNull(ref));
161214
if (PyStackRef_IsTaggedInt(ref)) {
162215
return;
163216
}
164217
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
165-
Py_DECREF(obj);
218+
assert(Py_REFCNT(obj) > 0);
219+
if (PyStackRef_RefcountOnObject(ref)) {
220+
Py_DECREF(obj);
221+
}
166222
}
167223
#define PyStackRef_CLOSE(REF) _PyStackRef_CLOSE((REF), __FILE__, __LINE__)
168224

169-
170225
static inline void
171226
_PyStackRef_XCLOSE(_PyStackRef ref, const char *filename, int linenumber)
172227
{
@@ -182,31 +237,46 @@ static inline _PyStackRef
182237
_PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber)
183238
{
184239
assert(!PyStackRef_IsError(ref));
240+
assert(!PyStackRef_IsNull(ref));
185241
if (PyStackRef_IsTaggedInt(ref)) {
186242
return ref;
187243
}
188-
else {
189-
PyObject *obj = _Py_stackref_get_object(ref);
244+
PyObject *obj = _Py_stackref_get_object(ref);
245+
uint16_t flags = 0;
246+
if (PyStackRef_RefcountOnObject(ref)) {
190247
Py_INCREF(obj);
191-
return _Py_stackref_create(obj, filename, linenumber);
248+
} else {
249+
flags = Py_TAG_REFCNT;
192250
}
251+
return _Py_stackref_create(obj, flags, filename, linenumber);
193252
}
194253
#define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__)
195254

196-
extern void _PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *filename, int linenumber);
197-
#define PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT) _PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT, __FILE__, __LINE__)
198-
199-
static inline _PyStackRef
200-
PyStackRef_MakeHeapSafe(_PyStackRef ref)
255+
static inline void
256+
_PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *filename, int linenumber)
201257
{
202-
return ref;
258+
assert(!PyStackRef_IsError(ref));
259+
assert(!PyStackRef_IsNull(ref));
260+
assert(!PyStackRef_IsTaggedInt(ref));
261+
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
262+
if (PyStackRef_RefcountOnObject(ref)) {
263+
_Py_DECREF_SPECIALIZED(obj, destruct);
264+
}
203265
}
266+
#define PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT) _PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT, __FILE__, __LINE__)
204267

205268
static inline _PyStackRef
206-
PyStackRef_Borrow(_PyStackRef ref)
269+
_PyStackRef_Borrow(_PyStackRef ref, const char *filename, int linenumber)
207270
{
208-
return PyStackRef_DUP(ref);
271+
assert(!PyStackRef_IsError(ref));
272+
assert(!PyStackRef_IsNull(ref));
273+
if (PyStackRef_IsTaggedInt(ref)) {
274+
return ref;
275+
}
276+
PyObject *obj = _Py_stackref_get_object(ref);
277+
return _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber);
209278
}
279+
#define PyStackRef_Borrow(REF) _PyStackRef_Borrow((REF), __FILE__, __LINE__)
210280

211281
#define PyStackRef_CLEAR(REF) \
212282
do { \
@@ -219,28 +289,47 @@ PyStackRef_Borrow(_PyStackRef ref)
219289
static inline _PyStackRef
220290
_PyStackRef_FromPyObjectStealMortal(PyObject *obj, const char *filename, int linenumber)
221291
{
292+
assert(obj != NULL);
222293
assert(!_Py_IsImmortal(obj));
223-
return _Py_stackref_create(obj, filename, linenumber);
294+
return _Py_stackref_create(obj, 0, filename, linenumber);
224295
}
225296
#define PyStackRef_FromPyObjectStealMortal(obj) _PyStackRef_FromPyObjectStealMortal(_PyObject_CAST(obj), __FILE__, __LINE__)
226297

227298
static inline bool
228299
PyStackRef_IsHeapSafe(_PyStackRef ref)
229300
{
230-
return true;
301+
if ((ref.index & Py_TAG_BITS) != Py_TAG_REFCNT || PyStackRef_IsNull(ref)) {
302+
// Tagged ints and ERROR are included.
303+
return true;
304+
}
305+
306+
PyObject *obj = _Py_stackref_get_object(ref);
307+
return _Py_IsImmortal(obj);
231308
}
232309

310+
static inline _PyStackRef
311+
_PyStackRef_MakeHeapSafe(_PyStackRef ref, const char *filename, int linenumber)
312+
{
313+
if (PyStackRef_IsHeapSafe(ref)) {
314+
return ref;
315+
}
316+
317+
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
318+
Py_INCREF(obj);
319+
return _Py_stackref_create(obj, 0, filename, linenumber);
320+
}
321+
#define PyStackRef_MakeHeapSafe(REF) _PyStackRef_MakeHeapSafe(REF, __FILE__, __LINE__)
322+
233323
static inline _PyStackRef
234324
_PyStackRef_FromPyObjectNewMortal(PyObject *obj, const char *filename, int linenumber)
235325
{
326+
assert(obj != NULL);
236327
assert(!_Py_IsStaticImmortal(obj));
237328
Py_INCREF(obj);
238-
return _Py_stackref_create(obj, filename, linenumber);
329+
return _Py_stackref_create(obj, 0, filename, linenumber);
239330
}
240331
#define PyStackRef_FromPyObjectNewMortal(obj) _PyStackRef_FromPyObjectNewMortal(_PyObject_CAST(obj), __FILE__, __LINE__)
241332

242-
#define PyStackRef_RefcountOnObject(REF) 1
243-
244333
extern int PyStackRef_Is(_PyStackRef a, _PyStackRef b);
245334

246335
extern bool PyStackRef_IsTaggedInt(_PyStackRef ref);
@@ -257,11 +346,6 @@ PyStackRef_IsNullOrInt(_PyStackRef ref);
257346

258347
#else
259348

260-
#define Py_INT_TAG 3
261-
#define Py_TAG_INVALID 2
262-
#define Py_TAG_REFCNT 1
263-
#define Py_TAG_BITS 3
264-
265349
static const _PyStackRef PyStackRef_ERROR = { .bits = Py_TAG_INVALID };
266350

267351
/* Wrap a pointer in a stack ref.
@@ -273,6 +357,7 @@ PyStackRef_Wrap(void *ptr)
273357
{
274358
assert(ptr != NULL);
275359
#ifdef Py_DEBUG
360+
assert(((uintptr_t)ptr & Py_TAG_BITS) == 0);
276361
return (_PyStackRef){ .bits = ((uintptr_t)ptr) | Py_TAG_INVALID };
277362
#else
278363
return (_PyStackRef){ .bits = (uintptr_t)ptr };
@@ -318,25 +403,25 @@ PyStackRef_IsTaggedInt(_PyStackRef i)
318403
static inline _PyStackRef
319404
PyStackRef_TagInt(intptr_t i)
320405
{
321-
assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << 2), 2) == i);
322-
return (_PyStackRef){ .bits = ((((uintptr_t)i) << 2) | Py_INT_TAG) };
406+
assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << Py_TAGGED_SHIFT), Py_TAGGED_SHIFT) == i);
407+
return (_PyStackRef){ .bits = ((((uintptr_t)i) << Py_TAGGED_SHIFT) | Py_INT_TAG) };
323408
}
324409

325410
static inline intptr_t
326411
PyStackRef_UntagInt(_PyStackRef i)
327412
{
328413
assert(PyStackRef_IsTaggedInt(i));
329414
intptr_t val = (intptr_t)i.bits;
330-
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, 2);
415+
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, Py_TAGGED_SHIFT);
331416
}
332417

333418

334419
static inline _PyStackRef
335420
PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
336421
{
337422
assert((ref.bits & Py_TAG_BITS) == Py_INT_TAG); // Is tagged int
338-
assert((ref.bits & (~Py_TAG_BITS)) != (INT_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
339-
return (_PyStackRef){ .bits = ref.bits + 4 };
423+
assert((ref.bits & (~Py_TAG_BITS)) != (INTPTR_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
424+
return (_PyStackRef){ .bits = ref.bits + (1 << Py_TAGGED_SHIFT) };
340425
}
341426

342427
#define PyStackRef_IsDeferredOrTaggedInt(ref) (((ref).bits & Py_TAG_REFCNT) != 0)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Changes in stackref debugging mode when ``Py_STACKREF_DEBUG`` is set. We use
2+
the same pattern of refcounting for stackrefs as in production build.

Python/stackrefs.c

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,19 @@ _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber)
109109
}
110110

111111
_PyStackRef
112-
_Py_stackref_create(PyObject *obj, const char *filename, int linenumber)
112+
_Py_stackref_create(PyObject *obj, uint16_t flags, const char *filename, int linenumber)
113113
{
114114
if (obj == NULL) {
115115
Py_FatalError("Cannot create a stackref for NULL");
116116
}
117117
PyInterpreterState *interp = PyInterpreterState_Get();
118118
uint64_t new_id = interp->next_stackref;
119-
interp->next_stackref = new_id + 2;
119+
interp->next_stackref = new_id + (1 << Py_TAGGED_SHIFT);
120120
TableEntry *entry = make_table_entry(obj, filename, linenumber);
121121
if (entry == NULL) {
122122
Py_FatalError("No memory left for stackref debug table");
123123
}
124+
new_id |= flags;
124125
if (_Py_hashtable_set(interp->open_stackrefs_table, (void *)new_id, entry) < 0) {
125126
Py_FatalError("No memory left for stackref debug table");
126127
}
@@ -194,24 +195,18 @@ _Py_stackref_report_leaks(PyInterpreterState *interp)
194195
}
195196
}
196197

197-
void
198-
_PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *filename, int linenumber)
199-
{
200-
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
201-
_Py_DECREF_SPECIALIZED(obj, destruct);
202-
}
203-
204198
_PyStackRef PyStackRef_TagInt(intptr_t i)
205199
{
206-
return (_PyStackRef){ .index = (i << 1) + 1 };
200+
assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << Py_TAGGED_SHIFT), Py_TAGGED_SHIFT) == i);
201+
return (_PyStackRef){ .index = (i << Py_TAGGED_SHIFT) | Py_INT_TAG };
207202
}
208203

209204
intptr_t
210205
PyStackRef_UntagInt(_PyStackRef i)
211206
{
212207
assert(PyStackRef_IsTaggedInt(i));
213208
intptr_t val = (intptr_t)i.index;
214-
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, 1);
209+
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, Py_TAGGED_SHIFT);
215210
}
216211

217212
bool
@@ -223,8 +218,9 @@ PyStackRef_IsNullOrInt(_PyStackRef ref)
223218
_PyStackRef
224219
PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
225220
{
226-
assert(ref.index <= INT_MAX - 2); // No overflow
227-
return (_PyStackRef){ .index = ref.index + 2 };
221+
assert(PyStackRef_IsTaggedInt(ref));
222+
assert((ref.index & (~Py_TAG_BITS)) != (INTPTR_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
223+
return (_PyStackRef){ .index = ref.index + (1 << Py_TAGGED_SHIFT) };
228224
}
229225

230226

0 commit comments

Comments
 (0)