diff options
author | Yulong Bai <[email protected]> | 2019-05-21 13:48:52 +0200 |
---|---|---|
committer | Yulong Bai <[email protected]> | 2019-06-17 16:19:15 +0200 |
commit | e9520ec84c95e10a6826b2289e46552a2d446895 (patch) | |
tree | 74b0a97cb50c3608f82b4f3bb2cc00e115164b73 /src/quick | |
parent | c1663865e68d96d4a51351d4d1d2bfa5f313dc18 (diff) |
Fix crash caused by objects self-destructions during displacement animations
The root cause was that the QAbstractAnimationJob::finished() might delegate its
destruction to change.listener->animationFinished(this), and the original author
was aware of that and provided a RETURN_IF_DELETE macro to return early if itself
got deleted. In the bug's case, change.listener->animationFinished(this)
dispatched to QQuickItemViewPrivate::animationFinished() which called
QQuickItemViewPrivate::release() and deleted the QAbstractAnimationJob object
itself in the end.
However, any objects derived from QAbstractAnimationJob, or holding a pointer
to a QAbstractAnimationJob, may potentially fall into the code path calling
QAbstractAnimationJob::finished(). Any QAnimationJobChangeListener that directly
or indirectly deletes QAbstractAnimationJob should be very suspicious to this
kind of "heap-use-after-free" bug. Should ensure that the QAbstractAnimationJob
won't be referenced after deletion.
In the bug's case, within the code path triggered by ListView displacement
animation, the other affected classes by QAbstractAnimationJob are:
QQuickItemViewFxItem, QQuickItemViewTransitionableItem, QQuickTransitionManager.
To fix this, a new SelfDeletable class is factored out to simplify the self-deletion
test logic. Any affected classes are made to have a public member m_selfDeletable.
Any code paths that finally reach QAbstractAnimationJob::finished() are
wrapped with related util macro.
Change-Id: Idd33fc3f2d529fd7d8bb088c329101b1e70dd6c0
Task-number: QTBUG-44308
Reviewed-by: Richard Moe Gustavsen <[email protected]>
Diffstat (limited to 'src/quick')
-rw-r--r-- | src/quick/items/qquickflickable.cpp | 2 | ||||
-rw-r--r-- | src/quick/items/qquickitemview.cpp | 5 | ||||
-rw-r--r-- | src/quick/items/qquickitemviewfxitem.cpp | 2 | ||||
-rw-r--r-- | src/quick/items/qquickitemviewfxitem_p_p.h | 2 | ||||
-rw-r--r-- | src/quick/items/qquickitemviewtransition.cpp | 20 | ||||
-rw-r--r-- | src/quick/items/qquickitemviewtransition_p.h | 2 | ||||
-rw-r--r-- | src/quick/util/qquicktransitionmanager.cpp | 7 | ||||
-rw-r--r-- | src/quick/util/qquicktransitionmanager_p_p.h | 2 |
8 files changed, 22 insertions, 20 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index d6dddc3f1c..7e1f54f07e 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -206,8 +206,8 @@ public: axisData->move.setValue(-flickable->contentX()); else axisData->move.setValue(-flickable->contentY()); - cancel(); active = false; + cancel(); } protected: diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index 8dafc16cf4..2e1962bc7b 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -2225,7 +2225,10 @@ bool QQuickItemViewPrivate::prepareNonVisibleItemTransition(FxViewItem *item, co if (item->scheduledTransitionType() == QQuickItemViewTransitioner::MoveTransition) repositionItemAt(item, item->index, 0); - if (item->prepareTransition(transitioner, viewBounds)) { + bool success = false; + ACTION_IF_DELETED(item, success = item->prepareTransition(transitioner, viewBounds), return success); + + if (success) { item->releaseAfterTransition = true; return true; } diff --git a/src/quick/items/qquickitemviewfxitem.cpp b/src/quick/items/qquickitemviewfxitem.cpp index f9c65967ea..16c2182962 100644 --- a/src/quick/items/qquickitemviewfxitem.cpp +++ b/src/quick/items/qquickitemviewfxitem.cpp @@ -56,6 +56,8 @@ QQuickItemViewFxItem::QQuickItemViewFxItem(QQuickItem *item, bool ownItem, QQuic QQuickItemViewFxItem::~QQuickItemViewFxItem() { delete transitionableItem; + transitionableItem = nullptr; + if (ownItem && item) { trackGeometry(false); item->setParentItem(0); diff --git a/src/quick/items/qquickitemviewfxitem_p_p.h b/src/quick/items/qquickitemviewfxitem_p_p.h index 48ffe248bc..d10ebb9cdf 100644 --- a/src/quick/items/qquickitemviewfxitem_p_p.h +++ b/src/quick/items/qquickitemviewfxitem_p_p.h @@ -54,6 +54,7 @@ #include <QtQuick/private/qtquickglobal_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitemviewtransition_p.h> +#include <private/qanimationjobutil_p.h> QT_REQUIRE_CONFIG(quick_itemview); @@ -94,6 +95,7 @@ public: virtual bool contains(qreal x, qreal y) const = 0; + SelfDeletable m_selfDeletable; int index = -1; QPointer<QQuickItem> item; bool ownItem; diff --git a/src/quick/items/qquickitemviewtransition.cpp b/src/quick/items/qquickitemviewtransition.cpp index 0fde0beb75..109851608b 100644 --- a/src/quick/items/qquickitemviewtransition.cpp +++ b/src/quick/items/qquickitemviewtransition.cpp @@ -61,7 +61,6 @@ public: QPointF m_toPos; QQuickItemViewTransitioner::TransitionType m_type; bool m_isTarget; - bool *m_wasDeleted; protected: void finished() override; @@ -73,14 +72,11 @@ QQuickItemViewTransitionJob::QQuickItemViewTransitionJob() , m_item(nullptr) , m_type(QQuickItemViewTransitioner::NoTransition) , m_isTarget(false) - , m_wasDeleted(nullptr) { } QQuickItemViewTransitionJob::~QQuickItemViewTransitionJob() { - if (m_wasDeleted) - *m_wasDeleted = true; if (m_transitioner) m_transitioner->runningJobs.remove(this); } @@ -138,13 +134,7 @@ void QQuickItemViewTransitionJob::finished() QQuickTransitionManager::finished(); if (m_transitioner) { - bool deleted = false; - m_wasDeleted = &deleted; - m_transitioner->finishedTransition(this, m_item); - if (deleted) - return; - m_wasDeleted = nullptr; - + RETURN_IF_DELETED(m_transitioner->finishedTransition(this, m_item)); m_transitioner = nullptr; } @@ -482,7 +472,7 @@ bool QQuickItemViewTransitionableItem::prepareTransition(QQuickItemViewTransitio // if transition type is not valid, the previous transition still has to be // canceled so that the item can move immediately to the right position item->setPosition(nextTransitionTo); - stopTransition(); + ACTION_IF_DELETED(this, stopTransition(), return false); } prepared = true; @@ -501,12 +491,12 @@ void QQuickItemViewTransitionableItem::startTransition(QQuickItemViewTransitione if (!transition || transition->m_type != nextTransitionType || transition->m_isTarget != isTransitionTarget) { if (transition) - transition->cancel(); + RETURN_IF_DELETED(transition->cancel()); delete transition; transition = new QQuickItemViewTransitionJob; } - transition->startTransition(this, index, transitioner, nextTransitionType, nextTransitionTo, isTransitionTarget); + RETURN_IF_DELETED(transition->startTransition(this, index, transitioner, nextTransitionType, nextTransitionTo, isTransitionTarget)); clearCurrentScheduledTransition(); } @@ -558,7 +548,7 @@ void QQuickItemViewTransitionableItem::clearCurrentScheduledTransition() void QQuickItemViewTransitionableItem::stopTransition() { if (transition) - transition->cancel(); + RETURN_IF_DELETED(transition->cancel()); clearCurrentScheduledTransition(); resetNextTransitionPos(); } diff --git a/src/quick/items/qquickitemviewtransition_p.h b/src/quick/items/qquickitemviewtransition_p.h index 29a62f7f10..0c7a9cad75 100644 --- a/src/quick/items/qquickitemviewtransition_p.h +++ b/src/quick/items/qquickitemviewtransition_p.h @@ -60,6 +60,7 @@ QT_REQUIRE_CONFIG(quick_viewtransitions); #include <QtQml/qqml.h> #include <private/qqmlguard_p.h> #include <private/qquicktransition_p.h> +#include <private/qanimationjobutil_p.h> QT_BEGIN_NAMESPACE @@ -157,6 +158,7 @@ public: bool prepareTransition(QQuickItemViewTransitioner *transitioner, int index, const QRectF &viewBounds); void startTransition(QQuickItemViewTransitioner *transitioner, int index); + SelfDeletable m_selfDeletable; QPointF nextTransitionTo; QPointF lastMovedTo; QPointF nextTransitionFrom; diff --git a/src/quick/util/qquicktransitionmanager.cpp b/src/quick/util/qquicktransitionmanager.cpp index e51de1a02a..0ee7e57997 100644 --- a/src/quick/util/qquicktransitionmanager.cpp +++ b/src/quick/util/qquicktransitionmanager.cpp @@ -47,6 +47,7 @@ #include <private/qqmlproperty_p.h> #include <QtCore/qdebug.h> +#include <private/qanimationjobutil_p.h> QT_BEGIN_NAMESPACE @@ -79,6 +80,7 @@ void QQuickTransitionManager::setState(QQuickState *s) QQuickTransitionManager::~QQuickTransitionManager() { delete d->transitionInstance; + d->transitionInstance = nullptr; delete d; d = nullptr; } @@ -129,7 +131,7 @@ void QQuickTransitionManager::transition(const QList<QQuickStateAction> &list, QQuickTransition *transition, QObject *defaultTarget) { - cancel(); + RETURN_IF_DELETED(cancel()); // The copy below is ON PURPOSE, because firing actions might involve scripts that modify the list. QQuickStateOperation::ActionList applyList = list; @@ -154,7 +156,6 @@ void QQuickTransitionManager::transition(const QList<QQuickStateAction> &list, // // This doesn't catch everything, and it might be a little fragile in // some cases - but whatcha going to do? - if (transition && !d->bindingsList.isEmpty()) { // Apply all the property and binding changes @@ -258,7 +259,7 @@ void QQuickTransitionManager::transition(const QList<QQuickStateAction> &list, void QQuickTransitionManager::cancel() { if (d->transitionInstance && d->transitionInstance->isRunning()) - d->transitionInstance->stop(); + RETURN_IF_DELETED(d->transitionInstance->stop()); for (const QQuickStateAction &action : qAsConst(d->bindingsList)) { if (action.toBinding && action.deletableToBinding) { diff --git a/src/quick/util/qquicktransitionmanager_p_p.h b/src/quick/util/qquicktransitionmanager_p_p.h index 89317e1e07..fc00ec8a52 100644 --- a/src/quick/util/qquicktransitionmanager_p_p.h +++ b/src/quick/util/qquicktransitionmanager_p_p.h @@ -52,6 +52,7 @@ // #include "qquickanimation_p.h" +#include <private/qanimationjobutil_p.h> QT_BEGIN_NAMESPACE @@ -70,6 +71,7 @@ public: void cancel(); + SelfDeletable m_selfDeletable; protected: virtual void finished(); |