Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6783070

Browse files
committed
Make PyDict_Next safe to use for loops that merely modify the values
associated with existing dict keys. This is a variant of part of Michael Hudson's patch #409864 "lazy fix for Pings bizarre scoping crash".
1 parent 66b0e9c commit 6783070

1 file changed

Lines changed: 32 additions & 8 deletions

File tree

Objects/dictobject.c

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ The value ma_fill is the number of non-NULL keys (sum of Active and Dummy);
9292
ma_used is the number of non-NULL, non-dummy keys (== the number of non-NULL
9393
values == the number of Active items).
9494
To avoid slowing down lookups on a near-full table, we resize the table when
95-
it is more than half filled.
95+
it's two-thirds full.
9696
*/
9797
typedef struct dictobject dictobject;
9898
struct dictobject {
@@ -486,13 +486,15 @@ PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value)
486486
if (hash == -1)
487487
return -1;
488488
}
489-
/* if fill >= 2/3 size, double in size */
490-
if (mp->ma_fill*3 >= mp->ma_size*2) {
491-
if (dictresize(mp, mp->ma_used*2) != 0) {
492-
if (mp->ma_fill+1 > mp->ma_size)
493-
return -1;
494-
}
495-
}
489+
/* If fill >= 2/3 size, adjust size. Normally, this doubles the
490+
* size, but it's also possible for the dict to shrink (if ma_fill is
491+
* much larger than ma_used, meaning a lot of dict keys have been
492+
* deleted).
493+
* CAUTION: this resize logic must match the logic in PyDict_Next.
494+
*/
495+
if (mp->ma_fill*3 >= mp->ma_size*2 &&
496+
dictresize(mp, mp->ma_used*2) != 0)
497+
return -1;
496498
Py_INCREF(value);
497499
Py_INCREF(key);
498500
insertdict(mp, key, hash, value);
@@ -562,6 +564,11 @@ PyDict_Clear(PyObject *op)
562564
PyMem_DEL(table);
563565
}
564566

567+
/* CAUTION: In general, it isn't safe to use PyDict_Next in a loop that
568+
* mutates the dict. One exception: it is safe if the loop merely changes
569+
* the values associated with the keys (but doesn't insert new keys or
570+
* delete keys), via PyDict_SetItem().
571+
*/
565572
int
566573
PyDict_Next(PyObject *op, int *ppos, PyObject **pkey, PyObject **pvalue)
567574
{
@@ -573,6 +580,23 @@ PyDict_Next(PyObject *op, int *ppos, PyObject **pkey, PyObject **pvalue)
573580
i = *ppos;
574581
if (i < 0)
575582
return 0;
583+
584+
/* A hack to support loops that merely change values.
585+
* The problem: PyDict_SetItem() can either grow or shrink the dict
586+
* even when passed a key that's already in the dict. This was a
587+
* repeated source of subtle bugs, bad enough to justify a hack here.
588+
* Approach: If this is the first time PyDict_Next() is being called
589+
* (i==0), first figure out whether PyDict_SetItem() *will* change the
590+
* size, and if so get it changed before we start passing out internal
591+
* indices.
592+
*/
593+
if (i == 0) {
594+
/* This must be a clone of PyDict_SetItem's resize logic. */
595+
if (mp->ma_fill*3 >= mp->ma_size*2 &&
596+
dictresize(mp, mp->ma_used*2) != 0)
597+
return -1;
598+
}
599+
576600
while (i < mp->ma_size && mp->ma_table[i].me_value == NULL)
577601
i++;
578602
*ppos = i+1;

0 commit comments

Comments
 (0)