Make C's __providedBy__ stop ignoring all errors and catch only AttributeError.

Fixes #239

There was a similar bug in the Python side where it would ignore a __provides__ of None, unlike the C implementation.
I documented this in the code but not the CHANGES.rst because I can't imagine anyone relying on that.
This commit is contained in:
Jason Madden 2021-04-13 16:44:48 -05:00
parent 4a686fc8d8
commit 8a0a8f1dea
No known key found for this signature in database
GPG Key ID: 349F84431A08B99E
4 changed files with 76 additions and 9 deletions

View File

@ -5,6 +5,13 @@
5.4.0 (unreleased)
==================
- Make the C implementation of the ``__providedBy__`` descriptor stop
ignoring all errors raised when accessing the instance's
``__provides__``. Now it behaves like the Python version and only
catches ``AttributeError``. The previous behaviour could lead to
crashing the interpreter in cases of recursion and errors. See
`issue 239 <https://github.com/zopefoundation/zope.interface/issues>`_.
- Update the ``repr()`` and ``str()`` of various objects to be shorter
and more informative. In many cases, the ``repr()`` is now something
that can be evaluated to produce an equal object. For example, what

View File

@ -526,8 +526,10 @@ OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls)
return getObjectSpecification(NULL, cls);
provides = PyObject_GetAttr(inst, str__provides__);
if (provides != NULL)
/* Return __provides__ if we got it, or return NULL and propagate non-AttributeError. */
if (provides != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError))
return provides;
PyErr_Clear();
return implementedBy(NULL, cls);
}

View File

@ -1243,10 +1243,19 @@ def providedBy(ob):
@_use_c_impl
class ObjectSpecificationDescriptor(object):
"""Implement the `__providedBy__` attribute
"""Implement the ``__providedBy__`` attribute
The `__providedBy__` attribute computes the interfaces provided by
an object.
The ``__providedBy__`` attribute computes the interfaces provided by
an object. If an object has an ``__provides__`` attribute
.. versionchanged:: 5.4.0
Both the default (C) implementation and the Python implementation
now let exceptions raised by accessing ``__provides__`` propagate.
Previously, the C version ignored all exceptions.
.. versionchanged:: 5.4.0
The Python implementation now matches the C implementation and lets
a ``__provides__`` of ``None`` override what the class is declared to
implement.
"""
def __get__(self, inst, cls):
@ -1255,11 +1264,10 @@ class ObjectSpecificationDescriptor(object):
if inst is None:
return getObjectSpecification(cls)
provides = getattr(inst, '__provides__', None)
if provides is not None:
return provides
return implementedBy(cls)
try:
return inst.__provides__
except AttributeError:
return implementedBy(cls)
##############################################################################

View File

@ -2531,6 +2531,56 @@ class ObjectSpecificationDescriptorFallbackTests(unittest.TestCase):
directlyProvides(foo, IBaz)
self.assertEqual(list(foo.__providedBy__), [IBaz, IFoo])
def test_arbitrary_exception_accessing_provides_not_caught(self):
class MyException(Exception):
pass
class Foo(object):
__providedBy__ = self._makeOne()
@property
def __provides__(self):
raise MyException
foo = Foo()
with self.assertRaises(MyException):
getattr(foo, '__providedBy__')
def test_AttributeError_accessing_provides_caught(self):
class MyException(Exception):
pass
class Foo(object):
__providedBy__ = self._makeOne()
@property
def __provides__(self):
raise AttributeError
foo = Foo()
provided = getattr(foo, '__providedBy__')
self.assertIsNotNone(provided)
def test_None_in__provides__overrides(self):
from zope.interface import Interface
from zope.interface import implementer
class IFoo(Interface):
pass
@implementer(IFoo)
class Foo(object):
@property
def __provides__(self):
return None
Foo.__providedBy__ = self._makeOne()
provided = getattr(Foo(), '__providedBy__')
self.assertIsNone(provided)
class ObjectSpecificationDescriptorTests(
ObjectSpecificationDescriptorFallbackTests,