View::dealloc dispatches the virtual moveToWindow:
static void dealloc(Object *self) {
View *this = (View *) self;
SDL_FilterEvents(filterViewEvents, this);
$(this, moveToWindow, NULL);
...
Because that is a virtual dispatch, willMoveToWindow and didMoveToWindow run on a
half-destroyed object: a subclass's own dealloc body has already released its state by the
time super(Object, self, dealloc) reaches View::dealloc. Any override that touches
subclass state on detach is therefore a use-after-free.
This actually bit Text, and crashed Quetoo on Windows (debug CRT heap assert):
Text::dealloc calls invalidate(), clearing naturalSizeCache.isValid, then
release(this->font) — which really deallocs the Font and closes the TTF_Font, since
Font::fontWithAttributes allocates a fresh instance per call.
super(Object, self, dealloc) runs View::dealloc, which dispatches
$(this, moveToWindow, NULL).
Text::didMoveToWindow unconditionally called $(self, sizeToFit).
Text::naturalSize sees a dangling but non-NULL self->font, so its NULL guard passes, and
the cache was just invalidated, so it recomputes rather than returning the cached size.
Font::sizeCharacters reads the freed Font and calls TTF_GetFontHeight on the closed
TTF_Font.
That specific crash is fixed by guarding Text::didMoveToWindow on window, matching
View::didMoveToWindow's own guard. This issue tracks the underlying hazard, which remains:
Text is currently the only didMoveToWindow override and nothing overrides
willMoveToWindow, so nothing else is broken today, but the next override to touch its own
state on detach will hit the same thing.
The detach work View::dealloc needs is real — willMoveToWindow resigns key/touch responder
status and detaches the stylesheet, and the recursion into subviews clears their window — so
it cannot simply be dropped. Some options:
- Do the detach inline in
dealloc (non-virtual), and decide what the subview recursion still
owes, given dealloc also nullifies subviews' superview.
- Split an internal
detachFromWindow out of moveToWindow for dealloc to call, leaving the
public notifications for live view-hierarchy changes only.
- Keep the dispatch and document that overrides MUST NOT touch subclass state when
window is
NULL.
View::deallocdispatches the virtualmoveToWindow:Because that is a virtual dispatch,
willMoveToWindowanddidMoveToWindowrun on ahalf-destroyed object: a subclass's own
deallocbody has already released its state by thetime
super(Object, self, dealloc)reachesView::dealloc. Any override that touchessubclass state on detach is therefore a use-after-free.
This actually bit
Text, and crashed Quetoo on Windows (debug CRT heap assert):Text::dealloccallsinvalidate(), clearingnaturalSizeCache.isValid, thenrelease(this->font)— which really deallocs theFontand closes theTTF_Font, sinceFont::fontWithAttributesallocates a fresh instance per call.super(Object, self, dealloc)runsView::dealloc, which dispatches$(this, moveToWindow, NULL).Text::didMoveToWindowunconditionally called$(self, sizeToFit).Text::naturalSizesees a dangling but non-NULLself->font, so its NULL guard passes, andthe cache was just invalidated, so it recomputes rather than returning the cached size.
Font::sizeCharactersreads the freedFontand callsTTF_GetFontHeighton the closedTTF_Font.That specific crash is fixed by guarding
Text::didMoveToWindowonwindow, matchingView::didMoveToWindow's own guard. This issue tracks the underlying hazard, which remains:Textis currently the onlydidMoveToWindowoverride and nothing overrideswillMoveToWindow, so nothing else is broken today, but the next override to touch its ownstate on detach will hit the same thing.
The detach work
View::deallocneeds is real —willMoveToWindowresigns key/touch responderstatus and detaches the stylesheet, and the recursion into subviews clears their
window— soit cannot simply be dropped. Some options:
dealloc(non-virtual), and decide what the subview recursion stillowes, given
deallocalso nullifies subviews'superview.detachFromWindowout ofmoveToWindowfordeallocto call, leaving thepublic notifications for live view-hierarchy changes only.
windowisNULL.