Skip to content

copt: NULL-without-exception returns in SB_extends, IB__adapt__, CPB_descr_get #359

@jensens

Description

@jensens

A detailed analysis of _zope_interface_coptimizations.c was published at
https://gist.github.com/devdanzin/5afbad864934b165a2cc080f110aa720 by @devdanzin.
We reviewed the report with agent support and verified the findings against the
current source (zope.interface 8.2). This issue is one of several extracted from
that review.

Problem

When _implied or _cls fields are NULL (possible because they use
T_OBJECT_EX which allows deletion), three functions return NULL without setting
a Python exception, causing SystemError:

  • SB_extends line 331: if (implied == NULL) return NULL;
  • IB__adapt__ line 789: same pattern for _implied
  • CPB_descr_get line 618: if (self->_cls == NULL) return NULL;

Reproducer

from zope.interface._zope_interface_coptimizations import SpecificationBase
from zope.interface import Interface

sb = SpecificationBase()
sb.isOrExtends(Interface)
# SystemError: <method 'isOrExtends' of 'SpecificationBase' objects>
#   returned NULL without setting an exception
from zope.interface._zope_interface_coptimizations import ClassProvidesBase

cpb = ClassProvidesBase.__new__(ClassProvidesBase)
cpb.__get__(object(), type)
# SystemError: <slot wrapper '__get__' of 'ClassProvidesBase' objects>
#   returned NULL without setting an exception

Fix

Set an exception before returning NULL:

if (implied == NULL) {
    PyErr_SetString(PyExc_AttributeError, "_implied");
    return NULL;
}

Same for _cls in CPB_descr_get.

Scope

3 sites. No behavioral change for properly initialized objects.

Tests

Directly testable. The reproducers from the report work as-is as test cases:

def test_isOrExtends_raises_AttributeError_when_implied_is_None(self):
    from zope.interface.interface import SpecificationBase
    sb = SpecificationBase()
    # _implied is not set (NULL in C)
    with self.assertRaises(AttributeError):
        sb.isOrExtends(object())

def test_CPB_descr_get_raises_AttributeError_when_cls_is_None(self):
    from zope.interface._zope_interface_coptimizations import ClassProvidesBase
    cpb = ClassProvidesBase.__new__(ClassProvidesBase)
    with self.assertRaises(AttributeError):
        cpb.__get__(object(), type)

These currently raise SystemError (the bug). After the fix they raise
AttributeError. The tests verify the fix works.

Note: these test the C implementation specifically. The Python fallback may
behave differently (regular AttributeError from Python attribute access). Both
paths should be tested via the existing Fallback/Optimized test class pattern.

Performance impact

Zero. The NULL check already exists in the current code. The fix only adds a
PyErr_SetString call inside the existing never-taken branch. No new branches,
no new instructions on the happy path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions