Skip to content

Fix stale event object in GridWithLabelRenderersMixin paint handlers - #2929

Merged
swt2c merged 1 commit into
wxWidgets:masterfrom
jmoraleda:fix/gridlabelrenderer-stale-event-object
Aug 11, 2026
Merged

Fix stale event object in GridWithLabelRenderersMixin paint handlers#2929
swt2c merged 1 commit into
wxWidgets:masterfrom
jmoraleda:fix/gridlabelrenderer-stale-event-object

Conversation

@jmoraleda

Copy link
Copy Markdown
Contributor

The three paint handlers (_onPaintRowLabels, _onPaintColLabels, _onPaintCornerLabel) resolved their target window via evt.GetEventObject(), even though each handler is bound directly to one specific label window in init and so already knows which window it is.

Resolving via GetEventObject() requires SIP to look up the event's wxObject* in its C++-to-Python object map. If some unrelated, non- window wxObject is deleted without SIP being notified (e.g. a wx.MenuItem freed by ~wxMenuBase, which does not go through SIP's teardown path) and a new C++ object is later allocated at that same freed address, the map lookup can return a stale Python wrapper of the wrong type for the live paint event. This surfaces as "TypeError: PaintDC(): argument 1 has unexpected type ''" raised repeatedly on every paint of the affected label window, since the window itself is alive and genuinely painting.

Reported before at: https://discuss.wxpython.org/t/typeerror-when-using-gridwithlabelrenderersmixin/35137

Fix: cache the three label windows in init (the same windows the handlers are bound to) and use the cached references instead of evt.GetEventObject().

The three paint handlers (_onPaintRowLabels, _onPaintColLabels,
_onPaintCornerLabel) resolved their target window via
evt.GetEventObject(), even though each handler is bound directly to
one specific label window in __init__ and so already knows which
window it is.

Resolving via GetEventObject() requires SIP to look up the event's
wxObject* in its C++-to-Python object map. That lookup can return a
stale Python wrapper of the wrong type if some unrelated wxObject was
deleted without SIP being notified and a new C++ object was later
allocated at the same freed address. This surfaces as "TypeError:
PaintDC(): argument 1 has unexpected type '<OtherClass>'" raised
repeatedly on every paint of the affected label window, since the
window itself is alive and genuinely painting.

Confirmed at least one concrete source of such stale wrappers: unlike
wx.Frame, wx.MenuBar and wx.Menu, a wx.MenuItem's SIP wrapper is not
marked deleted when the item is destroyed as part of its owning
wxMenuBar's teardown (~wxMenuBase's wxClearList()). Touching the
stale wrapper afterwards dereferences freed memory and segfaults the
process outright, rather than raising a Python exception:

    frame.Destroy()
    # ... after idle-time deletion runs ...
    sip.isdeleted(frame)    # True
    sip.isdeleted(menuBar)  # True
    sip.isdeleted(menu)     # True
    sip.isdeleted(item)     # False -- stale, still owned by the deleted menu
    item.GetItemLabel()     # SIGSEGV

Reported independently at least twice with a different offending type
(SizerItem):
https://discuss.wxpython.org/t/typeerror-when-using-gridwithlabelrenderersmixin/35137

Fix: cache the three label windows in __init__ (the same windows the
handlers are bound to) and use the cached references instead of
evt.GetEventObject().
@jmoraleda

Copy link
Copy Markdown
Contributor Author

Filed the underlying stale-wrapper bug that this mixin fix defends against: #2931. Confirmed the same mechanism also affects wx.SizerItem (matching the SizerItem symptom in the originally-linked forum thread) and wx.ToolBarToolBase, with repros for all three. This PR stands on its own regardless of how #2931 gets resolved -- it stops trusting evt.GetEventObject(), which is unsafe independent of the root cause.

@swt2c
swt2c merged commit e836fd1 into wxWidgets:master Aug 11, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants