Skip to content

Commit

Permalink
Merge pull request #240 from zopefoundation/issue239
Browse files Browse the repository at this point in the history
Make C's __providedBy__ stop ignoring all errors and catch only AttributeError
  • Loading branch information
jamadden committed Apr 15, 2021
2 parents 253456f + 6a293da commit 24b6a01
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 10 deletions.
7 changes: 7 additions & 0 deletions CHANGES.rst
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/zope/interface/_zope_interface_coptimizations.c
Expand Up @@ -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);
}
Expand Down
27 changes: 18 additions & 9 deletions src/zope/interface/declarations.py
Expand Up @@ -1258,10 +1258,20 @@ def providedBy(ob):

@_use_c_impl
class ObjectSpecificationDescriptor(object):
"""Implement the `__providedBy__` attribute
The `__providedBy__` attribute computes the interfaces provided by
an object.
"""Implement the ``__providedBy__`` attribute
The ``__providedBy__`` attribute computes the interfaces provided by
an object. If an object has an ``__provides__`` attribute, that is returned.
Otherwise, `implementedBy` the *cls* is returned.
.. 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):
Expand All @@ -1270,11 +1280,10 @@ def __get__(self, inst, cls):
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)


##############################################################################
Expand Down
50 changes: 50 additions & 0 deletions src/zope/interface/tests/test_declarations.py
Expand Up @@ -2581,6 +2581,56 @@ class Foo(object):
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,
Expand Down

0 comments on commit 24b6a01

Please sign in to comment.