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. Trace the EXACT OOB. In qtbase/src/corelib/kernel/qmetaobject.cpp read QMetaObject::cast(const QObject*) and its helper (inherits/ the superdata walk). In qwidget.cpp read QWidget::isActiveWindow() and QWidget::window()/QWidgetPrivate. Determine the precise dereference that reads out of bounds: is it obj->metaObject() on a dangling QObject (garbage vtable), or the target-metaobject superdata chain, or a QWindow* from window() whose handle is null/dangling? State exactly which pointer is read OOB and what invalid value (freed ptr / null+offset / uninitialized) produces a wasm 'out of bounds'. Cite file:line.
Fable
thinking
I need to dig into the Qt source code to understand the exact nature of this out-of-bounds issue.
Fable
⚙ Bash {"command": "grep -n \"QMetaObject::cast\" /opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qmetaobject.cpp | head", "description": "Find QMetaObject::cast definition"}
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
⚙ Bash {"command": "grep -n \"isActiveWindow\\|::window()\\b\\|QWidget::window\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp | head", "description": "Find isActiveWindow and window in qwidget.cpp"}
tool result
576:        \l isActiveWindow, activateWindow(), \l minimized, showMinimized(),
2522:QWindow *QWidget::windowHandle() const
2826:    \property QWidget::windowModality
2841:Qt::WindowModality QWidget::windowModality() const
2935:Qt::WindowStates QWidget::windowState() const
3726:    \sa QWidget::windowState(), QWidget::geometry
4376:QWidget *QWidget::window() const
4581:    } else if ((!isVisible() || isActiveWindow())
6010:    \property QWidget::windowTitle
6032:QString QWidget::windowTitle() co  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qmetaobject.cpp", "offset": 398, "limit": 40}
tool result
398	    \fn QObject *QMetaObject::cast(QObject *obj) const
399	    \internal
400	
401	    Returns \a obj if object \a obj inherits from this
402	    meta-object; otherwise returns \nullptr.
403	*/
404	
405	/*!
406	    \internal
407	
408	    Returns \a obj if object \a obj inherits from this
409	    meta-object; otherwise returns \nullptr.
410	*/
411	const QObject *QMetaObject::cast(const QObject *obj) const
412	{
413	    return (obj && obj->metaObject()->inherits(this)) ? obj : nullptr;
414	}
41  …[truncated]
Fable
⚙ Bash {"command": "grep -n \"isActiveWindow\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "description": "Find isActiveWindow implementation line"}
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": 6967, "limit": 60}
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
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 4376, "limit": 30}
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
thinking
Looking at the crash stack, I see the out-of-bounds read happens in QMetaObject::cast, which gets called from isActiveWindow through a qobject_cast to QWidgetWindow. The issue is that this cast only occurs when QGuiApplication::focusWindow() returns a non-null pointer, so I need to trace why that's leading to an invalid memory access.
Fable
⚙ Bash {"command": "grep -n \"initFrom\" /opt/toolchains/qtsrc/qtbase/src/widgets/styles/qstyleoption.cpp", "description": "Find initFrom in qstyleoption.cpp"}
tool result
129:    \fn void QStyleOption::initFrom(const QWidget *widget)
141:void QStyleOption::initFrom(const QWidget *widget)
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 file descriptor
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qmetaobject.cpp", "offset": 280, "limit": 120}
tool result
280	    forwarded to it the same way signal-slot connections do.
281	
282	    Note that only constructors that are declared with the Q_INVOKABLE
283	    modifier are made available through the meta-object system.
284	
285	    \sa constructor()
286	*/
287	
288	QObject *QMetaObject::newInstanceImpl(const QMetaObject *mobj, qsizetype paramCount,
289	                                      const void **parameters, const char **typeNames,
290	                                      const QtPrivate::QMeta  …[truncated]
Fable
thinking
Looking at the initFrom function now.
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/styles/qstyleoption.cpp", "offset": 141, "limit": 50}
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 call stack to understand where `QMetaObject::cast` gets invoked within `isActiveWindow`. The stack shows it's being called directly from `isActiveWindow`, but I need to figure out which operation inside that function triggers it—it's not coming from `QApplication::activeWindow()` itself, so there must be another path I'm missing.

Looking at the crash more carefully: `QMetaObject::cast` is reading `obj->metaObject()`, which means it's dereferencing the object's vtable pointer. If the object is dangling or freed, that vtable read becomes garbage, and calling through it would fail. In WebAssembly, an out-of-bounds memory access happens when the address exceeds the linea  …
Fable
⚙ Bash {"command": "grep -n \"QApplication::activeWindow\\|activeWindow()\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qapplication.cpp | head", "description": "Find activeWindow"}
tool result
274:            activeWindow(),
1494:    \sa QWidget::setFocus(), QWidget::hasFocus(), activeWindow(), focusChanged()
1565:    activeWindow() even if there is no focusWidget(), for example if no widget
1571:QWidget *QApplication::activeWindow()
1656:    QMessageBox::aboutQt(activeWindow());
1803:    It sets the activeWindow() and focusWidget() attributes and sends proper
1810:    \sa activeWindow(), QWidget::activateWindow()
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "grep -rn \"qobject_cast\\|reinterpret_cast<T>\\|::cast(\" /opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h | head; echo ---; grep -n \"static.*qobject_cast\\|T qobject_cast\" /opt/toolchains/qtsrc/qtbase/src/corelib/ke…
tool result
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h:427:inline T qobject_cast(QObject *object)
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h:429:    return QtPrivate::qobject_cast_helper<T>(object);
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h:433:inline T qobject_cast(const QObject *object)
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h:436:                  "qobject_cast cannot cast away constness (use const_cast)");
/opt/toolchains/qtsrc/qtbase/src/co  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h", "offset": 405, "limit": 40}
tool result
405	
406	    static bool disconnectImpl(const QObject *sender, void **signal, const QObject *receiver, void **slot,
407	                               const QMetaObject *senderMetaObject);
408	
409	};
410	
411	inline QMetaObject::Connection QObject::connect(const QObject *asender, const char *asignal,
412	                                            const char *amember, Qt::ConnectionType atype) const
413	{ return connect(asender, asignal, this, amember, atype); }
414	
415	#if QT_CORE_INLINE_IMPL  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qapplication.cpp", "offset": 1571, "limit": 10}
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
Fable
⚙ Bash {"command": "grep -n \"qobject_cast_helper\" /opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h", "description": "Find qobject_cast_helper"}
tool result
429:    return QtPrivate::qobject_cast_helper<T>(object);
437:    return QtPrivate::qobject_cast_helper<T>(object);
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Bash {"command": "grep -rn \"qobject_cast_helper\" /opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobjectdefs.h /opt/toolchains/qtsrc/qtbase/src/corelib/kernel/*.h 2>/dev/null | head", "description": "Find qobject_cast_helper definition"}
tool result
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobjectdefs.h:745:    inline TgtType qobject_cast_helper(SrcType *object)
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobjectdefs.h:745:    inline TgtType qobject_cast_helper(SrcType *object)
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h:429:    return QtPrivate::qobject_cast_helper<T>(object);
/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobject.h:437:    return QtPrivate::qobject_cast_helper<T>(object);
zshexit:1: parameter HIS  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/corelib/kernel/qobjectdefs.h", "offset": 740, "limit": 30}
tool result
740	        static int test(int (Object::*)(QMetaObject::Call, int, void **));
741	        enum { Value =  sizeof(test(&Object::qt_metacall)) == sizeof(int) };
742	    };
743	
744	    template <class TgtType, class SrcType>
745	    inline TgtType qobject_cast_helper(SrcType *object)
746	    {
747	        using ObjType = std::remove_cv_t<std::remove_pointer_t<TgtType>> ;
748	        static_assert(std::is_pointer_v<TgtType>,
749	                "qobject_cast requires to cast towards a pointer type  …[truncated]
Fable
thinking
So the crash happens when `qobject_cast_helper` calls `QMetaObject::cast`, which checks if the object is null before dereferencing it. If object is null, there's no crash—but if it's a dangling pointer, then accessing `obj->metaObject()` reads from garbage memory and causes an out-of-bounds access. This means the crash requires a non-null but invalid pointer. Looking at `isActiveWindow`, the `qobject_cast` calls are trying to cast to `QWidgetWindow *`

and `QWindowContainer *`, both going through the same cast mechanism. The key question is whether the pointer being cast—likely `window` or `tlw` from the widget—is actually a dangling pointer rather than a valid object. Let me reconsider whet  …
Fable
⚙ Bash {"command": "grep -n \"hideChildren\\|::hide()\\|void QWidgetPrivate::hide_helper\\|hideChildren(bool\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp | head", "description": "Find hideChildren"}
tool result
8231:void QWidget::hide()
8238:void QWidgetPrivate::hide_helper()
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
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 8535, "limit": 45}
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
thinking
When hideChildren sends a QHideEvent to visible child widgets, the QGroupBox processes it through its event handler, which calls initStyleOption to determine styling. This eventually traces up the parent chain via window() to find the top-level widget, then checks if that window is currently active using isActiveWindow().

The issue might be that during a workbench switch, a widget gets deleted but something still holds a reference to it, causing QMetaObject::cast to read out of bounds when it tries to validate the object.
Fable
⚙ Bash {"command": "grep -n \"QGroupBox::event\\|initStyleOption\\|::initFrom\\|QGroupBox::\" /opt/toolchains/qtsrc/qtbase/src/widgets/widgets/qgroupbox.cpp | head -30", "description": "Find QGroupBox event/initStyleOption"}
tool result
61:    \sa QStyleOption::initFrom()
63:void QGroupBox::initStyleOption(QStyleOptionGroupBox *option) const
149:QGroupBox::QGroupBox(QWidget *parent)
160:QGroupBox::QGroupBox(const QString &title, QWidget *parent)
170:QGroupBox::~QGroupBox()
192:void QGroupBox::setTitle(const QString &title)
213:    \property QGroupBox::title
231:QString QGroupBox::title() const
238:    \property QGroupBox::alignment
255:Qt::Alignment QGroupBox::alignment() const
261:void QGroupBox::setAlignment(int alignment)
27  …[truncated]
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/widgets/qgroupbox.cpp", "offset": 288, "limit": 40}
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 QGroupBox::event is calling initStyleOption for every single event, including QEvent::Hide, which triggers a chain through initFrom that eventually calls window()->isActiveWindow(). This isActiveWindow call is where the crash happens in QMetaObject::cast—there's an out-of-bounds access happening during the cast operation.

The real question is whether the OOB is in the casts themselves (qobject_cast<QWidgetWindow*> or qobject_cast<QWindowContainer*>), or if the window pointer itself is already dangling when we call isActiveWindow on it. If window is a stale heap pointer, then calling its non-virtual methods would read garbage from the parent chain, eventually leading to a bad cast. The di  …
Fable
QGroupBox::event unconditionally calls initStyleOption on every event. Let me confirm the FreeCAD workbench-switch path and the CAM/BIM specificity.
Fable
⚙ Bash {"command": "grep -n \"activateWorkbench\\|onToolBarSwitch\\|hide()\\|setVisible\\|deleteLater\\|delete \" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Application.cpp | head -40", "description": "Scan Application.cpp for workbench …
tool result
246:        delete macroMngr;
247:        delete prefPackManager;
770:        delete pNavlibInterface;
791:    delete d;
1358:            activateWorkbench(d->activeDocument->workbench().c_str());
1740:        vp->hide();
1901:bool Application::activateWorkbench(const char* name)
1963:            getMainWindow()->activateWorkbench(QString::fromLatin1(name));
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Read {"file_path": "/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidget.cpp", "offset": 8238, "limit": 30}
tool result
8238	void QWidgetPrivate::hide_helper()
8239	{
8240	    Q_Q(QWidget);
8241	
8242	    bool isEmbedded = false;
8243	#if QT_CONFIG(graphicsview)
8244	    isEmbedded = q->isWindow() && !bypassGraphicsProxyWidget(q) && nearestGraphicsProxyWidget(q->parentWidget()) != nullptr;
8245	#else
8246	    Q_UNUSED(isEmbedded);
8247	#endif
8248	
8249	    if (!isEmbedded && (q->windowType() == Qt::Popup))
8250	        qApp->d_func()->closePopup(q);
8251	
8252	    q->setAttribute(Qt::WA_Mapped, false);
8253	      …[truncated]
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Application.cpp", "offset": 1901, "limit": 70}
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 examine the MainWindow::activateWorkbench function in Gui/MainWindow.cpp to understand how it handles the UI switching, particularly the toolbars and dock windows, and then look at how onToolBarSwitch fits into that flow.
Fable
⚙ Bash {"command": "grep -n \"activateWorkbench\\|hide\\|setVisible\\|delete\\|removeDockWindow\\|retranslate\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/MainWindow.cpp | head -40", "description": "Scan MainWindow.cpp"}
tool result
202:        //: A context menu action used to show or hide the unit system chooser in the status bar
231:        retranslateUi();
252:            retranslateUi();
296:    void retranslateUi()
315:    /// The user's show/hide intent. Tracked here rather than read from
387:                    sb->setVisible(d->hGrp->GetBool("StatusBar", sb->isVisible()));
466:         //: A context menu action used to show or hide the preselection info in the status bar
481:         //: A context menu action used   …[truncated]
Fable
⚙ Bash {"command": "grep -rn \"activateWorkbench\\|onToolBarSwitch\\|setupWorkbench\\|hideChildren\\|activate(\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Workbench.cpp | head", "description": "Scan Workbench.cpp"}
tool result
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/Workbench.cpp:453:bool Workbench::activate()
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/MainWindow.cpp", "offset": 1161, "limit": 80}
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
The workbench activation triggers toolbar and dock window reconstruction through Qt's signal-slot mechanism, while hidden widgets can leave dangling window references. I'm stepping back from the FreeCAD specifics to focus on the core Qt-level out-of-bounds issue, then briefly revisiting the CAM/BIM context with a more targeted approach.

CAM and BIM likely instantiate a widget containing a QGroupBox during their Initialize phase—possibly a task panel, preference dialog, or dock widget—whose parent window has already been destroyed or reparented. When the workbench switches and hideChildren traverses the widget tree, the QGroupBox's window() pointer becomes invalid, triggering the crash.

Mor  …
Fable
The only `QMetaObject::cast` inside `isActiveWindow` is the `qobject_cast` in the focus-window loop. Let me confirm `focusWindow()` is a stored raw pointer that can dangle, and check the FreeCAD teardown that would leave it dangling.
Fable
⚙ Bash {"command": "grep -n \"QWindow \\*QGuiApplication::focusWindow\\|focus_window\" /opt/toolchains/qtsrc/qtbase/src/gui/kernel/qguiapplication.cpp | head", "description": "Find focusWindow storage"}
tool result
194:Q_CONSTINIT QWindow *QGuiApplicationPrivate::focus_window = nullptr;
1045:QWindow *QGuiApplication::focusWindow()
1047:    return QGuiApplicationPrivate::focus_window;
2703:    QWindow *previous = QGuiApplicationPrivate::focus_window;
2726:    QGuiApplicationPrivate::focus_window = newFocus;
2742:    if (QGuiApplicationPrivate::focus_window) {
2748:        QCoreApplication::sendSpontaneousEvent(QGuiApplicationPrivate::focus_window, &focusIn);
2749:        QObject::connect(QGuiApplicationPriv  …[truncated]
Fable
⚙ Bash {"command": "grep -rn \"QWidgetWindow *:\\|class QWidgetWindow\\|QWidget \\*QWidgetWindow::widget\\|widget()\" /opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidgetwindow_p.h | head", "description": "Find QWidgetWindow::widget accessor"}
tool result
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidgetwindow_p.h:33:class QWidgetWindowPrivate;
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidgetwindow_p.h:35:class QWidgetWindow : public QWindow
/opt/toolchains/qtsrc/qtbase/src/widgets/kernel/qwidgetwindow_p.h:43:    QWidget *widget() const { return m_widget; }
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
thinking
Looking at how `focus_window` works as a stored QWindow pointer that gets cleared when windows are destroyed, but in this single-window offscreen WASM setup, the main QWidgetWindow persists across workbench switches, so the pointer remains valid and the cast should work correctly.

But wait — I need to reconsider which `isActiveWindow()` call is actually crashing. The stack shows the cast happens within `isActiveWindow`, but if the focus loop operates on valid objects (the main window and its parent, which is null), then only one iteration occurs and the cast should succeed. Unless the crash is happening on a different `isActiveWindow()` call entirely, not from the focus loop. The stack attr  …
Fable
`focus_window` is a raw stale-able pointer (qguiapplication.cpp:194). Let me quickly check what CAM/BIM specifically create that Part/Draft don't, focusing on dock widgets / QGroupBox panels created at Initialize.
Fable
⚙ Bash {"command": "cd /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Mod; for m in CAM Arch BIM Path Part Draft; do if [ -d \"$m\" ]; then echo \"== $m ==\"; grep -rln \"addDockWidget\\|QGroupBox\\|registerDockWidget\\|DockWindow\" \"$m/Gui\" 2…
tool result
== CAM ==
CAM/Gui/DlgSettingsPathColor.ui
CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui
CAM/Gui/Resources/panels/HoldingTagsEdit.ui
CAM/Gui/Resources/panels/DlgJobTemplateExport.ui
CAM/Gui/Resources/panels/PageOpProbeEdit.ui
== BIM ==
== Part ==
Part/Gui/CrossSections.ui
Part/Gui/DlgImportExportIges.ui
Part/Gui/DlgRevolution.ui
Part/Gui/DlgFilletEdges.ui
Part/Gui/TaskFaceAppearances.ui
== Draft ==
zshexit:1: parameter HISTDB_FD does not contain a file descriptor
Fable
thinking
I'm noticing that CAM and BIM both crash identically, and they're both Python workbenches, but Draft is also Python and works fine—so the language isn't the issue. I need to figure out what CAM and BIM have in common that's different from the working workbenches like Part, Draft, and Sketcher.

My best guess is that CAM and BIM both register a preference page or create a status bar widget containing QGroupBox during initialization, or they import a heavy module that instantiates a hidden widget with QGroupBox. But I can't reliably pin down the exact FreeCAD-specific object without actually building and running it.

For the Qt-level crash itself, I'm confident the issue is an invalid object p  …
Fable
I have traced the exact path. Let me compile the findings. The chain is fully confirmed at the Qt level: 1. **qgroupbox.cpp:288-306** — `QGroupBox::event()` calls `initStyleOption(&box)` unconditionally for *every* event, including the `QHideEvent`. 2. **qwidget.cpp:8535-8560** — `QWidgetPrivate::hideChildren()` sends that `QHideEvent` (line 8560 `QCoreApplication::sendEvent(widget, &e)`) to each visible child, so a QGroupBox child gets `event()` → `initStyleOption`. 3. **qstyleoption.cpp:141-153** — `QStyleOption::initFrom()` does `window = widget->window()` (143) then `window->isActiveWindow()` (153). 4. **qwidget.cpp:6967-7003** — the *only* `QMetaObject::cast` reachable inside `isActiveWindow()` is the `qobject_cast` in the focus-window container loop, lines 6997-6998. (Line 6970 `activeWindow()` is a plain pointer compare; the `windowHandle()->handle()` path at 7010-7012 is null-guarded.) 5. **qobjectdefs.h:758** — `qobject_cast_helper` → `staticMetaObject.cast(object)`. 6. **qmetaobject.cpp:411-414** — `cast()` null-checks `obj`, then evaluates `obj->metaObject()`.
Fable
⚙ StructuredOutput {"area": "Qt use-after-free: QMetaObject::cast dereferencing a dangling QObject's corrupted vtable pointer during QGroupBox hide on CAM/BIM workbench activation", "findings": "EXACT OOB: The out-of-bounds read is the virtual-dispatch load i…
tool result
Structured output provided successfully