Fix crash in QMacPanGestureRecognizer

The reason it crashed was this:
1. Button was pressed => _panTimer started with the graphics view as
   destination.
2. Button was released => the graphicsview is destroyed
3. 300 ms later: Qt tries to deliver TimerEvent from _panTimer to the
   graphics view. Unfortunately, the graphics view is deleted, but Qt
   doesn't know that... (*crash*)

We therefore chose to start the timer with a destination we are controlling the
lifetime of: the QMacPanGestureRecognizer. Inside the timerEvent of that we can
check if the actual target is already destroyed.

Task-number: QTBUG-60404
Change-Id: Iff8f5b7217de42c4c5cf551ca507f0cff1c99a78
Reviewed-by: Frederik Gladhorn <frederik.gladhorn@qt.io>
This commit is contained in:
Jan Arve Saether 2018-04-23 11:39:56 +02:00 committed by Jan Arve Sæther
parent 364bd6ca74
commit c1ead32a84
3 changed files with 45 additions and 2 deletions

View File

@ -43,6 +43,7 @@
#include "qevent.h" #include "qevent.h"
#include "qwidget.h" #include "qwidget.h"
#include "qdebug.h" #include "qdebug.h"
#include <QtCore/qcoreapplication.h>
#ifndef QT_NO_GESTURES #ifndef QT_NO_GESTURES
@ -181,6 +182,16 @@ QGesture *QMacPanGestureRecognizer::create(QObject *target)
return 0; return 0;
} }
void QMacPanGestureRecognizer::timerEvent(QTimerEvent *ev)
{
if (ev->timerId() == _panTimer.timerId()) {
if (_panTimer.isActive())
_panTimer.stop();
if (_target)
QCoreApplication::sendEvent(_target, ev);
}
}
QGestureRecognizer::Result QGestureRecognizer::Result
QMacPanGestureRecognizer::recognize(QGesture *gesture, QObject *target, QEvent *event) QMacPanGestureRecognizer::recognize(QGesture *gesture, QObject *target, QEvent *event)
{ {
@ -195,7 +206,8 @@ QMacPanGestureRecognizer::recognize(QGesture *gesture, QObject *target, QEvent *
if (ev->touchPoints().size() == 1) { if (ev->touchPoints().size() == 1) {
reset(gesture); reset(gesture);
_startPos = QCursor::pos(); _startPos = QCursor::pos();
_panTimer.start(panBeginDelay, target); _target = target;
_panTimer.start(panBeginDelay, this);
_panCanceled = false; _panCanceled = false;
return QGestureRecognizer::MayBeGesture; return QGestureRecognizer::MayBeGesture;
} }
@ -242,7 +254,6 @@ QMacPanGestureRecognizer::recognize(QGesture *gesture, QObject *target, QEvent *
case QEvent::Timer: { case QEvent::Timer: {
QTimerEvent *ev = static_cast<QTimerEvent *>(event); QTimerEvent *ev = static_cast<QTimerEvent *>(event);
if (ev->timerId() == _panTimer.timerId()) { if (ev->timerId() == _panTimer.timerId()) {
_panTimer.stop();
if (_panCanceled) if (_panCanceled)
break; break;
// Begin new pan session! // Begin new pan session!

View File

@ -55,6 +55,7 @@
#include "qtimer.h" #include "qtimer.h"
#include "qpoint.h" #include "qpoint.h"
#include "qgesturerecognizer.h" #include "qgesturerecognizer.h"
#include <QtCore/qpointer.h>
#ifndef QT_NO_GESTURES #ifndef QT_NO_GESTURES
@ -88,10 +89,13 @@ public:
QGesture *create(QObject *target); QGesture *create(QObject *target);
QGestureRecognizer::Result recognize(QGesture *gesture, QObject *watched, QEvent *event); QGestureRecognizer::Result recognize(QGesture *gesture, QObject *watched, QEvent *event);
void reset(QGesture *gesture); void reset(QGesture *gesture);
protected:
void timerEvent(QTimerEvent *ev) override;
private: private:
QPointF _startPos; QPointF _startPos;
QBasicTimer _panTimer; QBasicTimer _panTimer;
bool _panCanceled; bool _panCanceled;
QPointer<QObject> _target;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -331,6 +331,9 @@ private slots:
void graphicsViewParentPropagation(); void graphicsViewParentPropagation();
void panelPropagation(); void panelPropagation();
void panelStacksBehindParent(); void panelStacksBehindParent();
#ifdef Q_OS_MACOS
void deleteMacPanGestureRecognizerTargetWidget();
#endif
void deleteGestureTargetWidget(); void deleteGestureTargetWidget();
void deleteGestureTargetItem_data(); void deleteGestureTargetItem_data();
void deleteGestureTargetItem(); void deleteGestureTargetItem();
@ -1807,6 +1810,31 @@ void tst_Gestures::panelStacksBehindParent()
QCOMPARE(panel->gestureOverrideEventsReceived, 0); QCOMPARE(panel->gestureOverrideEventsReceived, 0);
} }
#ifdef Q_OS_MACOS
void tst_Gestures::deleteMacPanGestureRecognizerTargetWidget()
{
QWidget window;
window.resize(400,400);
QGraphicsScene scene;
QGraphicsView *view = new QGraphicsView(&scene, &window);
view->resize(400, 400);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
QTouchDevice *device = QTest::createTouchDevice();
// QMacOSPenGestureRecognizer will start a timer on a touch press event
QTest::touchEvent(&window, device).press(1, QPoint(100, 100), &window);
delete view;
// wait until after that the QMacOSPenGestureRecognizer timer (300ms) is triggered.
// This is needed so that the whole test does not finish before the timer triggers
// and to make sure it crashes while executing *this* function. (otherwise it might give the
// impression that some of the subsequent test function caused the crash...)
QTest::qWait(400); // DO NOT CRASH while waiting
}
#endif
void tst_Gestures::deleteGestureTargetWidget() void tst_Gestures::deleteGestureTargetWidget()
{ {
} }