Issue #28583: PyDict_SetDefault didn't combine split table when needed.

Patch by Xiang Zhang.
This commit is contained in:
INADA Naoki 2016-11-02 18:45:16 +09:00
parent 8567e58ae3
commit 93f26f794d
3 changed files with 67 additions and 17 deletions

View File

@ -851,6 +851,23 @@ class DictTest(unittest.TestCase):
return dicts return dicts
@support.cpython_only
def test_splittable_setdefault(self):
"""split table must be combined when setdefault()
breaks insertion order"""
a, b = self.make_shared_key_dict(2)
a['a'] = 1
size_a = sys.getsizeof(a)
a['b'] = 2
b.setdefault('b', 2)
size_b = sys.getsizeof(b)
b['a'] = 1
self.assertGreater(size_b, size_a)
self.assertEqual(list(a), ['x', 'y', 'z', 'a', 'b'])
self.assertEqual(list(b), ['x', 'y', 'z', 'b', 'a'])
@support.cpython_only @support.cpython_only
def test_splittable_del(self): def test_splittable_del(self):
"""split table must be combined when del d[k]""" """split table must be combined when del d[k]"""

View File

@ -10,6 +10,9 @@ What's New in Python 3.6.0 beta 4
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #28583: PyDict_SetDefault didn't combine split table when needed.
Patch by Xiang Zhang.
Library Library
------- -------

View File

@ -2757,58 +2757,88 @@ PyObject *
PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
{ {
PyDictObject *mp = (PyDictObject *)d; PyDictObject *mp = (PyDictObject *)d;
PyObject *val = NULL; PyObject *value;
Py_hash_t hash; Py_hash_t hash;
Py_ssize_t hashpos, ix; Py_ssize_t hashpos, ix;
PyDictKeyEntry *ep;
PyObject **value_addr; PyObject **value_addr;
if (!PyDict_Check(d)) { if (!PyDict_Check(d)) {
PyErr_BadInternalCall(); PyErr_BadInternalCall();
return NULL; return NULL;
} }
if (!PyUnicode_CheckExact(key) || if (!PyUnicode_CheckExact(key) ||
(hash = ((PyASCIIObject *) key)->hash) == -1) { (hash = ((PyASCIIObject *) key)->hash) == -1) {
hash = PyObject_Hash(key); hash = PyObject_Hash(key);
if (hash == -1) if (hash == -1)
return NULL; return NULL;
} }
if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) {
if (insertion_resize(mp) < 0)
return NULL;
}
ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, &hashpos); ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, &hashpos);
if (ix == DKIX_ERROR) if (ix == DKIX_ERROR)
return NULL; return NULL;
if (ix == DKIX_EMPTY || *value_addr == NULL) {
val = defaultobj; if (_PyDict_HasSplitTable(mp) &&
((ix >= 0 && *value_addr == NULL && mp->ma_used != ix) ||
(ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) {
if (insertion_resize(mp) < 0) {
return NULL;
}
find_empty_slot(mp, key, hash, &value_addr, &hashpos);
ix = DKIX_EMPTY;
}
if (ix == DKIX_EMPTY) {
PyDictKeyEntry *ep, *ep0;
value = defaultobj;
if (mp->ma_keys->dk_usable <= 0) { if (mp->ma_keys->dk_usable <= 0) {
/* Need to resize. */
if (insertion_resize(mp) < 0) { if (insertion_resize(mp) < 0) {
return NULL; return NULL;
} }
find_empty_slot(mp, key, hash, &value_addr, &hashpos); find_empty_slot(mp, key, hash, &value_addr, &hashpos);
} }
ix = mp->ma_keys->dk_nentries; ep0 = DK_ENTRIES(mp->ma_keys);
Py_INCREF(defaultobj); ep = &ep0[mp->ma_keys->dk_nentries];
dk_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
Py_INCREF(key); Py_INCREF(key);
MAINTAIN_TRACKING(mp, key, defaultobj); Py_INCREF(value);
dk_set_index(mp->ma_keys, hashpos, ix); MAINTAIN_TRACKING(mp, key, value);
ep = &DK_ENTRIES(mp->ma_keys)[ix];
ep->me_key = key; ep->me_key = key;
ep->me_hash = hash; ep->me_hash = hash;
if (mp->ma_values) { if (mp->ma_values) {
mp->ma_values[ix] = val; assert(mp->ma_values[mp->ma_keys->dk_nentries] == NULL);
mp->ma_values[mp->ma_keys->dk_nentries] = value;
} }
else { else {
ep->me_value = val; ep->me_value = value;
} }
mp->ma_keys->dk_usable--;
mp->ma_keys->dk_nentries++;
mp->ma_used++; mp->ma_used++;
mp->ma_version_tag = DICT_NEXT_VERSION(); mp->ma_version_tag = DICT_NEXT_VERSION();
assert(_PyDict_CheckConsistency(mp)); mp->ma_keys->dk_usable--;
mp->ma_keys->dk_nentries++;
assert(mp->ma_keys->dk_usable >= 0);
}
else if (*value_addr == NULL) {
value = defaultobj;
assert(_PyDict_HasSplitTable(mp));
assert(ix == mp->ma_used);
Py_INCREF(value);
MAINTAIN_TRACKING(mp, key, value);
*value_addr = value;
mp->ma_used++;
mp->ma_version_tag = DICT_NEXT_VERSION();
} }
else { else {
val = *value_addr; value = *value_addr;
} }
return val;
assert(_PyDict_CheckConsistency(mp));
return value;
} }
static PyObject * static PyObject *