@@ -4148,8 +4148,11 @@ dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override, PyObject **
41484148 STORE_USED (mp , other -> ma_used );
41494149 ASSERT_CONSISTENT (mp );
41504150
4151- if (_PyObject_GC_IS_TRACKED (other ) && !_PyObject_GC_IS_TRACKED (mp )) {
4152- /* Maintain tracking. */
4151+ if (_PyObject_GC_IS_TRACKED (other ) && !_PyObject_GC_IS_TRACKED (mp )
4152+ && !PyFrozenDict_Check (mp )) {
4153+ /* Maintain tracking. A frozendict is left untracked while it is
4154+ built and GC-tracked once by its constructor when fully
4155+ built, so don't track it here. */
41534156 _PyObject_GC_TRACK (mp );
41544157 }
41554158
@@ -5357,8 +5360,7 @@ frozendict_vectorcall(PyObject *type, PyObject * const*args,
53575360 }
53585361 }
53595362 }
5360- /* Track only once fully built. Only ever reached for the exact frozendict
5361- type, so self is still untracked here. */
5363+ assert (!_PyObject_GC_IS_TRACKED (self ));
53625364 _PyObject_GC_TRACK (self );
53635365 return self ;
53645366}
@@ -8378,17 +8380,28 @@ frozendict_hash(PyObject *op)
83788380}
83798381
83808382
8381- /* Allocate an empty, GC-untracked frozendict. Staying untracked while it is
8382- filled keeps a half-built frozendict so another thread can't observe it
8383- changing. Callers must GC-track it once fully built. */
8383+ /* Allocate an empty, GC-untracked frozendict. Allocation never tracks the
8384+ object -- even for subclasses, whose tp_alloc is PyType_GenericAlloc -- so it
8385+ stays untracked for the whole construction and a half-built frozendict is kept
8386+ out of gc.get_objects(). The constructor GC-tracks it exactly once, when
8387+ fully built. */
83848388static PyObject *
83858389frozendict_new_untracked (PyTypeObject * type )
83868390{
8387- PyObject * d = dict_new_untracked (type );
8391+ assert (_PyType_IS_GC (type ));
8392+ PyObject * d = _PyType_AllocNoTrack (type , 0 );
83888393 if (d == NULL ) {
83898394 return NULL ;
83908395 }
8391- assert (can_modify_dict (_PyAnyDict_CAST (d )));
8396+ PyDictObject * mp = (PyDictObject * )d ;
8397+ mp -> ma_used = 0 ;
8398+ mp -> _ma_watcher_tag = 0 ;
8399+ // Py_EMPTY_KEYS is immortal, so it is not incref'd.
8400+ assert ((Py_EMPTY_KEYS )-> dk_refcnt == _Py_DICT_IMMORTAL_INITIAL_REFCNT );
8401+ mp -> ma_keys = Py_EMPTY_KEYS ;
8402+ mp -> ma_values = NULL ;
8403+ ASSERT_CONSISTENT (mp );
8404+ assert (can_modify_dict (mp ));
83928405 _PyFrozenDictObject_CAST (d )-> ma_hash = -1 ;
83938406 return d ;
83948407}
@@ -8411,10 +8424,8 @@ frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
84118424 assert (kwds == NULL );
84128425 }
84138426
8414- /* Track only once fully built. */
8415- if (!_PyObject_GC_IS_TRACKED (d )) {
8416- _PyObject_GC_TRACK (d );
8417- }
8427+ assert (!_PyObject_GC_IS_TRACKED (d ));
8428+ _PyObject_GC_TRACK (d );
84188429 return d ;
84198430}
84208431
0 commit comments