subagent transcript

session 17f09f89 · workflow wf_93de0fbc
delegated task
PROVEN (empirical, ground truth): On FreeCAD-for-WebAssembly (Qt 6.11 static, JSPI + native wasm-EH, single-thread QT_FEATURE_thread=-1, shiboken/PySi
▸ Magik
PROVEN (empirical, ground truth): On FreeCAD-for-WebAssembly (Qt 6.11 static, JSPI + native wasm-EH, single-thread QT_FEATURE_thread=-1, shiboken/PySide bindings), activating the CAM **or** BIM GUI workbench throws a wasm 'RuntimeError: memory access out of bounds'. It is NOT OOM (peak wasm memory 23-29MB, MAXIMUM_MEMORY=4GB, no emscripten abort() — onAbort never fires) and NOT a missing Python module (all imports succeed first). CAM and BIM produce the IDENTICAL, deterministic stack (top frames, truncated below by the JSPI promise boundary): RuntimeError: memory access out of bounds at QMetaObject::cast(QObject const*) const <-- OOB read occurs HERE at QWidget::isActiveWindow() const at QStyleOption::initFrom(QWidget const*) at QGroupBox::initStyleOption(QStyleOptionGroupBox*) const at QGroupBox::event(QEvent*) at QApplicationPrivate::notify_helper(QObject*, QEvent*) at QApplication::notify -> Gui::GUIApplication::notify at QCoreApplication::sendEvent(QObject*, QEvent*) at QWidgetPrivate::hideChildren(bool) Working workbenches (Part, Draft, Sketcher) do NOT crash on activation. Qt source tree: /opt/toolchains/qtsrc/qtbase (read qtbase/src/corelib/kernel/qmetaobject.cpp, qtbase/src/widgets/kernel/qwidget.cpp, qtbase/src/widgets/styles/qstyleoption.cpp, qtbase/src/widgets/widgets/qgroupbox.cpp). FreeCAD source: /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui (Application.cpp activateWorkbench/onToolBarSwitch, Workbench.cpp, MainWindow.cpp, DockWindowManager.cpp, PreferencePages/dialog). Read-only — do NOT build. Analyze the trigger. In qwidget.cpp read QWidgetPrivate::hideChildren(bool) and what event it sends to each child (HideToParent? or a paint/polish?), and QGroupBox::event in qgroupbox.cpp (why does hide trigger initStyleOption -> style polish/repaint?). Then in FreeCAD Gui (Application.cpp activateWorkbench, Workbench.cpp, DockWindowManager/ToolBarManager) find what gets hidden on workbench switch and whether a widget's PARENT or top-level WINDOW is destroyed/reparented while a QGroupBox child still receives events. Is this a use-after-free (parent deleted, child lingers) or a widget with a null/dangling window()? Cite file:line.
Fable
thinking
I'm looking at a crash stack trace from a Qt/FreeCAD issue. The out-of-bounds read is happening in QMetaObject::cast, which gets called through a chain starting with QWidget::isActiveWindow, flowing through style option initialization in QGroupBox, and ultimately triggered by an event handler in the application's notification system.
Fable
I'll analyze the crash by reading the relevant Qt and FreeCAD sources.
Fable
⚙ Bash {"command": "grep -n \"hideChildren\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "description": "Find hideChildren in qwidget.cpp"}
tool result
8264:    hideChildren(false);
8535:void QWidgetPrivate::hideChildren(bool spontaneous)
8555:        widget->d_func()->hideChildren(spontaneous);
9308:                d->hideChildren(true);
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "grep -n \"isActiveWindow\\|QMetaObject::cast\\|::cast(\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp | head", "description": "Find isActiveWindow"}
tool result
576:        \l isActiveWindow, activateWindow(), \l minimized, showMinimized(),
4581:    } else if ((!isVisible() || isActiveWindow())
6550:    proxy) if this widget or one of its parents is the \l{isActiveWindow()}{active window}. The \a reason argument will
6620:    if (f->isActiveWindow()) {
6754:    \l{isActiveWindow()}{active window}.
6953:    \property QWidget::isActiveWindow
6967:bool QWidget::isActiveWindow() const
6976:            return tlwExtra->proxyWidget->isActiveWindow();
6983:     …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 8535, "limit": 30}
tool result
8535	void QWidgetPrivate::hideChildren(bool spontaneous)
8536	{
8537	    Q_Q(QWidget);
8538	    qCDebug(lcWidgetShowHide) << "Hiding children of" << q
8539	                              << "spontaneously" << spontaneous;
8540	
8541	    QList<QObject*> childList = children;
8542	    for (int i = 0; i < childList.size(); ++i) {
8543	        QWidget *widget = qobject_cast<QWidget*>(childList.at(i));
8544	        if (!widget)
8545	            continue;
8546	        qCDebug(lcWidgetShowHide) << "Cons  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 6967, "limit": 30}
tool result
6967	bool QWidget::isActiveWindow() const
6968	{
6969	    QWidget *tlw = window();
6970	    if (tlw == QApplication::activeWindow() || (isVisible() && (tlw->windowType() == Qt::Popup)))
6971	        return true;
6972	
6973	#if QT_CONFIG(graphicsview)
6974	    if (const auto &tlwExtra = tlw->d_func()->extra) {
6975	        if (isVisible() && tlwExtra->proxyWidget)
6976	            return tlwExtra->proxyWidget->isActiveWindow();
6977	    }
6978	#endif
6979	
6980	    if (style()->styleHint(QStyle::  …[truncated]
Fable
thinking
I'm tracing through the crash in `isActiveWindow()` — it calls `window()` to get the top-level widget, then compares it against `QApplication::activeWindow()`. The stack trace shows `QMetaObject::cast` is being invoked from within `isActiveWindow()`, but the comparison itself shouldn't trigger a cast operation.
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 6996, "limit": 30}
tool result
6996	        while (ww) {
6997	            QWidgetWindow *qww = qobject_cast<QWidgetWindow *>(ww);
6998	            QWindowContainer *qwc = qww ? qobject_cast<QWindowContainer *>(qww->widget()) : 0;
6999	            if (qwc && qwc->topLevelWidget() == tlw)
7000	                return true;
7001	            ww = ww->parent();
7002	        }
7003	    }
7004	
7005	    // Check if platform adaptation thinks the window is active. This is necessary for
7006	    // example in case of ActiveQt servers t  …[truncated]
Fable
⚙ Bash {"command": "grep -n \"QWidget \\*QWidget::window\\|::activeWindow\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qapplication.cpp", "description": "Find window() and activeWind…
tool result
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qapplication.cpp:1571:QWidget *QApplication::activeWindow()
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp:2349:    if (QApplication::activeWindow() == q)
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp:4376:QWidget *QWidget::window() const
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp:6965:    \sa activateWindow(), QApplication::activeWindow()
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp:6970:   …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 4376, "limit": 25}
tool result
4376	QWidget *QWidget::window() const
4377	{
4378	    QWidget *w = const_cast<QWidget *>(this);
4379	    QWidget *p = w->parentWidget();
4380	    while (!w->isWindow() && p) {
4381	        w = p;
4382	        p = p->parentWidget();
4383	    }
4384	    return w;
4385	}
4386	
4387	/*!
4388	    \since 4.4
4389	
4390	    Returns the native parent for this widget, i.e. the next ancestor widget
4391	    that has a system identifier, or \nullptr if it does not have any native
4392	    parent.
4393	
439  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qapplication.cpp", "offset": 1571, "limit": 15}
tool result
1571	QWidget *QApplication::activeWindow()
1572	{
1573	    return QApplicationPrivate::active_window;
1574	}
1575	
1576	/*!
1577	    \fn QFontMetrics QApplication::fontMetrics()
1578	    \deprecated [6.0] Use QFontMetricsF(qApp->font()) instead.
1579	
1580	    Returns display (screen) font metrics for the application font. For more
1581	    information about the difference between \l QFontMetrics and \l QFontMetricsF,
1582	    see the detailed description of \l QFontMetrics.
1583	
1584	    \sa f  …[truncated]
Fable
thinking
I'm tracing through the crash in `QMetaObject::cast` that gets triggered by `isActiveWindow`, which uses `qobject_cast<QWidgetWindow *>` to check the focused window. The issue seems to stem from how `style()->styleHint(QStyle::SH_Widget_ShareActiv...)` is being called, though the thought cuts off before revealing the full context.

Looking at the actual cast mechanism, `QMetaObject::cast` receives the target type's static metaobject (which is valid) and the object pointer as an argument. If that object pointer is dangling, the virtual call to `metaObject()` would read from an invalid vtable, causing the crash. I need to examine the actual implementation of `QMetaObject::cast` to understand e  …
Fable
⚙ Bash {"command": "grep -n \"QMetaObject::cast\" /opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qmetaobject.cpp", "description": "Find cast in qmetaobject.cpp"}
tool result
398:    \fn QObject *QMetaObject::cast(QObject *obj) const
411:const QObject *QMetaObject::cast(const QObject *obj) const
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qmetaobject.cpp", "offset": 411, "limit": 40}
tool result
411	const QObject *QMetaObject::cast(const QObject *obj) const
412	{
413	    return (obj && obj->metaObject()->inherits(this)) ? obj : nullptr;
414	}
415	
416	#ifndef QT_NO_TRANSLATION
417	/*!
418	    \internal
419	*/
420	QString QMetaObject::tr(const char *s, const char *c, int n) const
421	{
422	    return QCoreApplication::translate(className(), s, c, n);
423	}
424	#endif // QT_NO_TRANSLATION
425	
426	/*!
427	    \since 6.2
428	    Returns the metatype corresponding to this metaobject.
429	    …[truncated]
Fable
thinking
I'm looking at a cast operation that checks if an object inherits from a certain type, but there's a critical issue: if the `obj` pointer is dangling or freed, dereferencing it to call the virtual `metaObject()` method will read garbage from the vtable, leading to out-of-bounds access. The problem traces back to wherever this cast is being called—likely in `isActiveWindow`—where a dangling pointer is being passed as the argument.
Fable
⚙ Bash {"command": "grep -n \"initFrom\\|isActiveWindow\" /opt/toolchains/qtsrc/qtbase/src/widgets/styles/qstyleoption.cpp | head", "description": "Find initFrom in qstyleoption.cpp"}
tool result
129:    \fn void QStyleOption::initFrom(const QWidget *widget)
141:void QStyleOption::initFrom(const QWidget *widget)
153:    if (window->isActiveWindow())
241:    \sa initFrom()
250:    \sa initFrom()
259:    \sa initFrom()
268:    \sa initFrom()
284:    \sa initFrom()
293:    \sa initFrom(), QStyle::drawPrimitive(), QStyle::drawControl(),
349:    state |= QStyle::State_KeyboardFocusChange;  // assume we had one, will be corrected in initFrom()
zshexit:1: parameter HISTDB_FD does not contain a   …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/styles/qstyleoption.cpp", "offset": 141, "limit": 40}
tool result
141	void QStyleOption::initFrom(const QWidget *widget)
142	{
143	    QWidget *window = widget->window();
144	    state = QStyle::State_None;
145	    if (widget->isEnabled())
146	        state |= QStyle::State_Enabled;
147	    if (widget->hasFocus())
148	        state |= QStyle::State_HasFocus;
149	    if (window->testAttribute(Qt::WA_KeyboardFocusChange))
150	        state |= QStyle::State_KeyboardFocusChange;
151	    if (widget->underMouse())
152	        state |= QStyle::State_MouseOver;
153	    …[truncated]
Fable
thinking
I'm tracing through the `isActiveWindow()` call chain — the QGroupBox's window() method walks up to the top-level widget, then isActiveWindow() gets the top-level again and performs checks including a cast to QWidgetWindow from the focused window, or alternatively calls the style's styleHint method.

But I should consider whether the window pointer itself could be dangling — if the QGroupBox's parent chain leads to a deleted top-level widget. Though QObject destruction properly cleans up parent-child relationships by nullifying pointers and deleting children, so if the parent was destroyed, the QGroupBox would be deleted too. Let me look at what actually happens when hideChildren sends the Q  …
Fable
⚙ Bash {"command": "grep -n \"initStyleOption\\|::event\\|QEvent::\" /opt/toolchains/qtsrc/qtbase/src/widgets/widgets/qgroupbox.cpp | head -40", "description": "Find event and initStyleOption in qgroupbox.cpp"}
tool result
63:void QGroupBox::initStyleOption(QStyleOptionGroupBox *option) const
283:    initStyleOption(&option);
288:bool QGroupBox::event(QEvent *e)
292:    if (e->type() == QEvent::Shortcut) {
306:    initStyleOption(&box);
308:    case QEvent::HoverEnter:
309:    case QEvent::HoverMove: {
322:    case QEvent::HoverLeave:
330:    case QEvent::KeyPress: {
339:    case QEvent::KeyRelease: {
354:    return QWidget::event(e);
429:    q->initStyleOption(&box);
456:    initStyleOption(&option);
651:    if (  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/widgets/qgroupbox.cpp", "offset": 288, "limit": 30}
tool result
288	bool QGroupBox::event(QEvent *e)
289	{
290	    Q_D(QGroupBox);
291	#ifndef QT_NO_SHORTCUT
292	    if (e->type() == QEvent::Shortcut) {
293	        QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
294	        if (se->shortcutId() == d->shortcutId) {
295	            if (!isCheckable()) {
296	                d->_q_fixFocus(Qt::ShortcutFocusReason);
297	            } else {
298	                d->click();
299	                setFocus(Qt::ShortcutFocusReason);
300	            }
301	          …[truncated]
Fable
thinking
So the issue traces back to QGroupBox::event being called for every event, including the QHideEvent that hideChildren sends. This triggers a chain: the event reaches initStyleOption, which calls initFrom on the style option, and that eventually checks if the window is active—leading to the out-of-bounds access through QMetaObject::cast. Now I need to figure out why this crash only affects CAM/BIM workbenches and not Part/Draft/Sketcher.

The crash itself is a dangling QObject pointer inside QMetaObject::cast—something's reading from freed memory. I'm tracing through the code path: isActiveWindow calls qobject_cast<QWidgetWindow *> on the focus window, but that's a global so it should be cons  …
Fable
QGroupBox::event calls initStyleOption on EVERY event including QHideEvent. Now let me look at FreeCAD's workbench-switch path.
Fable
⚙ Bash {"command": "grep -n \"activateWorkbench\\|hide\\|setVisible\\|onToolBarSwitch\\|removeWorkbench\\|deleteLater\\|delete \" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Application.cpp | head -50", "description": "Search activateWork…
tool result
246:        delete macroMngr;
247:        delete prefPackManager;
770:        delete pNavlibInterface;
791:    delete d;
1358:            activateWorkbench(d->activeDocument->workbench().c_str());
1735:void Application::hideViewProvider(const App::DocumentObject* obj)
1737:    requireMainThread("Gui::Application::hideViewProvider");
1740:        vp->hide();
1901:bool Application::activateWorkbench(const char* name)
1963:            getMainWindow()->activateWorkbench(QString::fromLatin1(name));
z  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Application.cpp", "offset": 1901, "limit": 100}
tool result
1901	bool Application::activateWorkbench(const char* name)
1902	{
1903	    bool ok = false;
1904	    WaitCursor wc;
1905	    Workbench* oldWb = WorkbenchManager::instance()->active();
1906	    if (oldWb && oldWb->name() == name) {
1907	        return false;  // already active
1908	    }
1909	
1910	    Base::PyGILStateLocker lock;
1911	    // we check for the currently active workbench and call its 'Deactivated'
1912	    // method, if available
1913	    PyObject* pcOldWorkbench = nullptr;
1914	    …[truncated]
Fable
thinking
I need to trace through the MainWindow and Workbench activation code to see which UI elements get hidden during the process.
Fable
Now let me look at MainWindow::activateWorkbench and Workbench.cpp activate.
Fable
⚙ Bash {"command": "grep -n \"activateWorkbench\\|setupWorkbench\\|hide\\|setVisible\\|removeDockWindow\\|delete\\|deleteLater\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Workbench.cpp | head -40", "description": "Search Workbench.cpp f…
tool result
459:    delete tb;
464:    // delete cb;
469:    delete dw;
475:    delete mb;
969:        it->toggleViewAction()->setVisible(false);
971:    getMainWindow()->statusBar()->hide();
1124:    delete _menuBar;
1125:    delete _contextMenu;
1126:    delete _toolBar;
1127:    delete _commandBar;
1219:        delete item;
1248:        delete item;
1275:        delete item;
1297:        delete item;
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "grep -n \"activateWorkbench\\|hideChildren\\|setVisible\\|->hide\\|activateAllStandardMenus\\|setUpdatesEnabled\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/MainWindow.cpp | head -40", "description": "Search MainWindo…
tool result
387:                    sb->setVisible(d->hGrp->GetBool("StatusBar", sb->isVisible()));
1161:void MainWindow::activateWorkbench(const QString& name)
1587:            this->activateWorkbench(currWb);
1658:        action->setVisible(true);
1665:        actions[index]->setVisible(false);
1669:        actions.last()->setVisible(true);
1771:            mdi->hide();
2209:    statusBar()->setVisible(showStatusBar);
2747:        widget->setVisible(enabled);
2841:            item.widget->setVisible(item.  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/MainWindow.cpp", "offset": 1161, "limit": 60}
tool result
1161	void MainWindow::activateWorkbench(const QString& name)
1162	{
1163	    // remember workbench by tab (if enabled)
1164	
1165	    const ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
1166	        "User parameter:BaseApp/Preferences/View"
1167	    );
1168	    const bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
1169	    MDIView* view = activeWindow();
1170	    if (view && saveWB) {
1171	        view->setProperty("ownWB", name);
1172	    }
1173	
1174	    // emit  …[truncated]
Fable
thinking
I'm tracing through the activateWorkbench flow to understand how WorkbenchManager triggers the Workbench activation, which then constructs menus, toolbars, and dock windows. I need to examine what happens in Workbench::activate and how ToolBarManager and DockWindowManager handle the setup, plus understand when hideChildren gets called during widget visibility changes or reparenting.

Looking at the stack trace, a container widget with a QGroupBox child is being hidden during the workbench switch, which triggers QHideEvent propagation. CAM and BIM likely add task panels or dock widgets containing QGroupBox elements that get hidden during this transition. But the actual crash points to a dangl  …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/MainWindow.cpp", "offset": 2720, "limit": 140}
tool result
2720	void MainWindow::setRightSideMessage(const QString& message)
2721	{
2722	    d->rightSideLabel->setText(message.simplified());
2723	}
2724	
2725	bool MainWindow::isRightSideMessageVisible() const
2726	{
2727	    return d->rightSideLabel->isVisible();
2728	}
2729	
2730	namespace
2731	{
2732	// Whether a widget owns its own show/hide lifecycle (the progress bar, which the
2733	// sequencer shows only while an operation runs). Such widgets expose a userEnabled
2734	// Q_PROPERTY that the regis  …[truncated]
Fable
thinking
I need to step back from this tangent and focus on the actual crash. The real problem is the QMetaObject::cast going out of bounds, which means there's a dangling pointer somewhere. Looking at isActiveWindow more carefully, it retrieves the active window through QApplication::activeWindow(), which returns a raw QWidget pointer from QApplicationPrivate::active_window, then compares it against the top-level widget.

The qobject_cast at line 6997 is casting `ww` from focusWindow, and we only reach that point if several earlier conditions pass: the top-level widget isn't the active window, it's not a popup, the style hint check doesn't trigger, and focusWindow actually returns something. So if f  …
Fable
This is getting broad. Let me focus on what's CAM/BIM-specific. Let me check the module directories and QGroupBox usage.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod; ls -d */Gui 2>/dev/null; echo \"---CAM/Path---\"; ls -d CAM Path BIM Arch 2>/dev/null", "description": "List module Gui dirs and CAM/BIM"}
tool result
Exit code 2
Assembly/Gui
CAM/Gui
Fem/Gui
Import/Gui
Inspection/Gui
Material/Gui
Measure/Gui
Mesh/Gui
MeshPart/Gui
PartDesign/Gui
Part/Gui
Points/Gui
ReverseEngineering/Gui
Robot/Gui
Sketcher/Gui
Spreadsheet/Gui
Start/Gui
Surface/Gui
TechDraw/Gui
Test/Gui
-  …[truncated]
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui; grep -rln \"QGroupBox\" DockWindowManager.cpp ToolBarManager.cpp Workbench.cpp SelectionView.cpp 2>/dev/null; echo \"===\"; grep -rn \"focusWindow\\|active_window\\|activ…
tool result
===
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
thinking
CAM has a GUI directory written in C++, while BIM is Python-only without a GUI subdirectory. I'm examining how both workbenches initialize and noticing they produce identical crash stacks during activation—when a workbench switches, the previous one's toolbars and dock windows hide while new ones appear. The interesting part is that CAM and BIM generate the same stack traces despite CAM being C++ and BIM being Python, so I'm trying to figure out what they have in common.

Both workbenches likely register something that triggers during activation—maybe a task panel, dock widget, or status bar element with a QGroupBox. I'm wondering if it's not workbench-specific code at all, but rather shared  …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui; grep -rln \"QGroupBox\\|: public QGroupBox\\|QGroupBox(\" TaskView/ 2>/dev/null; echo \"=== class defs deriving QGroupBox ===\"; grep -rn \"public QGroupBox\" . 2>/dev/nu…
tool result
TaskView/TaskImage.ui
TaskView/TaskOrientation.ui
=== class defs deriving QGroupBox ===
PrefWidgets.h:492:class GuiExport PrefCheckableGroupBox: public QGroupBox, public PrefWidget
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
thinking
I should step back and focus on the actual crash mechanism rather than getting lost in which specific QGroupBox is involved. The real question is whether this is a use-after-free situation where the parent was deleted and left a dangling child, or if it's a dangling window() pointer issue.

In the wasm build without real platform windows, `QGuiApplication::focusWindow()` returns from `QGuiApplicationPrivate::focus_window`, and there's an offscreen FBO setup for the 3D view. When switching workbenches, the MDI subwindow gets hidden, and that's likely when things go wrong. The simplest explanation might be that `obj->metaObject()` is being called on an object whose vtable pointer is already at  …
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Workbench.cpp", "offset": 1, "limit": 60}
tool result
1	/***************************************************************************
2	 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3	 *                                                                         *
4	 *   This file is part of the FreeCAD CAx development system.              *
5	 *                                                                         *
6	 *   This library is free software; you can redistribute it and/or         *
7	 *   modify it under the  …[truncated]
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui; grep -n \"::activate\\|setupToolBars\\|setupDockWindows\\|activated\\|deactivated\\|hideChildren\\|setVisible\\|removeToolBar\\|delete\" Workbench.cpp | head -40", "descr…
tool result
72: \ref Gui::Workbench::setupToolBars() "setupToolBars()", \ref Gui::Workbench::setupCommandBars()
73: "setupCommandBars()" and \ref Gui::Workbench::setupDockWindows() "setupDockWindows()".
94: *   ToolBarItem* setupToolBars() const
96: *     ToolBarItem* root = StdWorkbench::setupToolBars();
122: *   ToolBarItem* setupToolBars() const
209: activated.
445:void Workbench::activated()
450:void Workbench::deactivated()
453:bool Workbench::activate()
455:    ToolBarItem* tb = setupToolBars  …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Workbench.cpp", "offset": 453, "limit": 60}
tool result
453	bool Workbench::activate()
454	{
455	    ToolBarItem* tb = setupToolBars();
456	    setupCustomToolbars(tb, "Toolbar");
457	    WorkbenchManipulator::changeToolBars(tb);
458	    ToolBarManager::getInstance()->setup(tb);
459	    delete tb;
460	
461	    // ToolBarItem* cb = setupCommandBars();
462	    // setupCustomToolbars(cb, "Toolboxbar");
463	    // ToolBoxManager::getInstance()->setup( cb );
464	    // delete cb;
465	
466	    DockWindowItems* dw = setupDockWindows();
467	    WorkbenchMani  …[truncated]
Fable
thinking
I need to trace through ToolBarManager::setup to understand what's happening with the toolbars — it's likely deleting or hiding toolbars that contain the QGroupBox, which could be triggering the crash. Let me examine the hide/delete/reparent logic in that method.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui; grep -n \"::setup\\|hide\\|setVisible\\|deleteLater\\|delete \\|setParent\\|removeToolBar\\|new QToolBar\\|addWidget\" ToolBarManager.cpp | head -50", "description": "Fin…
tool result
144:        delete it;
195:        setVisible(true);
210:        grip->deleteLater();
223:void Gui::ToolBar::setupConnections()
259:    setVisible(true);
399:    delete _instance;
420:void ToolBarManager::setupParameters()
430:void ToolBarManager::setupStatusBar()
451:void ToolBarManager::setupMenuBar()
478:void ToolBarManager::setupConnection()
520:void ToolBarManager::setupTimer()
526:void ToolBarManager::setupSizeTimer()
532:void ToolBarManager::setupResizeTimer()
545:void ToolBa  …[truncated]
Fable
thinking
I'm tracing through the crash sequence: when switching workbenches, the ToolBarManager hides unnecessary toolbars, which triggers hideChildren events that propagate to any QGroupBox widgets nested in toolbar actions, and that's where the crash occurs. Now I need to identify which QGroupBox is involved.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/ToolBarManager.cpp", "offset": 784, "limit": 30}
tool result
784	    // hide all unneeded toolbars
785	    for (QToolBar* it : std::as_const(toolbars)) {
786	        // make sure that the main window has the focus when hiding the toolbar with
787	        // the combo box inside
788	        QWidget* fw = QApplication::focusWidget();
789	        while (fw && !fw->isWindow()) {
790	            if (fw == it) {
791	                getMainWindow()->setFocus();
792	                break;
793	            }
794	            fw = fw->parentWidget();
795	        }
79  …[truncated]
Fable
thinking
The `hide()` call on toolbars triggers `hideChildren`, which sends a `QHideEvent` to child widgets like the `QGroupBox`. I need to figure out which CAM/BIM toolbars actually contain a `QGroupBox`, though the crash could also stem from a dock widget being hidden instead. Let me trace back through `hide_helper` to see where this is really coming from.

Both CAM and BIM produce identical stacks, so I'm looking for a shared widget with a `QGroupBox` that both hide. The core issue is that `QMetaObject::cast` is dereferencing a dangling pointer — either a use-after-free or a dangling window reference. I need to determine which: is `ww` (the focusWindow) dangling, or is `qww->widget()` the problem?  …
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui; grep -n \"::setup\\|hide\\|setVisible\\|deleteLater\\|delete \\|setParent\\|removeDockWindow\\|new QDockWidget\\|addDockWindow\" DockWindowManager.cpp | head -40", "descr…
tool result
180:    delete _instance;
198:    delete d;
206:void DockWindowManager::setupOverlayManagement()
242:                dw->setVisible(d->_hPref->GetBool(dockName, dw->isVisible()));
251:QDockWidget* DockWindowManager::addDockWindow(const char* name, QWidget* widget, Qt::DockWidgetArea pos)
263:    dw = new QDockWidget(mw);
270:    // menu. First, hide immediately the dock widget to avoid flickering, after setting up the dock
272:    dw->hide();
286:    widget->setParent(dw);
365:QWidget*   …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/DockWindowManager.cpp", "offset": 516, "limit": 160}
tool result
516	void DockWindowManager::setup(DockWindowItems* items)
517	{
518	    // save state of current dock windows
519	    saveState();
520	    d->_dockWindowItems = *items;
521	
522	    QList<QDockWidget*> docked = d->_dockedWindows;
523	    const QList<DockWindowItem>& dws = items->dockWidgets();
524	    for (const auto& it : dws) {
525	        QDockWidget* dw = findDockWidget(docked, it.name);
526	        QByteArray dockName = it.name.toLatin1();
527	        bool visible = d->_hPref->GetBool(dockN  …[truncated]
Fable
thinking
I'm noticing a clever lifetime detection mechanism in the code — it's checking whether the metaObject pointer has been downgraded to the base QDockWidget class, which happens during destruction as derived class destructors unwind. This is essentially a way to detect when a dock widget is being deleted. The pattern suggests there's some delicate lifetime management happening around dock widgets, and now I'm trying to understand what triggers the crash when switching work.

When the work bench switches, `setVisible(false)` gets called on dock widgets, which triggers hide events down to child QGroupBox elements. But I keep circling back to the core issue: the out-of-bounds access happens in `QM  …
Fable
Let me check what CAM/BIM specifically register that differs, and the removeDockWindow deletion path.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/DockWindowManager.cpp", "offset": 365, "limit": 55}
tool result
365	QWidget* DockWindowManager::removeDockWindow(const char* name)
366	{
367	    QWidget* widget = nullptr;
368	    for (QList<QDockWidget*>::Iterator it = d->_dockedWindows.begin(); it != d->_dockedWindows.end();
369	         ++it) {
370	        if ((*it)->objectName() == QString::fromUtf8(name)) {
371	            QDockWidget* dw = *it;
372	            d->_dockedWindows.erase(it);
373	
374	            if (d->overlayManager) {
375	                d->overlayManager->unsetupDockWidget(dw);
376	     …[truncated]
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod; echo \"=== CAM setupDockWindows / QGroupBox ===\"; grep -rn \"setupDockWindows\\|QGroupBox\\|addDockWindow\\|StatusBar\\|GroupBox\" CAM/Gui/Workbench.cpp 2>/dev/null | he…
tool result
=== CAM setupDockWindows / QGroupBox ===
=== BIM InitGui dock/statusbar ===
BIM/BimStatus.py:90:        st = mw.statusBar()
BIM/BimStatus.py:91:        statuswidget = st.findChild(QtGui.QToolBar, "BIMStatusWidget")
BIM/BimStatus.py:99:                statuswidget.setObjectName("BIMStatusWidget")
BIM/BimStatus.py:101:                    "BIMStatusWidget",
BIM/BimStatus.py:109:                    id="BIMStatusWidget",
BIM/BimStatus.py:184:                statuswidget = mw.findChild(QtGui.QToolBar,  …[truncated]
Fable
thinking
I'm looking at how BIM adds a status bar widget called BIMStatusWidget, which is a QToolBar, and trying to trace where the crash is happening around the QGroupBox in BimStatus.py near line 90.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod/BIM/BimStatus.py", "offset": 80, "limit": 120}
tool result
80	            result = form.exec_()
81	            if not result:
82	                return
83	            utext = form.inputField.text()
84	        action.parent().parent().parent().setText(utext)
85	
86	    # main code
87	
88	    mw = FreeCADGui.getMainWindow()
89	    if mw:
90	        st = mw.statusBar()
91	        statuswidget = st.findChild(QtGui.QToolBar, "BIMStatusWidget")
92	        if show:
93	            if statuswidget:
94	                statuswidget.show()
95	                if has  …[truncated]
Fable
thinking
The BIM status widget is actually a toolbar in the status bar rather than a QGroupBox directly, so it's not the crash source itself. I've spent considerable time on this, and without actually building and running the code, I can't pinpoint the exact CAM/BIM QGroupBox instance causing the issue. Let me refocus on what I can confirm about the trigger mechanism and make a reasoned assessment about whether this is specific to CAM/BIM.

The crash on `obj->metaObject()` when `obj` is the result of `focusWindow()` or `qww->widget()` points to a dangling top-level QWindow or QWidget—the QGroupBox still references a window or focus object that's been freed. This is a use-after-free where the QGroupBo  …
Fable
I have enough to give a well-grounded analysis. Let me verify the one discriminating detail in isActiveWindow's early-return.
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/widgets/qgroupbox.cpp", "offset": 63, "limit": 30}
tool result
63	void QGroupBox::initStyleOption(QStyleOptionGroupBox *option) const
64	{
65	    if (!option)
66	        return;
67	
68	    Q_D(const QGroupBox);
69	    option->initFrom(this);
70	    option->text = d->title;
71	    option->lineWidth = 1;
72	    option->midLineWidth = 0;
73	    option->textAlignment = Qt::Alignment(d->align);
74	    option->activeSubControls |= d->pressedControl;
75	    option->subControls = QStyle::SC_GroupBoxFrame;
76	
77	    option->state.setFlag(QStyle::State_MouseOver, d-  …[truncated]
Fable
thinking
I've confirmed that initStyleOption at line 69 calls option->initFrom(this) without any conditions, so that piece checks out. Now I'm ready to put together the structured output with all the verified details.
Fable
I now have a complete picture. Let me compile the analysis.
Fable
⚙ StructuredOutput {"area": "Qt widget hide/show + FreeCAD workbench-switch widget lifetime (use-after-free on a dangling top-level/focus QObject, surfaced through QGroupBox's per-event style init)", "findings": "TRIGGER MECHANISM (confirmed from Qt source):\…
tool result
Structured output provided successfully