diff options
author | Jason Madden <jamadden@gmail.com> | 2021-04-15 04:50:36 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-15 04:50:36 -0500 |
commit | 24b6a016bfbac83837f13fbc83d2faa511aaa9ef (patch) | |
tree | f28131d60f5b6440531a08f3d9436b92f2a98224 | |
parent | 253456fe1781bdf528b51c28b0341f840a35f2c6 (diff) | |
parent | 6a293da77550df4a57bce7ae3ba53390034fbf23 (diff) | |
download | zope-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.rst | 7 | ||||
-rw-r--r-- | src/zope/interface/_zope_interface_coptimizations.c | 4 | ||||
-rw-r--r-- | src/zope/interface/declarations.py | 27 | ||||
-rw-r--r-- | src/zope/interface/tests/test_declarations.py | 50 |
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, |