Skip to content

Commit 1ddb412

Browse files
authored
gh-141510: Add can_modify_dict() in dictobject.c (#144955)
can_modify_dict() is stricter than ASSERT_DICT_LOCKED() for frozendict. It uses PyUnstable_Object_IsUniquelyReferenced() which matters for free-threaded builds. Replace anydict_setitem_take2() with setitem_take2_lock_held(). It's no longer useful to have two functions.
1 parent f705486 commit 1ddb412

File tree

1 file changed

+55
-47
lines changed

1 file changed

+55
-47
lines changed

Objects/dictobject.c

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,24 @@ load_keys_nentries(PyDictObject *mp)
282282

283283
#endif
284284

285+
#ifndef NDEBUG
286+
// Check if it's possible to modify a dictionary.
287+
// Usage: assert(can_modify_dict(mp)).
288+
static inline int
289+
can_modify_dict(PyDictObject *mp)
290+
{
291+
if (PyFrozenDict_Check(mp)) {
292+
// No locking required to modify a newly created frozendict
293+
// since it's only accessible from the current thread.
294+
return PyUnstable_Object_IsUniquelyReferenced(_PyObject_CAST(mp));
295+
}
296+
else {
297+
ASSERT_DICT_LOCKED(mp);
298+
return 1;
299+
}
300+
}
301+
#endif
302+
285303
#define _PyAnyDict_CAST(op) \
286304
(assert(PyAnyDict_Check(op)), _Py_CAST(PyDictObject*, op))
287305

@@ -1867,8 +1885,9 @@ insert_split_key(PyDictKeysObject *keys, PyObject *key, Py_hash_t hash)
18671885
static void
18681886
insert_split_value(PyDictObject *mp, PyObject *key, PyObject *value, Py_ssize_t ix)
18691887
{
1888+
assert(can_modify_dict(mp));
18701889
assert(PyUnicode_CheckExact(key));
1871-
ASSERT_DICT_LOCKED(mp);
1890+
18721891
PyObject *old_value = mp->ma_values->values[ix];
18731892
if (old_value == NULL) {
18741893
_PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value);
@@ -1896,11 +1915,11 @@ static int
18961915
insertdict(PyDictObject *mp,
18971916
PyObject *key, Py_hash_t hash, PyObject *value)
18981917
{
1918+
assert(can_modify_dict(mp));
1919+
18991920
PyObject *old_value = NULL;
19001921
Py_ssize_t ix;
19011922

1902-
ASSERT_DICT_LOCKED(mp);
1903-
19041923
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
19051924
ix = insert_split_key(mp->ma_keys, key, hash);
19061925
if (ix != DKIX_EMPTY) {
@@ -1967,8 +1986,8 @@ static int
19671986
insert_to_emptydict(PyDictObject *mp,
19681987
PyObject *key, Py_hash_t hash, PyObject *value)
19691988
{
1989+
assert(can_modify_dict(mp));
19701990
assert(mp->ma_keys == Py_EMPTY_KEYS);
1971-
ASSERT_DICT_LOCKED(mp);
19721991

19731992
int unicode = PyUnicode_CheckExact(key);
19741993
PyDictKeysObject *newkeys = new_keys_object(PyDict_LOG_MINSIZE, unicode);
@@ -2069,11 +2088,11 @@ static int
20692088
dictresize(PyDictObject *mp,
20702089
uint8_t log2_newsize, int unicode)
20712090
{
2091+
assert(can_modify_dict(mp));
2092+
20722093
PyDictKeysObject *oldkeys, *newkeys;
20732094
PyDictValues *oldvalues;
20742095

2075-
ASSERT_DICT_LOCKED(mp);
2076-
20772096
if (log2_newsize >= SIZEOF_SIZE_T*8) {
20782097
PyErr_NoMemory();
20792098
return -1;
@@ -2671,11 +2690,13 @@ _PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
26712690

26722691
/* Consumes references to key and value */
26732692
static int
2674-
anydict_setitem_take2(PyDictObject *mp, PyObject *key, PyObject *value)
2693+
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
26752694
{
2695+
assert(PyAnyDict_Check(mp));
2696+
assert(can_modify_dict(mp));
26762697
assert(key);
26772698
assert(value);
2678-
assert(PyAnyDict_Check(mp));
2699+
26792700
Py_hash_t hash = _PyObject_HashFast(key);
26802701
if (hash == -1) {
26812702
dict_unhashable_type(key);
@@ -2691,14 +2712,6 @@ anydict_setitem_take2(PyDictObject *mp, PyObject *key, PyObject *value)
26912712
return insertdict(mp, key, hash, value);
26922713
}
26932714

2694-
/* Consumes references to key and value */
2695-
static int
2696-
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
2697-
{
2698-
ASSERT_DICT_LOCKED(mp);
2699-
return anydict_setitem_take2(mp, key, value);
2700-
}
2701-
27022715
int
27032716
_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value)
27042717
{
@@ -2800,9 +2813,9 @@ static void
28002813
delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
28012814
PyObject *old_value)
28022815
{
2803-
PyObject *old_key;
2816+
assert(can_modify_dict(mp));
28042817

2805-
ASSERT_DICT_LOCKED(mp);
2818+
PyObject *old_key;
28062819

28072820
Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix);
28082821
assert(hashpos >= 0);
@@ -2856,19 +2869,17 @@ int
28562869
_PyDict_DelItem_KnownHash_LockHeld(PyObject *op, PyObject *key, Py_hash_t hash)
28572870
{
28582871
Py_ssize_t ix;
2859-
PyDictObject *mp;
28602872
PyObject *old_value;
28612873

28622874
if (!PyDict_Check(op)) {
28632875
PyErr_BadInternalCall();
28642876
return -1;
28652877
}
2866-
2867-
ASSERT_DICT_LOCKED(op);
2878+
PyDictObject *mp = (PyDictObject *)op;
2879+
assert(can_modify_dict(mp));
28682880

28692881
assert(key);
28702882
assert(hash != -1);
2871-
mp = (PyDictObject *)op;
28722883
ix = _Py_dict_lookup(mp, key, hash, &old_value);
28732884
if (ix == DKIX_ERROR)
28742885
return -1;
@@ -2897,19 +2908,18 @@ delitemif_lock_held(PyObject *op, PyObject *key,
28972908
int (*predicate)(PyObject *value, void *arg),
28982909
void *arg)
28992910
{
2911+
PyDictObject *mp = _PyAnyDict_CAST(op);
2912+
assert(can_modify_dict(mp));
2913+
29002914
Py_ssize_t ix;
2901-
PyDictObject *mp;
29022915
Py_hash_t hash;
29032916
PyObject *old_value;
29042917
int res;
29052918

2906-
ASSERT_DICT_LOCKED(op);
2907-
29082919
assert(key);
29092920
hash = PyObject_Hash(key);
29102921
if (hash == -1)
29112922
return -1;
2912-
mp = (PyDictObject *)op;
29132923
ix = _Py_dict_lookup(mp, key, hash, &old_value);
29142924
if (ix == DKIX_ERROR) {
29152925
return -1;
@@ -2951,16 +2961,16 @@ _PyDict_DelItemIf(PyObject *op, PyObject *key,
29512961
static void
29522962
clear_lock_held(PyObject *op)
29532963
{
2954-
PyDictObject *mp;
2964+
if (!PyDict_Check(op)) {
2965+
return;
2966+
}
2967+
PyDictObject *mp = (PyDictObject *)op;
2968+
assert(can_modify_dict(mp));
2969+
29552970
PyDictKeysObject *oldkeys;
29562971
PyDictValues *oldvalues;
29572972
Py_ssize_t i, n;
29582973

2959-
ASSERT_DICT_LOCKED(op);
2960-
2961-
if (!PyDict_Check(op))
2962-
return;
2963-
mp = ((PyDictObject *)op);
29642974
oldkeys = mp->ma_keys;
29652975
oldvalues = mp->ma_values;
29662976
if (oldkeys == Py_EMPTY_KEYS) {
@@ -3106,8 +3116,7 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash,
31063116
PyObject **result)
31073117
{
31083118
assert(PyDict_Check(mp));
3109-
3110-
ASSERT_DICT_LOCKED(mp);
3119+
assert(can_modify_dict(mp));
31113120

31123121
if (mp->ma_used == 0) {
31133122
if (result) {
@@ -3149,8 +3158,6 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash,
31493158
static int
31503159
pop_lock_held(PyObject *op, PyObject *key, PyObject **result)
31513160
{
3152-
ASSERT_DICT_LOCKED(op);
3153-
31543161
if (!PyDict_Check(op)) {
31553162
if (result) {
31563163
*result = NULL;
@@ -3159,6 +3166,7 @@ pop_lock_held(PyObject *op, PyObject *key, PyObject **result)
31593166
return -1;
31603167
}
31613168
PyDictObject *dict = (PyDictObject *)op;
3169+
assert(can_modify_dict(dict));
31623170

31633171
if (dict->ma_used == 0) {
31643172
if (result) {
@@ -3361,9 +3369,9 @@ dict_iter_exit:;
33613369
}
33623370
else if (PyFrozenDict_CheckExact(d)) {
33633371
while ((key = PyIter_Next(it)) != NULL) {
3364-
// anydict_setitem_take2 consumes a reference to key
3365-
status = anydict_setitem_take2((PyDictObject *)d,
3366-
key, Py_NewRef(value));
3372+
// setitem_take2_lock_held consumes a reference to key
3373+
status = setitem_take2_lock_held((PyDictObject *)d,
3374+
key, Py_NewRef(value));
33673375
if (status < 0) {
33683376
assert(PyErr_Occurred());
33693377
goto Fail;
@@ -3933,7 +3941,7 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
39333941
static int
39343942
dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override)
39353943
{
3936-
ASSERT_DICT_LOCKED(mp);
3944+
assert(can_modify_dict(mp));
39373945
ASSERT_DICT_LOCKED(other);
39383946

39393947
if (other == mp || other->ma_used == 0)
@@ -4474,15 +4482,14 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
44744482
Py_hash_t hash;
44754483
Py_ssize_t ix;
44764484

4477-
ASSERT_DICT_LOCKED(d);
4478-
44794485
if (!PyDict_Check(d)) {
44804486
PyErr_BadInternalCall();
44814487
if (result) {
44824488
*result = NULL;
44834489
}
44844490
return -1;
44854491
}
4492+
assert(can_modify_dict((PyDictObject*)d));
44864493

44874494
hash = _PyObject_HashFast(key);
44884495
if (hash == -1) {
@@ -4657,11 +4664,11 @@ static PyObject *
46574664
dict_popitem_impl(PyDictObject *self)
46584665
/*[clinic end generated code: output=e65fcb04420d230d input=ef28b4da5f0f762e]*/
46594666
{
4667+
assert(can_modify_dict(self));
4668+
46604669
Py_ssize_t i, j;
46614670
PyObject *res;
46624671

4663-
ASSERT_DICT_LOCKED(self);
4664-
46654672
/* Allocate the result tuple before checking the size. Believe it
46664673
* or not, this allocation could trigger a garbage collection which
46674674
* could empty the dict, so if we checked the size first and that
@@ -4799,11 +4806,12 @@ _PyDict_SizeOf_LockHeld(PyDictObject *mp)
47994806
}
48004807

48014808
void
4802-
_PyDict_ClearKeysVersionLockHeld(PyObject *mp)
4809+
_PyDict_ClearKeysVersionLockHeld(PyObject *op)
48034810
{
4804-
ASSERT_DICT_LOCKED(mp);
4811+
PyDictObject *mp = _PyAnyDict_CAST(op);
4812+
assert(can_modify_dict(mp));
48054813

4806-
FT_ATOMIC_STORE_UINT32_RELAXED(((PyDictObject *)mp)->ma_keys->dk_version, 0);
4814+
FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0);
48074815
}
48084816

48094817
Py_ssize_t

0 commit comments

Comments
 (0)