Commit afeb85bc authored by Tres Seaver's avatar Tres Seaver Committed by GitHub

Merge pull request #62 from zopefoundation/jam-systemerror

Avoid raising a SystemError when clearing slots if setstate() failed.
parents aba23595 e8b9c8e9
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
4.2.4 (unreleased) 4.2.4 (unreleased)
------------------ ------------------
- Nothing changed yet. - Avoid raising a ``SystemError: error return without exception set``
when loading an object with slots whose jar generates an exception
(such as a ZODB ``POSKeyError``) in ``setstate``.
4.2.3 (2017-03-08) 4.2.3 (2017-03-08)
......
...@@ -101,7 +101,7 @@ unghostify(cPersistentObject *self) ...@@ -101,7 +101,7 @@ unghostify(cPersistentObject *self)
{ {
/* Create a node in the ring for this unghostified object. */ /* Create a node in the ring for this unghostified object. */
self->cache->non_ghost_count++; self->cache->non_ghost_count++;
self->cache->total_estimated_size += self->cache->total_estimated_size +=
_estimated_size_in_bytes(self->estimated_size); _estimated_size_in_bytes(self->estimated_size);
ring_add(&self->cache->ring_home, &self->ring); ring_add(&self->cache->ring_home, &self->ring);
Py_INCREF(self); Py_INCREF(self);
...@@ -152,13 +152,14 @@ static void ...@@ -152,13 +152,14 @@ static void
ghostify(cPersistentObject *self) ghostify(cPersistentObject *self)
{ {
PyObject **dictptr, *slotnames; PyObject **dictptr, *slotnames;
PyObject *errtype, *errvalue, *errtb;
/* are we already a ghost? */ /* are we already a ghost? */
if (self->state == cPersistent_GHOST_STATE) if (self->state == cPersistent_GHOST_STATE)
return; return;
/* Is it ever possible to not have a cache? */ /* Is it ever possible to not have a cache? */
if (self->cache == NULL) if (self->cache == NULL)
{ {
self->state = cPersistent_GHOST_STATE; self->state = cPersistent_GHOST_STATE;
return; return;
...@@ -177,7 +178,7 @@ ghostify(cPersistentObject *self) ...@@ -177,7 +178,7 @@ ghostify(cPersistentObject *self)
/* If we're ghostifying an object, we better have some non-ghosts. */ /* If we're ghostifying an object, we better have some non-ghosts. */
assert(self->cache->non_ghost_count > 0); assert(self->cache->non_ghost_count > 0);
self->cache->non_ghost_count--; self->cache->non_ghost_count--;
self->cache->total_estimated_size -= self->cache->total_estimated_size -=
_estimated_size_in_bytes(self->estimated_size); _estimated_size_in_bytes(self->estimated_size);
ring_del(&self->ring); ring_del(&self->ring);
self->state = cPersistent_GHOST_STATE; self->state = cPersistent_GHOST_STATE;
...@@ -195,6 +196,12 @@ ghostify(cPersistentObject *self) ...@@ -195,6 +196,12 @@ ghostify(cPersistentObject *self)
* override __new__ ) */ * override __new__ ) */
if (Py_TYPE(self)->tp_new == Pertype.tp_new) if (Py_TYPE(self)->tp_new == Pertype.tp_new)
{ {
/* later we might clear an AttributeError but
* if we have a pending exception that still needs to be
* raised so that we don't generate a SystemError.
*/
PyErr_Fetch(&errtype, &errvalue, &errtb);
slotnames = pickle_slotnames(Py_TYPE(self)); slotnames = pickle_slotnames(Py_TYPE(self));
if (slotnames && slotnames != Py_None) if (slotnames && slotnames != Py_None)
{ {
...@@ -235,6 +242,7 @@ ghostify(cPersistentObject *self) ...@@ -235,6 +242,7 @@ ghostify(cPersistentObject *self)
} }
} }
Py_XDECREF(slotnames); Py_XDECREF(slotnames);
PyErr_Restore(errtype, errvalue, errtb);
} }
/* We remove the reference to the just ghosted object that the ring /* We remove the reference to the just ghosted object that the ring
...@@ -262,7 +270,7 @@ changed(cPersistentObject *self) ...@@ -262,7 +270,7 @@ changed(cPersistentObject *self)
if (meth == NULL) if (meth == NULL)
return -1; return -1;
arg = PyTuple_New(1); arg = PyTuple_New(1);
if (arg == NULL) if (arg == NULL)
{ {
Py_DECREF(meth); Py_DECREF(meth);
return -1; return -1;
...@@ -371,7 +379,7 @@ pickle_slotnames(PyTypeObject *cls) ...@@ -371,7 +379,7 @@ pickle_slotnames(PyTypeObject *cls)
return NULL; return NULL;
if (n) if (n)
slotnames = Py_None; slotnames = Py_None;
Py_INCREF(slotnames); Py_INCREF(slotnames);
return slotnames; return slotnames;
} }
...@@ -594,7 +602,7 @@ pickle___setstate__(PyObject *self, PyObject *state) ...@@ -594,7 +602,7 @@ pickle___setstate__(PyObject *self, PyObject *state)
int len; int len;
dict = _PyObject_GetDictPtr(self); dict = _PyObject_GetDictPtr(self);
if (!dict) if (!dict)
{ {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
......
...@@ -16,6 +16,9 @@ import unittest ...@@ -16,6 +16,9 @@ import unittest
import platform import platform
import sys import sys
from persistent._compat import copy_reg
py_impl = getattr(platform, 'python_implementation', lambda: None) py_impl = getattr(platform, 'python_implementation', lambda: None)
_is_pypy3 = py_impl() == 'PyPy' and sys.version_info[0] > 2 _is_pypy3 = py_impl() == 'PyPy' and sys.version_info[0] > 2
_is_jython = py_impl() == 'Jython' _is_jython = py_impl() == 'Jython'
...@@ -78,22 +81,22 @@ class _Persistent_Base(object): ...@@ -78,22 +81,22 @@ class _Persistent_Base(object):
self.called = 0 self.called = 0
def register(self,ob): def register(self,ob):
self.called += 1 self.called += 1
raise NotImplementedError raise NotImplementedError()
def setstate(self,ob): def setstate(self,ob):
raise NotImplementedError raise NotImplementedError()
jar = _BrokenJar() jar = _BrokenJar()
jar._cache = self._makeCache(jar) jar._cache = self._makeCache(jar)
return jar return jar
def _makeOneWithJar(self, klass=None): def _makeOneWithJar(self, klass=None, broken_jar=False):
from persistent.timestamp import _makeOctets from persistent.timestamp import _makeOctets
OID = _makeOctets('\x01' * 8) OID = _makeOctets('\x01' * 8)
if klass is not None: if klass is not None:
inst = klass() inst = klass()
else: else:
inst = self._makeOne() inst = self._makeOne()
jar = self._makeJar() jar = self._makeJar() if not broken_jar else self._makeBrokenJar()
jar._cache.new_ghost(OID, inst) # assigns _p_jar, _p_oid jar._cache.new_ghost(OID, inst) # assigns _p_jar, _p_oid
return inst, jar, OID return inst, jar, OID
...@@ -1401,6 +1404,26 @@ class _Persistent_Base(object): ...@@ -1401,6 +1404,26 @@ class _Persistent_Base(object):
self.assertEqual(list(jar._loaded), []) self.assertEqual(list(jar._loaded), [])
self.assertEqual(list(jar._registered), []) self.assertEqual(list(jar._registered), [])
def test_p_invalidate_with_slots_broken_jar(self):
# If jar.setstate() raises a POSKeyError (or any error)
# clearing an object with unset slots doesn't result in a
# SystemError, the original error is propagated
class Derived(self._getTargetClass()):
__slots__ = ('slot1',)
# Pre-cache in __slotnames__; cpersistent goes directly for this
# and avoids a call to copy_reg. (If it calls the python code in
# copy_reg, the pending exception will be immediately propagated by
# copy_reg, not by us.)
copy_reg._slotnames(Derived)
inst, jar, OID = self._makeOneWithJar(Derived, broken_jar=True)
inst._p_invalidate()
self.assertEqual(inst._p_status, 'ghost')
self.assertRaises(NotImplementedError, inst._p_activate)
def test__p_invalidate_from_sticky(self): def test__p_invalidate_from_sticky(self):
inst, jar, OID = self._makeOneWithJar() inst, jar, OID = self._makeOneWithJar()
inst._p_activate() # XXX inst._p_activate() # XXX
......
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