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

Skip to content

Commit 0c6010b

Browse files
committed
Jack Jansen hit a bug in the new dict code, reported on python-dev.
dictresize() was too aggressive about never ever resizing small dicts. If a small dict is entirely full, it needs to rebuild it despite that it won't actually resize it, in order to purge old dummy entries thus creating at least one virgin slot (lookdict assumes at least one such exists). Also took the opportunity to add some high-level comments to dictresize.
1 parent a5ca7dd commit 0c6010b

2 files changed

Lines changed: 43 additions & 9 deletions

File tree

Lib/test/test_operations.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,18 @@ def __cmp__(self, other):
2626
d[x1] = 1
2727
d[x2] = 2
2828
print "No exception passed through."
29+
30+
# Dict resizing bug, found by Jack Jansen in 2.2 CVS development.
31+
# This version got an assert failure in debug build, infinite loop in
32+
# release build. Unfortunately, provoking this kind of stuff requires
33+
# a mix of inserts and deletes hitting exactly the right hash codes in
34+
# exactly the right order, and I can't think of a randomized approach
35+
# that would be *likely* to hit a failing case in reasonable time.
36+
37+
d = {}
38+
for i in range(5):
39+
d[i] = i
40+
for i in range(5):
41+
del d[i]
42+
for i in range(5, 9): # i==8 was the problem
43+
d[i] = i

Objects/dictobject.c

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -395,14 +395,15 @@ actually be smaller than the old one.
395395
static int
396396
dictresize(dictobject *mp, int minused)
397397
{
398-
register int newsize, newpoly;
399-
register dictentry *oldtable = mp->ma_table;
400-
register dictentry *newtable;
401-
register dictentry *ep;
402-
register int i;
398+
int newsize, newpoly;
399+
dictentry *oldtable, *newtable, *ep;
400+
int i;
401+
int is_oldtable_malloced;
402+
dictentry small_copy[MINSIZE];
403403

404404
assert(minused >= 0);
405-
assert(oldtable != NULL);
405+
406+
/* Find the smallest table size > minused, and its poly[] entry. */
406407
newpoly = 0;
407408
newsize = MINSIZE;
408409
for (i = 0; i < sizeof(polys)/sizeof(polys[0]); ++i) {
@@ -419,10 +420,26 @@ dictresize(dictobject *mp, int minused)
419420
PyErr_NoMemory();
420421
return -1;
421422
}
423+
424+
/* Get space for a new table. */
425+
oldtable = mp->ma_table;
426+
assert(oldtable != NULL);
427+
is_oldtable_malloced = oldtable != mp->ma_smalltable;
428+
422429
if (newsize == MINSIZE) {
430+
/* Either a large table is shrinking, or we can't get any
431+
smaller. */
423432
newtable = mp->ma_smalltable;
424-
if (newtable == oldtable)
425-
return 0;
433+
if (newtable == oldtable) {
434+
if (mp->ma_fill < mp->ma_size)
435+
return 0;
436+
/* The small table is entirely full. We're not
437+
going to resise it, but need to rebuild it
438+
anyway to purge old dummy entries. */
439+
assert(mp->ma_fill > mp->ma_used); /* a dummy exists */
440+
memcpy(small_copy, oldtable, sizeof(small_copy));
441+
oldtable = small_copy;
442+
}
426443
}
427444
else {
428445
newtable = PyMem_NEW(dictentry, newsize);
@@ -431,6 +448,8 @@ dictresize(dictobject *mp, int minused)
431448
return -1;
432449
}
433450
}
451+
452+
/* Make the dict empty, using the new table. */
434453
assert(newtable != oldtable);
435454
mp->ma_table = newtable;
436455
mp->ma_size = newsize;
@@ -455,7 +474,7 @@ dictresize(dictobject *mp, int minused)
455474
/* else key == value == NULL: nothing to do */
456475
}
457476

458-
if (oldtable != mp->ma_smalltable)
477+
if (is_oldtable_malloced)
459478
PyMem_DEL(oldtable);
460479
return 0;
461480
}

0 commit comments

Comments
 (0)