@@ -296,16 +296,20 @@ static inline int
296296can_modify_dict (PyDictObject * mp )
297297{
298298 if (PyFrozenDict_Check (mp )) {
299+ // gh-151722: A frozendict must not be tracked by the GC
300+ // when it's being modified.
301+ assert (!_PyObject_GC_IS_TRACKED (mp ));
302+
299303 // No locking required to modify a newly created frozendict
300304 // since it's only accessible from the current thread.
301- return PyUnstable_Object_IsUniquelyReferenced (_PyObject_CAST (mp ));
305+ assert ( PyUnstable_Object_IsUniquelyReferenced (_PyObject_CAST (mp ) ));
302306 }
303307 else {
304308 // Locking is only required if the dictionary is not
305309 // uniquely referenced.
306310 ASSERT_DICT_LOCKED (mp );
307- return 1 ;
308311 }
312+ return 1 ;
309313}
310314#endif
311315
@@ -902,7 +906,7 @@ free_values(PyDictValues *values, bool use_qsbr)
902906static inline PyObject *
903907new_dict_impl (PyDictObject * mp , PyDictKeysObject * keys ,
904908 PyDictValues * values , Py_ssize_t used ,
905- int free_values_on_failure , int frozendict )
909+ int free_values_on_failure , int frozendict , int gc_track )
906910{
907911 assert (keys != NULL );
908912 if (mp == NULL ) {
@@ -921,12 +925,14 @@ new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
921925 ((PyFrozenDictObject * )mp )-> ma_hash = -1 ;
922926 }
923927 ASSERT_CONSISTENT (mp );
924- _PyObject_GC_TRACK (mp );
928+ if (gc_track ) {
929+ _PyObject_GC_TRACK (mp );
930+ }
925931 return (PyObject * )mp ;
926932}
927933
928934/* Consumes a reference to the keys object */
929- static PyObject *
935+ static PyObject *
930936new_dict (PyDictKeysObject * keys , PyDictValues * values ,
931937 Py_ssize_t used , int free_values_on_failure )
932938{
@@ -936,16 +942,30 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values,
936942 }
937943 assert (mp == NULL || Py_IS_TYPE (mp , & PyDict_Type ));
938944
939- return new_dict_impl (mp , keys , values , used , free_values_on_failure , 0 );
945+ return new_dict_impl (mp , keys , values , used , free_values_on_failure , 0 , 1 );
940946}
941947
942948/* Consumes a reference to the keys object */
943- static PyObject *
944- new_frozendict (PyDictKeysObject * keys , PyDictValues * values ,
945- Py_ssize_t used , int free_values_on_failure )
949+ static PyObject *
950+ new_dict_untracked (PyDictKeysObject * keys , PyDictValues * values ,
951+ Py_ssize_t used , int free_values_on_failure )
952+ {
953+ PyDictObject * mp = _Py_FREELIST_POP (PyDictObject , dicts );
954+ if (mp == NULL ) {
955+ mp = PyObject_GC_New (PyDictObject , & PyDict_Type );
956+ }
957+ assert (mp == NULL || Py_IS_TYPE (mp , & PyDict_Type ));
958+
959+ return new_dict_impl (mp , keys , values , used , free_values_on_failure , 0 , 0 );
960+ }
961+
962+ /* Consumes a reference to the keys object */
963+ static PyObject *
964+ new_frozendict_untracked (PyDictKeysObject * keys , PyDictValues * values ,
965+ Py_ssize_t used , int free_values_on_failure )
946966{
947967 PyDictObject * mp = PyObject_GC_New (PyDictObject , & PyFrozenDict_Type );
948- return new_dict_impl (mp , keys , values , used , free_values_on_failure , 1 );
968+ return new_dict_impl (mp , keys , values , used , free_values_on_failure , 1 , 0 );
949969}
950970
951971static PyObject *
@@ -3427,7 +3447,6 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34273447 }
34283448 if (PyFrozenDict_Check (d )) {
34293449 assert (can_modify_dict ((PyDictObject * )d ));
3430- assert (!_PyObject_GC_IS_TRACKED (d ));
34313450 }
34323451
34333452 if (PyDict_CheckExact (d )) {
@@ -4032,6 +4051,7 @@ merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override)
40324051 assert (d != NULL );
40334052 assert (PyAnyDict_Check (d ));
40344053 assert (seq2 != NULL );
4054+ assert (can_modify_dict ((PyDictObject * )d ));
40354055
40364056 it = PyObject_GetIter (seq2 );
40374057 if (it == NULL )
@@ -4233,11 +4253,13 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
42334253 assert (0 <= override && override <= 2 );
42344254
42354255 PyDictObject * mp = _PyAnyDict_CAST (a );
4256+
42364257 int res = 0 ;
42374258 if (PyAnyDict_Check (b ) && (Py_TYPE (b )-> tp_iter == dict_iter )) {
42384259 PyDictObject * other = (PyDictObject * )b ;
42394260 int res ;
42404261 Py_BEGIN_CRITICAL_SECTION2 (a , b );
4262+ assert (can_modify_dict (mp ));
42414263 res = dict_dict_merge ((PyDictObject * )a , other , override , dupkey );
42424264 ASSERT_CONSISTENT (a );
42434265 Py_END_CRITICAL_SECTION2 ();
@@ -4246,6 +4268,8 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
42464268 else {
42474269 /* Do it the generic, slower way */
42484270 Py_BEGIN_CRITICAL_SECTION (a );
4271+ assert (can_modify_dict (mp ));
4272+
42494273 PyObject * keys = PyMapping_Keys (b );
42504274 PyObject * iter ;
42514275 PyObject * key , * value ;
@@ -4338,9 +4362,7 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43384362 }
43394363
43404364 int res = dict_merge (a , b , override , dupkey );
4341- if (PyDict_Check (a )) {
4342- assert (_PyObject_GC_IS_TRACKED (a ));
4343- }
4365+ assert (_PyObject_GC_IS_TRACKED (a ));
43444366 return res ;
43454367}
43464368
@@ -4398,7 +4420,7 @@ copy_values(PyDictValues *values)
43984420}
43994421
44004422static PyObject *
4401- copy_lock_held (PyObject * o , int as_frozendict )
4423+ copy_lock_held_untracked (PyObject * o , int as_frozendict )
44024424{
44034425 PyObject * copy ;
44044426 PyDictObject * mp ;
@@ -4411,12 +4433,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44114433 mp = (PyDictObject * )o ;
44124434 if (mp -> ma_used == 0 ) {
44134435 /* The dict is empty; just return a new dict. */
4436+ PyObject * d ;
44144437 if (as_frozendict ) {
4415- return PyFrozenDict_New ( NULL );
4438+ d = frozendict_new_untracked ( & PyFrozenDict_Type );
44164439 }
44174440 else {
4418- return PyDict_New ( );
4441+ d = dict_new_untracked ( & PyDict_Type );
44194442 }
4443+ assert (!_PyObject_GC_IS_TRACKED (d ));
4444+ return d ;
44204445 }
44214446
44224447 if (_PyDict_HasSplitTable (mp )) {
@@ -4448,7 +4473,7 @@ copy_lock_held(PyObject *o, int as_frozendict)
44484473 PyFrozenDictObject * frozen = (PyFrozenDictObject * )split_copy ;
44494474 frozen -> ma_hash = -1 ;
44504475 }
4451- _PyObject_GC_TRACK ( split_copy );
4476+ assert (! _PyObject_GC_IS_TRACKED ( split_copy ) );
44524477 return (PyObject * )split_copy ;
44534478 }
44544479
@@ -4476,10 +4501,10 @@ copy_lock_held(PyObject *o, int as_frozendict)
44764501 }
44774502 PyDictObject * new ;
44784503 if (as_frozendict ) {
4479- new = (PyDictObject * )new_frozendict (keys , NULL , 0 , 0 );
4504+ new = (PyDictObject * )new_frozendict_untracked (keys , NULL , 0 , 0 );
44804505 }
44814506 else {
4482- new = (PyDictObject * )new_dict (keys , NULL , 0 , 0 );
4507+ new = (PyDictObject * )new_dict_untracked (keys , NULL , 0 , 0 );
44834508 }
44844509 if (new == NULL ) {
44854510 /* In case of an error, new_dict()/new_frozendict() takes care of
@@ -4489,14 +4514,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44894514
44904515 new -> ma_used = mp -> ma_used ;
44914516 ASSERT_CONSISTENT (new );
4517+ assert (!_PyObject_GC_IS_TRACKED (new ));
44924518 return (PyObject * )new ;
44934519 }
44944520
44954521 if (as_frozendict ) {
4496- copy = PyFrozenDict_New ( NULL );
4522+ copy = frozendict_new_untracked ( & PyFrozenDict_Type );
44974523 }
44984524 else {
4499- copy = PyDict_New ( );
4525+ copy = dict_new_untracked ( & PyDict_Type );
45004526 }
45014527 if (copy == NULL )
45024528 return NULL ;
@@ -4505,9 +4531,7 @@ copy_lock_held(PyObject *o, int as_frozendict)
45054531 return NULL ;
45064532 }
45074533
4508- if (PyDict_Check (copy )) {
4509- assert (_PyObject_GC_IS_TRACKED (copy ));
4510- }
4534+ assert (!_PyObject_GC_IS_TRACKED (copy ));
45114535 return copy ;
45124536}
45134537
@@ -4521,25 +4545,28 @@ PyDict_Copy(PyObject *o)
45214545
45224546 PyObject * res ;
45234547 Py_BEGIN_CRITICAL_SECTION (o );
4524- res = copy_lock_held (o , 0 );
4548+ res = copy_lock_held_untracked (o , 0 );
45254549 Py_END_CRITICAL_SECTION ();
4550+ if (res != NULL ) {
4551+ _PyObject_GC_TRACK (res );
4552+ }
45264553 return res ;
45274554}
45284555
45294556// Similar to PyDict_Copy(), but return a frozendict if the argument
45304557// is a frozendict.
45314558static PyObject *
4532- anydict_copy (PyObject * o )
4559+ anydict_copy_untracked (PyObject * o )
45334560{
45344561 assert (PyAnyDict_Check (o ));
45354562
45364563 PyObject * res ;
45374564 if (PyFrozenDict_Check (o )) {
4538- res = copy_lock_held (o , 1 );
4565+ res = copy_lock_held_untracked (o , 1 );
45394566 }
45404567 else {
45414568 Py_BEGIN_CRITICAL_SECTION (o );
4542- res = copy_lock_held (o , 0 );
4569+ res = copy_lock_held_untracked (o , 0 );
45434570 Py_END_CRITICAL_SECTION ();
45444571 }
45454572 return res ;
@@ -4554,13 +4581,16 @@ _PyDict_CopyAsDict(PyObject *o)
45544581
45554582 PyObject * res ;
45564583 if (PyFrozenDict_Check (o )) {
4557- res = copy_lock_held (o , 0 );
4584+ res = copy_lock_held_untracked (o , 0 );
45584585 }
45594586 else {
45604587 Py_BEGIN_CRITICAL_SECTION (o );
4561- res = copy_lock_held (o , 0 );
4588+ res = copy_lock_held_untracked (o , 0 );
45624589 Py_END_CRITICAL_SECTION ();
45634590 }
4591+ if (res != NULL ) {
4592+ _PyObject_GC_TRACK (res );
4593+ }
45644594 return res ;
45654595}
45664596
@@ -5113,14 +5143,15 @@ _PyDict_Or(PyObject *self, PyObject *other)
51135143 if (!PyAnyDict_Check (self ) || !PyAnyDict_Check (other )) {
51145144 Py_RETURN_NOTIMPLEMENTED ;
51155145 }
5116- PyObject * new = anydict_copy (self );
5146+ PyObject * new = anydict_copy_untracked (self );
51175147 if (new == NULL ) {
51185148 return NULL ;
51195149 }
51205150 if (dict_update_arg (new , other )) {
51215151 Py_DECREF (new );
51225152 return NULL ;
51235153 }
5154+ _PyObject_GC_TRACK (new );
51245155 return new ;
51255156}
51265157
@@ -5308,7 +5339,6 @@ dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds
53085339 if (self == NULL ) {
53095340 return NULL ;
53105341 }
5311- assert (!_PyObject_GC_IS_TRACKED (self ));
53125342 _PyObject_GC_TRACK (self );
53135343 return self ;
53145344}
@@ -5390,7 +5420,7 @@ frozendict_vectorcall(PyObject *type, PyObject * const*args,
53905420 }
53915421 }
53925422 }
5393- assert (! _PyObject_GC_IS_TRACKED ( self ));
5423+
53945424 _PyObject_GC_TRACK (self );
53955425 return self ;
53965426}
@@ -6709,10 +6739,12 @@ dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
67096739 ASSERT_DICT_LOCKED (d1 );
67106740 ASSERT_DICT_LOCKED (d2 );
67116741
6712- PyObject * temp_dict = copy_lock_held (d1 , 0 );
6742+ PyObject * temp_dict = copy_lock_held_untracked (d1 , 0 );
67136743 if (temp_dict == NULL ) {
67146744 return NULL ;
67156745 }
6746+ _PyObject_GC_TRACK (temp_dict );
6747+
67166748 PyObject * result_set = PySet_New (NULL );
67176749 if (result_set == NULL ) {
67186750 Py_CLEAR (temp_dict );
@@ -8417,7 +8449,6 @@ frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
84178449 assert (kwds == NULL );
84188450 }
84198451
8420- assert (!_PyObject_GC_IS_TRACKED (d ));
84218452 _PyObject_GC_TRACK (d );
84228453 return d ;
84238454}
@@ -8462,7 +8493,11 @@ frozendict_copy_impl(PyFrozenDictObject *self)
84628493 return Py_NewRef (self );
84638494 }
84648495
8465- return anydict_copy ((PyObject * )self );
8496+ PyObject * copy = anydict_copy_untracked ((PyObject * )self );
8497+ if (copy != NULL ) {
8498+ _PyObject_GC_TRACK (copy );
8499+ }
8500+ return copy ;
84668501}
84678502
84688503
0 commit comments