Skip to content

PyObject_RichCompareBool and PyObject_IsTrue error-as-truthy in signal disconnect, compare, and observer call #600

Description

@devdanzin

Several functions use the return value of PyObject_RichCompareBool or PyObject_IsTrue in a boolean if(...) context. Both APIs return -1 on error, which is truthy in C/C++ — comparison errors are silently treated as "true" matches.

1. _Disconnector_call — wrong slot disconnected (signaling.cpp:444-450)

slot.richcmp(other, Py_EQ) returns -1 on error, treated as truthy → the first slot in the list matches on comparison error, causing the wrong slot to be disconnected.

Reproducer:

import gc; gc.disable()
import os
from enaml.signaling import Signal

class BadSlot:
    def __eq__(self, other):
        raise TypeError("comparison bomb")
    def __hash__(self):
        return id(self)
    def __call__(self): pass

class Host: pass
host = Host()
sig = Signal()
bound = sig.__get__(host, type(host))

bound.connect(lambda: None)
bound.connect(BadSlot())
try:
    bound.disconnect(BadSlot())
except SystemError as e:
    print(f"SystemError (BUG - error-as-truthy): {e}")
except TypeError as e:
    print(f"TypeError propagated (correct): {e}")
os._exit(0)
# SystemError: <enaml.signaling._Disconnector object ...> returned a result with an exception set

2. BoundSignal_richcompare (signaling.cpp:622)

Same pattern — error during __eq__ treated as truthy.

3. CallableRef_richcompare (callableref.cpp:123,132)

Same pattern in both Py_EQ and Py_NE branches.

4. PyObject_IsTrue in SubscriptionObserver_call (subscription_observer.cpp:122)

PyObject_IsTrue(self->ref) returns -1 on error. Used in if(...) where -1 is truthy → proceeds with active exception.

Fix for all sites — check for error before branching:

int cmp = slot.richcmp( other, Py_EQ );
if( cmp < 0 )
    return 0;  // propagate exception
if( cmp ) { ... }

Found by cext-review-toolkit.

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