Kaydet (Commit) 0c6010be authored tarafından Tim Peters's avatar Tim Peters

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.
üst a5ca7dd7
...@@ -26,3 +26,18 @@ x2 = BadDictKey() ...@@ -26,3 +26,18 @@ x2 = BadDictKey()
d[x1] = 1 d[x1] = 1
d[x2] = 2 d[x2] = 2
print "No exception passed through." print "No exception passed through."
# Dict resizing bug, found by Jack Jansen in 2.2 CVS development.
# This version got an assert failure in debug build, infinite loop in
# release build. Unfortunately, provoking this kind of stuff requires
# a mix of inserts and deletes hitting exactly the right hash codes in
# exactly the right order, and I can't think of a randomized approach
# that would be *likely* to hit a failing case in reasonable time.
d = {}
for i in range(5):
d[i] = i
for i in range(5):
del d[i]
for i in range(5, 9): # i==8 was the problem
d[i] = i
...@@ -395,14 +395,15 @@ actually be smaller than the old one. ...@@ -395,14 +395,15 @@ actually be smaller than the old one.
static int static int
dictresize(dictobject *mp, int minused) dictresize(dictobject *mp, int minused)
{ {
register int newsize, newpoly; int newsize, newpoly;
register dictentry *oldtable = mp->ma_table; dictentry *oldtable, *newtable, *ep;
register dictentry *newtable; int i;
register dictentry *ep; int is_oldtable_malloced;
register int i; dictentry small_copy[MINSIZE];
assert(minused >= 0); assert(minused >= 0);
assert(oldtable != NULL);
/* Find the smallest table size > minused, and its poly[] entry. */
newpoly = 0; newpoly = 0;
newsize = MINSIZE; newsize = MINSIZE;
for (i = 0; i < sizeof(polys)/sizeof(polys[0]); ++i) { for (i = 0; i < sizeof(polys)/sizeof(polys[0]); ++i) {
...@@ -419,10 +420,26 @@ dictresize(dictobject *mp, int minused) ...@@ -419,10 +420,26 @@ dictresize(dictobject *mp, int minused)
PyErr_NoMemory(); PyErr_NoMemory();
return -1; return -1;
} }
/* Get space for a new table. */
oldtable = mp->ma_table;
assert(oldtable != NULL);
is_oldtable_malloced = oldtable != mp->ma_smalltable;
if (newsize == MINSIZE) { if (newsize == MINSIZE) {
/* Either a large table is shrinking, or we can't get any
smaller. */
newtable = mp->ma_smalltable; newtable = mp->ma_smalltable;
if (newtable == oldtable) if (newtable == oldtable) {
if (mp->ma_fill < mp->ma_size)
return 0; return 0;
/* The small table is entirely full. We're not
going to resise it, but need to rebuild it
anyway to purge old dummy entries. */
assert(mp->ma_fill > mp->ma_used); /* a dummy exists */
memcpy(small_copy, oldtable, sizeof(small_copy));
oldtable = small_copy;
}
} }
else { else {
newtable = PyMem_NEW(dictentry, newsize); newtable = PyMem_NEW(dictentry, newsize);
...@@ -431,6 +448,8 @@ dictresize(dictobject *mp, int minused) ...@@ -431,6 +448,8 @@ dictresize(dictobject *mp, int minused)
return -1; return -1;
} }
} }
/* Make the dict empty, using the new table. */
assert(newtable != oldtable); assert(newtable != oldtable);
mp->ma_table = newtable; mp->ma_table = newtable;
mp->ma_size = newsize; mp->ma_size = newsize;
...@@ -455,7 +474,7 @@ dictresize(dictobject *mp, int minused) ...@@ -455,7 +474,7 @@ dictresize(dictobject *mp, int minused)
/* else key == value == NULL: nothing to do */ /* else key == value == NULL: nothing to do */
} }
if (oldtable != mp->ma_smalltable) if (is_oldtable_malloced)
PyMem_DEL(oldtable); PyMem_DEL(oldtable);
return 0; return 0;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment