Crash (SIGSEGV) when a search walks a QtWebEngine item: dynamic_cast in the QWidget plugin requires RTTI
Description of Issue
The application under test crashes with SIGSEGV as soon as any Qat search walks past a QtWebEngine item, so Qat cannot be used at all against an application that embeds a WebEngineView.
CastObject() in the QWidget plugin runs dynamic_cast on every QObject a search visits (ObjectLocator::FindChildItems → WidgetWrapper::Cast → every plugin's CastObject). QtWebEngine's internal classes are compiled the way Chromium is, with -fno-rtti, so they carry no type_info, and dynamic_cast on one of them faults.
Affected version(s)
1.8.0 (the code is unchanged in current main)
System information
- OS: macOS (the faulting code is platform independent)
- OS version: 26.6.2 (build 25G83)
- Qt version: 6.7.3
- Python version: 3.14.7
Steps to reproduce
- Build a Qt Quick application that embeds a
WebEngineView— adding one to Qat's ownQmlAppis enough to reproduce. - Start it from a test:
qat.start_application('QmlApp'). - Run any search whose traversal reaches the web view's subtree, for example
qat.find_all_objects({'type': 'QQuickItem'}), or await_for_object()for any object in the window that hosts the view. - The application dies during the traversal.
What is the current issue behavior?
The application under test terminates immediately with EXC_BAD_ACCESS (SIGSEGV) at address 0x8, and the client raises:
ConnectionAbortedError: Server has been disconnectedThe object that triggers it is QtWebEngineCore::RenderWidgetHostViewQtDelegateItem, identified by descending the object tree and searching one subtree at a time until a single child reproduced the crash.
What is the expected behavior?
Objects that a plugin cannot wrap are skipped, and searches complete normally in applications that embed QtWebEngine.
Relevant logs and/or screenshots
Crash stack:
__dynamic_cast
Qat::Plugin::CastObject(QObject const*)
Qat::WidgetWrapper::Cast(QObject const*)
(anonymous namespace)::FindChildItems(...)That class carries no RTTI, which is what makes dynamic_cast fault. QtWebEngineCore emits type_info only for its public QWebEngine* API classes; the internal, Chromium-side classes — the ones that actually appear in the object tree — have none:
$ nm -a QtWebEngineCore | grep -c ' __ZTI'
19
$ nm -a QtWebEngineCore | grep ' __ZTI' | head -3
000000000a5d68f8 S __ZTI14QWebEnginePage
000000000a5d5d40 S __ZTI17QWebEngineHistory
000000000a5d6c50 S __ZTI17QWebEngineProfile
# the faulting class is in the binary, but has no typeinfo symbol
$ nm -a QtWebEngineCore | grep -c RenderWidgetHostViewQtDelegateItem
79
$ nm -a QtWebEngineCore | grep -c 'ZTI.*RenderWidgetHostViewQtDelegateItem'
0For comparison, QtCore emits 103 and QtQuick 326 such symbols, so this is specific to how QtWebEngine is built rather than a property of the Qt build as a whole.
Possible fixes
The two casts are in server/plugins/QWidget/plugin.cpp (lines 49-60), directly under the existing /// todo: investigate why qobject_cast cannot be used:
// qobject_cast does not work between the Server and this plugin
/// todo: investigate why qobject_cast cannot be used
auto* item = dynamic_cast<Qat::ModelIndexWrapper*>(qtobject);
if (item)
{
return new Qat::ItemWidget(item);
}
auto* menu = dynamic_cast<Qat::MenuWrapper*>(qtobject);
if (menu)
{
return Qat::FindMenuItem(menu);
}To answer the todo first: qobject_cast fails here because Qat::ModelIndexWrapper and Qat::MenuWrapper are linked into both the server and the plugin, so each binary holds its own staticMetaObject, and qobject_cast compares metaobject addresses.
Matching on the class name avoids both problems — it needs neither shared symbols nor RTTI, because QObject::inherits() compares class-name strings along the metaobject chain:
// These two classes are linked into both the server and this plugin, so each
// binary holds its own staticMetaObject, and qobject_cast, which compares
// their addresses, never matches. dynamic_cast is not an option either:
// CastObject runs on every object a search walks past, and it faults on
// classes compiled without RTTI, such as QtWebEngineCore's internal ones
// (Chromium builds with -fno-rtti). Matching on the class name goes through
// the metaobject, which needs neither shared symbols nor RTTI.
if (qtobject->inherits("Qat::ModelIndexWrapper"))
{
return new Qat::ItemWidget(static_cast<Qat::ModelIndexWrapper*>(qtobject));
}
if (qtobject->inherits("Qat::MenuWrapper"))
{
return Qat::FindMenuItem(static_cast<Qat::MenuWrapper*>(qtobject));
}I built the plugin with this change and ran a UI test suite against a QML application that embeds QtWebEngine: the crash is gone, the QWidget plugin loads normally alongside the QML and Cocoa ones, and the suite passes, including flows that drive a native file dialog. I did not exercise item views or menus that go through ItemWidget/FindMenuItem, so that part of the change is worth a look from someone with a test for it.
Two other dynamic_cast sites are worth a glance, though both are far safer: each only casts an object found by Qat's own picker object name among a window's direct children, so arbitrary application objects never reach them.
server/src/events/ObjectPickerFilter.cpp:53—dynamic_cast<IObjectPicker*>server/src/commands/ActionCommandExecutor.cpp:105—dynamic_cast<IObjectPicker*>