summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2021-04-15 04:50:36 -0500
committerGitHub <noreply@github.com>2021-04-15 04:50:36 -0500
commit24b6a016bfbac83837f13fbc83d2faa511aaa9ef (patch)
treef28131d60f5b6440531a08f3d9436b92f2a98224
parent253456fe1781bdf528b51c28b0341f840a35f2c6 (diff)
parent6a293da77550df4a57bce7ae3ba53390034fbf23 (diff)
downloadzope-interface-24b6a016bfbac83837f13fbc83d2faa511aaa9ef.tar.gz
Merge pull request #240 from zopefoundation/issue239
Make C's __providedBy__ stop ignoring all errors and catch only AttributeError
-rw-r--r--CHANGES.rst7
-rw-r--r--src/zope/interface/_zope_interface_coptimizations.c4
-rw-r--r--src/zope/interface/declarations.py27
-rw-r--r--src/zope/interface/tests/test_declarations.py50
4 files changed, 78 insertions, 10 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 5b57ddb..111ce3b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -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
diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c
index 374311e..0b0713e 100644
--- a/src/zope/interface/_zope_interface_coptimizations.c
+++ b/src/zope/interface/_zope_interface_coptimizations.c
@@ -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);
}
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py
index de90074..9c06a16 100644
--- a/src/zope/interface/declarations.py
+++ b/src/zope/interface/declarations.py
@@ -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):
@@ -1270,11 +1280,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)
##############################################################################
diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py
index 04a0dca..a01d39f 100644
--- a/src/zope/interface/tests/test_declarations.py
+++ b/src/zope/interface/tests/test_declarations.py
@@ -2581,6 +2581,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,