Skip to content

Commit

Permalink
Fix __setstate__ interning when state parameter is not a buil…
Browse files Browse the repository at this point in the history
…t-in dict
  • Loading branch information
agroszer committed Feb 22, 2017
1 parent 1c1ceba commit 30c6cd9
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -20,6 +20,8 @@

- Add support for Python 3.6.

- Fix ``__setstate__`` interning when ``state`` parameter is not a built-in dict


4.2.2 (2016-11-29)
------------------
Expand Down
46 changes: 36 additions & 10 deletions persistent/cPersistence.c
Expand Up @@ -588,8 +588,10 @@ pickle___setstate__(PyObject *self, PyObject *state)
if (state != Py_None)
{
PyObject **dict;
PyObject *items;
PyObject *d_key, *d_value;
Py_ssize_t i;
int len;

dict = _PyObject_GetDictPtr(self);

Expand All @@ -609,17 +611,41 @@ pickle___setstate__(PyObject *self, PyObject *state)

PyDict_Clear(*dict);

i = 0;
while (PyDict_Next(state, &i, &d_key, &d_value)) {
/* normally the keys for instance attributes are
interned. we should try to do that here. */
if (NATIVE_CHECK_EXACT(d_key)) {
Py_INCREF(d_key);
INTERN_INPLACE(&d_key);
Py_DECREF(d_key);
if (PyDict_CheckExact(state))
{
i = 0;
while (PyDict_Next(state, &i, &d_key, &d_value)) {
/* normally the keys for instance attributes are
interned. we should try to do that here. */
if (NATIVE_CHECK_EXACT(d_key)) {
Py_INCREF(d_key);
INTERN_INPLACE(&d_key);
Py_DECREF(d_key);
}
if (PyObject_SetItem(*dict, d_key, d_value) < 0)
return NULL;
}
}
else
{
/* can happen that not a built-in dict is passed as state
fall back to iterating over items, instead of silently
failing with PyDict_Next */
items = PyMapping_Items(state);
len = PySequence_Size(items);
for ( i=0; i<len; ++i ) {
PyObject *item = PySequence_GetItem(items, i);
d_key = PyTuple_GetItem(item, 0);
d_value = PyTuple_GetItem(item, 1);

if (NATIVE_CHECK_EXACT(d_key)) {
Py_INCREF(d_key);
INTERN_INPLACE(&d_key);
Py_DECREF(d_key);
}
if (PyObject_SetItem(*dict, d_key, d_value) < 0)
return NULL;
}
if (PyObject_SetItem(*dict, d_key, d_value) < 0)
return NULL;
}
}

Expand Down
18 changes: 18 additions & 0 deletions persistent/tests/test_persistence.py
Expand Up @@ -985,6 +985,24 @@ class Derived(self._getTargetClass()):
key2 = list(inst2.__dict__.keys())[0]
self.assertTrue(key1 is key2)

import UserDict
inst1 = Derived()
inst2 = Derived()
key1 = 'key'
key2 = 'ke'; key2 += 'y' # construct in a way that won't intern the literal
self.assertFalse(key1 is key2)
state1 = UserDict.IterableUserDict({key1: 1})
state2 = UserDict.IterableUserDict({key2: 2})
k1 = list(state1.keys())[0]
k2 = list(state2.keys())[0]
self.assertFalse(k1 is k2) # verify
inst1.__setstate__(state1)
inst2.__setstate__(state2)
key1 = list(inst1.__dict__.keys())[0]
key2 = list(inst2.__dict__.keys())[0]
self.assertTrue(key1 is key2)


def test___setstate___doesnt_fail_on_non_string_keys(self):
class Derived(self._getTargetClass()):
pass
Expand Down

0 comments on commit 30c6cd9

Please sign in to comment.