Skip to content

View::dealloc dispatches virtual moveToWindow on a half-destroyed object #62

Description

@jdolan

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):

  1. 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.
  2. super(Object, self, dealloc) runs View::dealloc, which dispatches
    $(this, moveToWindow, NULL).
  3. Text::didMoveToWindow unconditionally called $(self, sizeToFit).
  4. 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.
  5. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions