Skip to content

Commit 5cc70c9

Browse files
committed
Merge 3.6
2 parents 944dbc6 + 3d3f264 commit 5cc70c9

4 files changed

Lines changed: 70 additions & 5 deletions

File tree

Lib/test/test_dict.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,36 @@ def test_splittable_popitem(self):
933933
self.assertEqual(list(a), ['x', 'y'])
934934
self.assertEqual(list(b), ['x', 'y', 'z'])
935935

936+
@support.cpython_only
937+
def test_splittable_setattr_after_pop(self):
938+
"""setattr() must not convert combined table into split table."""
939+
# Issue 28147
940+
import _testcapi
941+
942+
class C:
943+
pass
944+
a = C()
945+
946+
a.a = 1
947+
self.assertTrue(_testcapi.dict_hassplittable(a.__dict__))
948+
949+
# dict.pop() convert it to combined table
950+
a.__dict__.pop('a')
951+
self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
952+
953+
# But C should not convert a.__dict__ to split table again.
954+
a.a = 1
955+
self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
956+
957+
# Same for popitem()
958+
a = C()
959+
a.a = 2
960+
self.assertTrue(_testcapi.dict_hassplittable(a.__dict__))
961+
a.__dict__.popitem()
962+
self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
963+
a.a = 3
964+
self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
965+
936966
def test_iterator_pickling(self):
937967
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
938968
data = {1:"a", 2:"b", 3:"c"}

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.7.0 alpha 1
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #28147: Fix a memory leak in split-table dictionaries: setattr()
14+
must not convert combined table into split table. Patch written by INADA
15+
Naoki.
16+
1317
- Issue #28739: f-string expressions no longer accepted as docstrings and
1418
by ast.literal_eval() even if they do not include expressions.
1519

Modules/_testcapimodule.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,19 @@ dict_getitem_knownhash(PyObject *self, PyObject *args)
259259
return result;
260260
}
261261

262+
static PyObject*
263+
dict_hassplittable(PyObject *self, PyObject *arg)
264+
{
265+
if (!PyDict_Check(arg)) {
266+
PyErr_Format(PyExc_TypeError,
267+
"dict_hassplittable() argument must be dict, not '%s'",
268+
arg->ob_type->tp_name);
269+
return NULL;
270+
}
271+
272+
return PyBool_FromLong(_PyDict_HasSplitTable((PyDictObject*)arg));
273+
}
274+
262275
/* Issue #4701: Check that PyObject_Hash implicitly calls
263276
* PyType_Ready if it hasn't already been called
264277
*/
@@ -4016,6 +4029,7 @@ static PyMethodDef TestMethods[] = {
40164029
{"test_list_api", (PyCFunction)test_list_api, METH_NOARGS},
40174030
{"test_dict_iteration", (PyCFunction)test_dict_iteration,METH_NOARGS},
40184031
{"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS},
4032+
{"dict_hassplittable", dict_hassplittable, METH_O},
40194033
{"test_lazy_hash_inheritance", (PyCFunction)test_lazy_hash_inheritance,METH_NOARGS},
40204034
{"test_long_api", (PyCFunction)test_long_api, METH_NOARGS},
40214035
{"test_xincref_doesnt_leak",(PyCFunction)test_xincref_doesnt_leak, METH_NOARGS},

Objects/dictobject.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ After resizing a table is always combined,
12141214
but can be resplit by make_keys_shared().
12151215
*/
12161216
static int
1217-
dictresize(PyDictObject *mp, Py_ssize_t minused)
1217+
dictresize(PyDictObject *mp, Py_ssize_t minsize)
12181218
{
12191219
Py_ssize_t newsize, numentries;
12201220
PyDictKeysObject *oldkeys;
@@ -1223,7 +1223,7 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)
12231223

12241224
/* Find the smallest table size > minused. */
12251225
for (newsize = PyDict_MINSIZE;
1226-
newsize <= minused && newsize > 0;
1226+
newsize < minsize && newsize > 0;
12271227
newsize <<= 1)
12281228
;
12291229
if (newsize <= 0) {
@@ -1244,6 +1244,8 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)
12441244
mp->ma_keys = oldkeys;
12451245
return -1;
12461246
}
1247+
// New table must be large enough.
1248+
assert(mp->ma_keys->dk_usable >= mp->ma_used);
12471249
if (oldkeys->dk_lookup == lookdict)
12481250
mp->ma_keys->dk_lookup = lookdict;
12491251

@@ -4270,10 +4272,25 @@ _PyObjectDict_SetItem(PyTypeObject *tp, PyObject **dictptr,
42704272
CACHED_KEYS(tp) = NULL;
42714273
DK_DECREF(cached);
42724274
}
4273-
} else {
4275+
}
4276+
else {
4277+
int was_shared = cached == ((PyDictObject *)dict)->ma_keys;
42744278
res = PyDict_SetItem(dict, key, value);
4275-
if (cached != ((PyDictObject *)dict)->ma_keys) {
4276-
/* Either update tp->ht_cached_keys or delete it */
4279+
if (was_shared && cached != ((PyDictObject *)dict)->ma_keys) {
4280+
/* PyDict_SetItem() may call dictresize and convert split table
4281+
* into combined table. In such case, convert it to split
4282+
* table again and update type's shared key only when this is
4283+
* the only dict sharing key with the type.
4284+
*
4285+
* This is to allow using shared key in class like this:
4286+
*
4287+
* class C:
4288+
* def __init__(self):
4289+
* # one dict resize happens
4290+
* self.a, self.b, self.c = 1, 2, 3
4291+
* self.d, self.e, self.f = 4, 5, 6
4292+
* a = C()
4293+
*/
42774294
if (cached->dk_refcnt == 1) {
42784295
CACHED_KEYS(tp) = make_keys_shared(dict);
42794296
}

0 commit comments

Comments
 (0)