kate: fix use-after-free crash in setupGhostProvider

m_ghost was parented to the view, so Qt parent-child cleanup could
destroy it when a view was replaced. The raw pointer remained non-null,
causing a double-free on the next viewChanged signal.

Fix by using QPointer<OllieGhostProvider> which auto-nulls on destroy.

Also replace qobject_cast with dynamic_cast for KF5 KTextEditor
interfaces to avoid crashes when qt_metacast accesses uninitialized
KateViewConfig during early view activation.
This commit is contained in:
Levi Neely 2026-07-15 11:06:20 +02:00
parent 5d18806ad6
commit 455d9c9190
3 changed files with 10 additions and 6 deletions

View File

@ -106,7 +106,10 @@ OllieGhostProvider::OllieGhostProvider(KTextEditor::View *view, QDBusInterface *
#if KTEXTEDITOR_VERSION_MAJOR >= 6
view->registerInlineNoteProvider(this);
#else
auto *noteIface = qobject_cast<KTextEditor::InlineNoteInterface *>(view);
// Use dynamic_cast instead of qobject_cast: during view construction,
// qt_metacast() can access uninitialized KateViewConfig and crash.
// dynamic_cast only needs the vtable which is valid once the C++ ctor runs.
auto *noteIface = dynamic_cast<KTextEditor::InlineNoteInterface *>(view);
if (noteIface)
noteIface->registerInlineNoteProvider(this);
#endif
@ -120,7 +123,7 @@ OllieGhostProvider::~OllieGhostProvider()
#if KTEXTEDITOR_VERSION_MAJOR >= 6
m_view->unregisterInlineNoteProvider(this);
#else
auto *iface = qobject_cast<KTextEditor::InlineNoteInterface *>(m_view);
auto *iface = dynamic_cast<KTextEditor::InlineNoteInterface *>(m_view);
if (iface)
iface->unregisterInlineNoteProvider(this);
#endif
@ -257,7 +260,7 @@ void OllieGhostProvider::onCursorPositionChanged()
if (m_view->isCompletionActive())
return;
#else
auto *ccIface = qobject_cast<KTextEditor::CodeCompletionInterface *>(m_view);
auto *ccIface = dynamic_cast<KTextEditor::CodeCompletionInterface *>(m_view);
if (ccIface && ccIface->isCompletionActive())
return;
#endif
@ -380,7 +383,7 @@ void OllieGhostProvider::showOverlay()
QFont font = m_view->configValue(QStringLiteral("font")).value<QFont>();
#else
QFont font;
auto *cfgIface = qobject_cast<KTextEditor::ConfigInterface *>(m_view);
auto *cfgIface = dynamic_cast<KTextEditor::ConfigInterface *>(m_view);
if (cfgIface)
font = cfgIface->configValue(QStringLiteral("font")).value<QFont>();
else

View File

@ -749,7 +749,7 @@ bool OllieKateView::eventFilter(QObject *obj, QEvent *event)
(activeView && activeView->isCompletionActive()))
return QObject::eventFilter(obj, event);
#else
auto *ccIface = activeView ? qobject_cast<KTextEditor::CodeCompletionInterface *>(activeView) : nullptr;
auto *ccIface = activeView ? dynamic_cast<KTextEditor::CodeCompletionInterface *>(activeView) : nullptr;
if (QApplication::activePopupWidget() ||
(ccIface && ccIface->isCompletionActive()))
return QObject::eventFilter(obj, event);

View File

@ -11,6 +11,7 @@
#include <QDBusInterface>
#include <QDBusConnection>
#include <QPointer>
#include <QTextEdit>
#include <QLineEdit>
#include <QLabel>
@ -109,7 +110,7 @@ private:
QString m_sessionId;
qlonglong m_chatOffset = 0;
OllieGhostProvider *m_ghost = nullptr;
QPointer<OllieGhostProvider> m_ghost;
bool m_ghostPrefix = false; // true after Meta+O, waiting for second key
QString projectBaseDir();