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

  1. Build a Qt Quick application that embeds a WebEngineView — adding one to Qat's own QmlApp is enough to reproduce.
  2. Start it from a test: qat.start_application('QmlApp').
  3. Run any search whose traversal reaches the web view's subtree, for example qat.find_all_objects({'type': 'QQuickItem'}), or a wait_for_object() for any object in the window that hosts the view.
  4. 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 disconnected

The 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'
0

For 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*>