From 455d9c91903e445a0f66b338116cfb04fcf87d8f Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 15 Jul 2026 11:06:20 +0200 Subject: [PATCH] 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 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. --- kate/ollie_ghost.cpp | 11 +++++++---- kate/ollie_kate.cpp | 2 +- kate/ollie_kate.h | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/kate/ollie_ghost.cpp b/kate/ollie_ghost.cpp index f342bb5..f54f9e1 100644 --- a/kate/ollie_ghost.cpp +++ b/kate/ollie_ghost.cpp @@ -106,7 +106,10 @@ OllieGhostProvider::OllieGhostProvider(KTextEditor::View *view, QDBusInterface * #if KTEXTEDITOR_VERSION_MAJOR >= 6 view->registerInlineNoteProvider(this); #else - auto *noteIface = qobject_cast(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(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(m_view); + auto *iface = dynamic_cast(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(m_view); + auto *ccIface = dynamic_cast(m_view); if (ccIface && ccIface->isCompletionActive()) return; #endif @@ -380,7 +383,7 @@ void OllieGhostProvider::showOverlay() QFont font = m_view->configValue(QStringLiteral("font")).value(); #else QFont font; - auto *cfgIface = qobject_cast(m_view); + auto *cfgIface = dynamic_cast(m_view); if (cfgIface) font = cfgIface->configValue(QStringLiteral("font")).value(); else diff --git a/kate/ollie_kate.cpp b/kate/ollie_kate.cpp index f378592..feeba98 100644 --- a/kate/ollie_kate.cpp +++ b/kate/ollie_kate.cpp @@ -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(activeView) : nullptr; + auto *ccIface = activeView ? dynamic_cast(activeView) : nullptr; if (QApplication::activePopupWidget() || (ccIface && ccIface->isCompletionActive())) return QObject::eventFilter(obj, event); diff --git a/kate/ollie_kate.h b/kate/ollie_kate.h index 97289da..55066ce 100644 --- a/kate/ollie_kate.h +++ b/kate/ollie_kate.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -109,7 +110,7 @@ private: QString m_sessionId; qlonglong m_chatOffset = 0; - OllieGhostProvider *m_ghost = nullptr; + QPointer m_ghost; bool m_ghostPrefix = false; // true after Meta+O, waiting for second key QString projectBaseDir();