From: Frank Reininghaus Date: Mon, 5 Nov 2012 21:03:52 +0000 (+0100) Subject: Prevent crashes caused by nested event loops run when renaming inline X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/commitdiff_plain/c0559a2a1d7d66b26e1d00b4ff59c7fce8848566?ds=inline Prevent crashes caused by nested event loops run when renaming inline When renaming inline and starting a drag or invoking the context menu, a nested event loop will be run. If the role editor loses focus and emits roleEditingFinished(), we must prevent that deleteLater() is called because this would delete the role editor inside a nested event loop which is run from one of its own functions. We would get a crash when returning from that event loop otherwise. BUG: 308018 BUG: 309421 FIXED-IN: 4.9.4 --- diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index bdc2859c0..f92cab50f 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -609,7 +609,10 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant))); disconnect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant))); - m_roleEditor->deleteLater(); + // Do not delete the role editor using deleteLater() because we might be + // inside a nested event loop which has been started by one of its event + // handlers (contextMenuEvent() or drag&drop inside mouseMoveEvent()). + m_roleEditor->deleteWhenIdle(); m_roleEditor = 0; } return; @@ -1274,7 +1277,11 @@ void KStandardItemListWidget::closeRoleEditor() this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant))); disconnect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)), this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant))); - m_roleEditor->deleteLater(); + + // Do not delete the role editor using deleteLater() because we might be + // inside a nested event loop which has been started by one of its event + // handlers (contextMenuEvent() or drag&drop inside mouseMoveEvent()). + m_roleEditor->deleteWhenIdle(); m_roleEditor = 0; } diff --git a/src/kitemviews/private/kitemlistroleeditor.cpp b/src/kitemviews/private/kitemlistroleeditor.cpp index 1e4b5fd4e..ead5b298e 100644 --- a/src/kitemviews/private/kitemlistroleeditor.cpp +++ b/src/kitemviews/private/kitemlistroleeditor.cpp @@ -26,7 +26,9 @@ KItemListRoleEditor::KItemListRoleEditor(QWidget *parent) : KTextEdit(parent), m_index(0), m_role(), - m_blockFinishedSignal(false) + m_blockFinishedSignal(false), + m_eventHandlingLevel(0), + m_deleteAfterEventHandling(false) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -64,6 +66,20 @@ QByteArray KItemListRoleEditor::role() const return m_role; } +void KItemListRoleEditor::deleteWhenIdle() +{ + if (m_eventHandlingLevel > 0) { + // We are handling an event at the moment. It could be that we + // are in a nested event loop run by contextMenuEvent() or a + // call of mousePressEvent() which results in drag&drop. + // -> do not call deleteLater() to prevent a crash when we + // return from the nested event loop. + m_deleteAfterEventHandling = true; + } else { + deleteLater(); + } +} + bool KItemListRoleEditor::eventFilter(QObject* watched, QEvent* event) { if (watched == parentWidget() && event->type() == QEvent::Resize) { @@ -75,13 +91,42 @@ bool KItemListRoleEditor::eventFilter(QObject* watched, QEvent* event) bool KItemListRoleEditor::event(QEvent* event) { + ++m_eventHandlingLevel; + if (event->type() == QEvent::FocusOut) { QFocusEvent* focusEvent = static_cast(event); if (focusEvent->reason() != Qt::PopupFocusReason) { emitRoleEditingFinished(); } } - return KTextEdit::event(event); + + const int result = KTextEdit::event(event); + --m_eventHandlingLevel; + + if (m_deleteAfterEventHandling && m_eventHandlingLevel == 0) { + // Schedule this object for deletion and make sure that we do not try + // to deleteLater() again when the DeferredDelete event is received. + deleteLater(); + m_deleteAfterEventHandling = false; + } + + return result; +} + +bool KItemListRoleEditor::viewportEvent(QEvent* event) +{ + ++m_eventHandlingLevel; + const bool result = KTextEdit::viewportEvent(event); + --m_eventHandlingLevel; + + if (m_deleteAfterEventHandling && m_eventHandlingLevel == 0) { + // Schedule this object for deletion and make sure that we do not try + // to deleteLater() again when the DeferredDelete event is received. + deleteLater(); + m_deleteAfterEventHandling = false; + } + + return result; } void KItemListRoleEditor::keyPressEvent(QKeyEvent* event) diff --git a/src/kitemviews/private/kitemlistroleeditor.h b/src/kitemviews/private/kitemlistroleeditor.h index aa2c97754..a2f705808 100644 --- a/src/kitemviews/private/kitemlistroleeditor.h +++ b/src/kitemviews/private/kitemlistroleeditor.h @@ -47,6 +47,15 @@ public: void setRole(const QByteArray& role); QByteArray role() const; + /** + * Calls deleteLater() if no event is being handled at the moment. + * Otherwise, the deletion is deferred until the event handling is + * finished. This prevents that the deletion happens inside a nested + * event loop which might be run in contextMenuEvent() or + * mouseMoveEvent() because this would probably cause a crash. + */ + void deleteWhenIdle(); + virtual bool eventFilter(QObject* watched, QEvent* event); signals: @@ -55,6 +64,7 @@ signals: protected: virtual bool event(QEvent* event); + virtual bool viewportEvent(QEvent* event); virtual void keyPressEvent(QKeyEvent* event); private slots: @@ -75,6 +85,8 @@ private: int m_index; QByteArray m_role; bool m_blockFinishedSignal; + int m_eventHandlingLevel; + bool m_deleteAfterEventHandling; }; #endif