@@ -3454,7 +3454,7 @@ static int
34543454batch_dict_exact (PickleState * state , PicklerObject * self , PyObject * obj )
34553455{
34563456 PyObject * key = NULL , * value = NULL ;
3457- int i ;
3457+ int i , has_next ;
34583458 Py_ssize_t dict_size , ppos = 0 ;
34593459
34603460 const char mark_op = MARK ;
@@ -3471,9 +3471,16 @@ batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
34713471 Py_ssize_t total = 0 ;
34723472 do {
34733473 if (dict_size - total == 1 ) {
3474+ /* gh-146452: Prevent concurrent dict mutation from
3475+ invalidating the borrowed refs from PyDict_Next(). */
3476+ Py_BEGIN_CRITICAL_SECTION (obj );
34743477 PyDict_Next (obj , & ppos , & key , & value );
3475- Py_INCREF (key );
3476- Py_INCREF (value );
3478+ Py_XINCREF (key );
3479+ Py_XINCREF (value );
3480+ Py_END_CRITICAL_SECTION ();
3481+ if (key == NULL ) {
3482+ return 0 ;
3483+ }
34773484 if (save (state , self , key , 0 ) < 0 ) {
34783485 goto error ;
34793486 }
@@ -3491,9 +3498,17 @@ batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
34913498 i = 0 ;
34923499 if (_Pickler_Write (self , & mark_op , 1 ) < 0 )
34933500 return -1 ;
3494- while (PyDict_Next (obj , & ppos , & key , & value )) {
3495- Py_INCREF (key );
3496- Py_INCREF (value );
3501+ for (;;) {
3502+ /* gh-146452: same as above. */
3503+ Py_BEGIN_CRITICAL_SECTION (obj );
3504+ has_next = PyDict_Next (obj , & ppos , & key , & value );
3505+ if (has_next ) {
3506+ Py_INCREF (key );
3507+ Py_INCREF (value );
3508+ }
3509+ Py_END_CRITICAL_SECTION ();
3510+ if (!has_next )
3511+ break ;
34973512 if (save (state , self , key , 0 ) < 0 ) {
34983513 goto error ;
34993514 }
0 commit comments