summaryrefslogtreecommitdiffstats
path: root/Objects/dictobject.c
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2016-12-15 16:21:23 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2016-12-15 16:21:23 (GMT)
commit3d3f264849580ab1e8f3a8e8e8ba402bfe2c6523 (patch)
treeffaff02924ed14efdc077f7385e98da21e33f9d4 /Objects/dictobject.c
parent8320193d106c63445e8186d9f8c9455ba931150d (diff)
downloadcpython-3d3f264849580ab1e8f3a8e8e8ba402bfe2c6523.zip
cpython-3d3f264849580ab1e8f3a8e8e8ba402bfe2c6523.tar.gz
cpython-3d3f264849580ab1e8f3a8e8e8ba402bfe2c6523.tar.bz2
Fix a memory leak in split-table dictionaries
Issue #28147: Fix a memory leak in split-table dictionaries: setattr() must not convert combined table into split table. Patch written by INADA Naoki.
Diffstat (limited to 'Objects/dictobject.c')
-rw-r--r--Objects/dictobject.c27
1 files changed, 22 insertions, 5 deletions
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 97f0418..5fff34b 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -1245,7 +1245,7 @@ After resizing a table is always combined,
but can be resplit by make_keys_shared().
*/
static int
-dictresize(PyDictObject *mp, Py_ssize_t minused)
+dictresize(PyDictObject *mp, Py_ssize_t minsize)
{
Py_ssize_t i, newsize;
PyDictKeysObject *oldkeys;
@@ -1254,7 +1254,7 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)
/* Find the smallest table size > minused. */
for (newsize = PyDict_MINSIZE;
- newsize <= minused && newsize > 0;
+ newsize < minsize && newsize > 0;
newsize <<= 1)
;
if (newsize <= 0) {
@@ -1269,6 +1269,8 @@ dictresize(PyDictObject *mp, Py_ssize_t minused)
mp->ma_keys = oldkeys;
return -1;
}
+ // New table must be large enough.
+ assert(mp->ma_keys->dk_usable >= mp->ma_used);
if (oldkeys->dk_lookup == lookdict)
mp->ma_keys->dk_lookup = lookdict;
mp->ma_values = NULL;
@@ -4306,10 +4308,25 @@ _PyObjectDict_SetItem(PyTypeObject *tp, PyObject **dictptr,
CACHED_KEYS(tp) = NULL;
DK_DECREF(cached);
}
- } else {
+ }
+ else {
+ int was_shared = cached == ((PyDictObject *)dict)->ma_keys;
res = PyDict_SetItem(dict, key, value);
- if (cached != ((PyDictObject *)dict)->ma_keys) {
- /* Either update tp->ht_cached_keys or delete it */
+ if (was_shared && cached != ((PyDictObject *)dict)->ma_keys) {
+ /* PyDict_SetItem() may call dictresize and convert split table
+ * into combined table. In such case, convert it to split
+ * table again and update type's shared key only when this is
+ * the only dict sharing key with the type.
+ *
+ * This is to allow using shared key in class like this:
+ *
+ * class C:
+ * def __init__(self):
+ * # one dict resize happens
+ * self.a, self.b, self.c = 1, 2, 3
+ * self.d, self.e, self.f = 4, 5, 6
+ * a = C()
+ */
if (cached->dk_refcnt == 1) {
CACHED_KEYS(tp) = make_keys_shared(dict);
}