Skip to content

Commit b8f7ab5

Browse files
gh-104252: Immortalize Py_EMPTY_KEYS (gh-104253)
This was missed in gh-19474. It matters for with a per-interpreter GIL since PyDictKeysObject.dk_refcnt breaks isolation and leads to races.
1 parent 2dcb289 commit b8f7ab5

File tree

3 files changed

+27
-14
lines changed

3 files changed

+27
-14
lines changed

Include/internal/pycore_dict_state.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ struct _Py_dict_state {
3838
PyDict_WatchCallback watchers[DICT_MAX_WATCHERS];
3939
};
4040

41+
#define _dict_state_INIT \
42+
{ \
43+
.next_keys_version = 2, \
44+
}
45+
4146

4247
#ifdef __cplusplus
4348
}

Include/internal/pycore_runtime_init.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,7 @@ extern PyTypeObject _PyExc_MemoryError;
107107
}, \
108108
}, \
109109
.dtoa = _dtoa_state_INIT(&(INTERP)), \
110-
.dict_state = { \
111-
.next_keys_version = 2, \
112-
}, \
110+
.dict_state = _dict_state_INIT, \
113111
.func_state = { \
114112
.next_version = 1, \
115113
}, \

Objects/dictobject.c

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,19 @@ _PyDict_DebugMallocStats(FILE *out)
300300

301301
static void free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys);
302302

303+
/* PyDictKeysObject has refcounts like PyObject does, so we have the
304+
following two functions to mirror what Py_INCREF() and Py_DECREF() do.
305+
(Keep in mind that PyDictKeysObject isn't actually a PyObject.)
306+
Likewise a PyDictKeysObject can be immortal (e.g. Py_EMPTY_KEYS),
307+
so we apply a naive version of what Py_INCREF() and Py_DECREF() do
308+
for immortal objects. */
309+
303310
static inline void
304311
dictkeys_incref(PyDictKeysObject *dk)
305312
{
313+
if (dk->dk_refcnt == _Py_IMMORTAL_REFCNT) {
314+
return;
315+
}
306316
#ifdef Py_REF_DEBUG
307317
_Py_IncRefTotal(_PyInterpreterState_GET());
308318
#endif
@@ -312,6 +322,9 @@ dictkeys_incref(PyDictKeysObject *dk)
312322
static inline void
313323
dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk)
314324
{
325+
if (dk->dk_refcnt == _Py_IMMORTAL_REFCNT) {
326+
return;
327+
}
315328
assert(dk->dk_refcnt > 0);
316329
#ifdef Py_REF_DEBUG
317330
_Py_DecRefTotal(_PyInterpreterState_GET());
@@ -447,7 +460,7 @@ estimate_log2_keysize(Py_ssize_t n)
447460
* (which cannot fail and thus can do no allocation).
448461
*/
449462
static PyDictKeysObject empty_keys_struct = {
450-
1, /* dk_refcnt */
463+
_Py_IMMORTAL_REFCNT, /* dk_refcnt */
451464
0, /* dk_log2_size */
452465
0, /* dk_log2_index_bytes */
453466
DICT_KEYS_UNICODE, /* dk_kind */
@@ -779,6 +792,7 @@ clone_combined_dict_keys(PyDictObject *orig)
779792
assert(PyDict_Check(orig));
780793
assert(Py_TYPE(orig)->tp_iter == (getiterfunc)dict_iter);
781794
assert(orig->ma_values == NULL);
795+
assert(orig->ma_keys != Py_EMPTY_KEYS);
782796
assert(orig->ma_keys->dk_refcnt == 1);
783797

784798
size_t keys_size = _PyDict_KeysSize(orig->ma_keys);
@@ -833,7 +847,7 @@ PyObject *
833847
PyDict_New(void)
834848
{
835849
PyInterpreterState *interp = _PyInterpreterState_GET();
836-
dictkeys_incref(Py_EMPTY_KEYS);
850+
/* We don't incref Py_EMPTY_KEYS here because it is immortal. */
837851
return new_dict(interp, Py_EMPTY_KEYS, NULL, 0, 0);
838852
}
839853

@@ -1331,7 +1345,7 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp,
13311345
Py_DECREF(value);
13321346
return -1;
13331347
}
1334-
dictkeys_decref(interp, Py_EMPTY_KEYS);
1348+
/* We don't decref Py_EMPTY_KEYS here because it is immortal. */
13351349
mp->ma_keys = newkeys;
13361350
mp->ma_values = NULL;
13371351

@@ -1529,14 +1543,10 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp,
15291543

15301544
// We can not use free_keys_object here because key's reference
15311545
// are moved already.
1546+
if (oldkeys != Py_EMPTY_KEYS) {
15321547
#ifdef Py_REF_DEBUG
1533-
_Py_DecRefTotal(_PyInterpreterState_GET());
1548+
_Py_DecRefTotal(_PyInterpreterState_GET());
15341549
#endif
1535-
if (oldkeys == Py_EMPTY_KEYS) {
1536-
oldkeys->dk_refcnt--;
1537-
assert(oldkeys->dk_refcnt > 0);
1538-
}
1539-
else {
15401550
assert(oldkeys->dk_kind != DICT_KEYS_SPLIT);
15411551
assert(oldkeys->dk_refcnt == 1);
15421552
#if PyDict_MAXFREELIST > 0
@@ -2080,8 +2090,8 @@ PyDict_Clear(PyObject *op)
20802090
dictkeys_decref(interp, oldkeys);
20812091
}
20822092
else {
2083-
assert(oldkeys->dk_refcnt == 1);
2084-
dictkeys_decref(interp, oldkeys);
2093+
assert(oldkeys->dk_refcnt == 1);
2094+
dictkeys_decref(interp, oldkeys);
20852095
}
20862096
ASSERT_CONSISTENT(mp);
20872097
}

0 commit comments

Comments
 (0)