Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Crash tearing 2nd-to-last dock out of a floating tabbed group (QDockWidgetGroupWindow) — Windows, Qt 6.11.1
Qt 6.11 is out! See what's new in the release blog

Crash tearing 2nd-to-last dock out of a floating tabbed group (QDockWidgetGroupWindow) — Windows, Qt 6.11.1

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
4 Posts 2 Posters 526 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    J Offline
    jvenema
    wrote last edited by
    #1

    Crash tearing 2nd-to-last dock out of a floating tabbed group (QDockWidgetGroupWindow) — Windows, Qt 6.11.1

    Posting here because the bug tracker login is looping for me; happy for a maintainer
    to move this into Jira. Minimal 40-line repro + crash stack below.

    What happens

    On Windows, dragging the second-to-last dock widget out of a floating tabbed
    group (QDockWidgetGroupWindow) crashes with an access violation. The group drops
    from 2 members to 1, Qt destroys the group window and reparents the surviving dock
    to the main window, and QDockAreaLayoutInfo::reparentWidgets() dereferences a
    dangling item.widgetItem while QMainWindowLayout::applyState() replays the
    drag's saved layout state.

    • A 3-member group torn down to 2 does not crash — only the transition that
      destroys the group window (2→1).
    • It's not drag-specific: programmatically removing one member of a 2-member
      group via addDockWidget() + setFloating() also crashes, just deferred ~1 s.
      Removing both (2→0, empty group) crashes too — so the fault is the group-window
      teardown itself.

    Looks like a Windows regression / uncovered case of
    QTBUG-118579 (fixed 6.5.4 / 6.6.2 /
    6.7.0 for Linux/X11 + macOS). That fix — the qobject_cast<QDockWidgetGroupWindow*>
    skip in reparentWidgets — is present in 6.11.1 but runs after the faulting
    dereference.

    Environment

    Qt 6.11.1, Windows 11, MSVC x64 (reproduced with the stock msvc2022_64 shared
    kit and with a from-source static build).

    Steps to reproduce

    1. Build & run the program below (3 dock widgets open floating).
    2. Drag two of them together into one tabbed floating group.
    3. Drag one of those two tabs back out and drop it. → crash.

    Crash stack

    QDockAreaLayoutInfo::reparentWidgets(QWidget*)            qdockarealayout.cpp:2210
    QMainWindowLayout::applyState(QMainWindowLayoutState&, bool)  qmainwindowlayout.cpp:3240
    QMainWindowLayout::restore(QInternal::SaveStateRule)         qmainwindowlayout.cpp:2727
    QDockWidgetPrivate::endDrag(EndDragMode)                     qdockwidget.cpp:833
    QDockWidgetPrivate::setFloating(bool)                        qdockwidget.cpp:1485
    QDockWidget::setFloating(bool)                              qdockwidget.cpp:1474
    QDockWidgetGroupWindow::reparentToMainWindow(QDockWidget*)  qdockwidget.cpp:772
    QDockWidgetPrivate::endDrag(EndDragMode)                    qdockwidget.cpp:860
    QMainWindowTabBar::mouseReleaseEvent(QMouseEvent*)          qmainwindowlayout.cpp:2111
    

    The AV reads the item.widgetItem value (seen as 0xFFFFFFFFFFFFFFFF and
    0x0000000100000001 across runs — a stale/garbage pointer in item_list):

    // qdockarealayout.cpp, QDockAreaLayoutInfo::reparentWidgets
    if (item.widgetItem) {
        QWidget *w = item.widgetItem->widget();        // <-- AV (item.widgetItem dangling)
        if (qobject_cast<QDockWidgetGroupWindow *>(w)) // QTBUG-118579 skip, runs too late
            continue;
        ...
    

    Minimal reproducer

    main.cpp:

    #include <QApplication>
    #include <QMainWindow>
    #include <QDockWidget>
    #include <QPlainTextEdit>
    #include <QLabel>
    
    int main(int argc, char** argv)
    {
        QApplication app(argc, argv);
    
        QMainWindow w;
        w.resize(1000, 650);
        w.setDockOptions(QMainWindow::AllowTabbedDocks
                       | QMainWindow::AllowNestedDocks
                       | QMainWindow::GroupedDragging);
        w.setCentralWidget(new QLabel("central", &w));
    
        for (int i = 1; i <= 3; ++i) {
            auto* d = new QDockWidget(QString("Dock %1").arg(i), &w);
            d->setWidget(new QPlainTextEdit(QString("content %1").arg(i)));
            d->setAllowedAreas(Qt::AllDockWidgetAreas);
            w.addDockWidget(Qt::LeftDockWidgetArea, d);
            d->setFloating(true);
            d->move(120 + i * 80, 120 + i * 70);
            d->resize(360, 260);
        }
    
        w.show();
        return app.exec();
    }
    

    CMakeLists.txt:

    cmake_minimum_required(VERSION 3.21)
    project(qtdockcrash LANGUAGES CXX)
    set(CMAKE_CXX_STANDARD 17)
    find_package(Qt6 REQUIRED COMPONENTS Widgets)
    qt_standard_project_setup()
    qt_add_executable(qtdockcrash WIN32 main.cpp)
    target_link_libraries(qtdockcrash PRIVATE Qt6::Widgets)
    
    1 Reply Last reply
    0
    • Christian EhrlicherC Offline
      Christian EhrlicherC Offline
      Christian Ehrlicher
      Lifetime Qt Champion
      wrote last edited by
      #2

      This is most likely fixed in Qt6.11.2 and Qt6.12

      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
      Visit the Qt Academy at https://academy.qt.io/catalog

      1 Reply Last reply
      1
      • J Offline
        J Offline
        jvenema
        wrote last edited by
        #3

        Root cause found, and a working fix (local qtbase patch)

        Tracked this down to QDockWidgetGroupWindow::reparentToMainWindow() in
        src/widgets/widgets/qmainwindowlayout.cpp. When the group drops below 2 members and
        is torn down, that function reparents the dock and calls setFloating(), which nests
        into QDockWidgetPrivate::endDrag() → QMainWindowLayout::restore():

        1. reparentToMainWindow() mutates mwLayout->layoutState (parentInfo.add() +
          layoutInfo()->remove()), which frees/replaces some QLayoutItem*.
        2. The drag's savedState snapshot shares those same QLayoutItem* pointers with
          layoutState, so it is now stale.
        3. The nested setFloating() → endDrag() → restore() does layoutState = savedState
          and walks it in applyState() → QDockAreaLayoutInfo::reparentWidgets(), where
          item.widgetItem->widget() dereferences a freed widgetItem → access violation
          (qdockarealayout.cpp ~line 2210). The qobject_cast<QDockWidgetGroupWindow*> skip
          that landed for QTBUG-118579 sits after that deref, so it doesn't help here.

        It is not drag-specific: doing the 2→1 dissolution purely programmatically
        (addDockWidget() + setFloating() on a member of a 2-member group) crashes too, just
        deferred — confirming the fault is the group-window teardown replaying a stale snapshot.

        The fix

        Drop the stale snapshot before the nested setFloating(), but only when this
        reparent actually dissolves the group (< 2 members left) — so a normal tear where the
        group survives keeps its original restore() untouched:

        // in QDockWidgetGroupWindow::reparentToMainWindow(), right before
        // dockWidget->setFloating(wasFloating):
        if (findChildren<QDockWidget *>(Qt::FindDirectChildrenOnly).size() < 2)
            mwLayout->savedState.clear();
        

        QMainWindowLayoutState::clear() only resets the structure (item_list.clear()); it
        does not delete the shared QLayoutItems, so layoutState (already updated by hand
        above) stays valid, and the nested restore() early-returns because savedState is now
        invalid. (An unconditional mwLayout->savedState.clear(); also fixes the crash, but it
        changes the surviving-tear path too and adds a needless repaint on it — hence the guard.)

        Build a patched qtbase (or just QtWidgets) and link against it. Verified on
        Windows 11, Qt 6.11.1, MSVC x64: the 2→1 tear no longer crashes; create / move /
        3→2 are unaffected. (A brief repaint flash on tear is inherent Qt reparent behaviour,
        present with or without the patch.)

        For maintainers

        This is a local workaround; the cleaner upstream fix is probably to stop restore()
        from replaying a savedState that reparentToMainWindow() has just invalidated — e.g.
        keep savedState consistent across the dissolution, or skip the nested
        restore/reparentWidgets when the group is being torn down. Happy to help test a proper
        patch. This looks like a Windows regression/uncovered case of the QTBUG-118579 family
        (fixed for Linux/X11 + macOS in 6.5.4 / 6.6.2 / 6.7.0).

        1 Reply Last reply
        0
        • J Offline
          J Offline
          jvenema
          wrote last edited by
          #4

          Thanks Christian — that lines up. I checked the qt/qtbase 6.11 branch and the fix
          is right there in QDockWidgetGroupWindow::reparentToMainWindow()
          (src/widgets/widgets/qmainwindowlayout.cpp): an early mwLayout->savedState.clear();,
          with a comment that nails the cause:

          mwLayout->widgetAnimator.abort(dockWidget);
          
          // The saved state is now invalid because it contains
          // a reference to the dock widget inside the group window.
          // - the dock widget has been reparented.
          // - if it was the last dock widget, the group window will be deleted
          // => clear saved state.
          mwLayout->savedState.clear();
          

          That matches the root cause exactly: the drag's savedState snapshot shares
          QLayoutItem* with layoutState, reparentToMainWindow() invalidates them, and the
          nested setFloating() → endDrag() → restore() replays the stale snapshot in
          reparentWidgets() → the AV. (Reassuring to land on the same one-liner independently.)

          So for anyone hitting this on 6.11.1 before 6.11.2 ships: the one-line backport above
          in reparentToMainWindow() fixes it — verified on Windows 11 / Qt 6.11.1 / MSVC x64
          (2→1 tear no longer crashes; create/move and the 3→2 tear are unaffected). And once on
          6.11.2 / 6.12 the official fix covers it, so the local patch can just be dropped.

          Thanks again for the quick pointer!

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved