Skip to content

Fix double-removal Q_ASSERT in DockManagerPrivate::restoreStateFromXml - #838

Merged
githubuser0xFFFF merged 1 commit into
githubuser0xFFFF:masterfrom
fdarmayan:lp360-patches
Jun 4, 2026
Merged

Fix double-removal Q_ASSERT in DockManagerPrivate::restoreStateFromXml#838
githubuser0xFFFF merged 1 commit into
githubuser0xFFFF:masterfrom
fdarmayan:lp360-patches

Conversation

@fdarmayan

Copy link
Copy Markdown

Problem

CDockManager::restoreState() can crash with a failed assertion when the
saved state contains floating dock containers. The teardown path in
DockManagerPrivate::restoreStateFromXml() looks like:

for (int i = 0; i < FloatingWidgets.count(); ++i)
{
    CFloatingDockContainer* floatingWidget = FloatingWidgets[i];
    if (!floatingWidget) continue;
    _this->removeDockContainer(floatingWidget->dockContainer());
    floatingWidget->deleteLater();
}

removeDockContainer() is called and the floating widget is queued for
deletion. When the queued deleteLater() fires, ~CDockContainerWidget()
runs and calls removeDockContainer() on the manager a second time for
the same container. That second call trips:

Q_ASSERT(removed == 1);

inside CDockManager::removeDockContainer(), killing the application in
debug builds and silently corrupting the container list in release.

Fix

Use CDockContainerWidget::removeFromDockManager() (introduced in
544c624 "Unbind containers from DockManager to prevent accidental reuse
before deletion") instead of the manager-side removeDockContainer().
That helper clears the container's back-pointer to the manager up front,
so the subsequent destructor call becomes a no-op for the manager and
the assertion no longer fires.

floatingWidget->dockContainer()->removeFromDockManager();
floatingWidget->deleteLater();

Repro

  1. Open an app that uses CDockManager with at least one floating dock
    container in the saved layout.
  2. Call CDockManager::saveState() / restoreState() on that layout
    while floating widgets exist.
  3. Observe the Q_ASSERT(removed == 1) failure in
    CDockManager::removeDockContainer() once deleteLater() drains.

Notes

  • One-line change, no new public API.
  • Behaviour-preserving: removeFromDockManager() already does the
    same list-removal work that removeDockContainer() did, just without
    the double-removal hazard.

In DockManagerPrivate::restoreStateFromXml() the floating widgets were
removed via removeDockContainer() and then queued for deletion with
deleteLater(). When ~CDockContainerWidget() ran it would call
removeDockContainer() a second time on the same container, tripping the
Q_ASSERT(removed == 1) check in CDockManager::removeDockContainer().

Switch to CDockContainerWidget::removeFromDockManager() (introduced in
upstream 544c624) so the container's back-pointer to the manager is
cleared up front and the destructor becomes a no-op for the manager
side. This keeps the LP360_AI workspace restore path crash-free until
the equivalent fix lands upstream.
@githubuser0xFFFF

Copy link
Copy Markdown
Owner

Thank you very much for this fix.

@githubuser0xFFFF
githubuser0xFFFF merged commit 3838651 into githubuser0xFFFF:master Jun 4, 2026
ksylvan added a commit to story-wizard/Qt-Advanced-Docking-System that referenced this pull request Jul 3, 2026
…ory)

Brings in 14 upstream commits incl. dark mode support (githubuser0xFFFF#842), Wayland
drag-and-drop with in-window preview (githubuser0xFFFF#844), auto-hide title bar fix
(githubuser0xFFFF#843), Linux floating-raise fix (githubuser0xFFFF#840), LP360 double-removal fix
(githubuser0xFFFF#838), and lazy CDockWidgetTab creation for Python factories (githubuser0xFFFF#848).

Conflict resolutions:
- DockManager.cpp: keep both new includes (QTimer ours, QStyleHints upstream)
- DockOverlay.cpp: keep fork's AutoHideFeatureEnabled guard structure and
  half-panel edge-band fall-throughs; adopt upstream's GlobalPos parameter
  (new dropAreaUnderCursor(QPoint) overload) instead of QCursor::pos()
- FloatingDockContainer.cpp: line-ending false conflict (fork stores LF,
  upstream CRLF) re-merged content-wise; kept macOS escape-key polling,
  took upstream's Wayland ctor (isWayland() null-parent) + comment
- FloatingDragPreview.cpp: kept fork's dropOverlaysEnabled() early-out;
  adopted upstream's SourceContainer/Wayland container-picking loop

Validated: full build clean; QuadrantHitTestTest + OverlayGateTest pass.
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