Fix crash when window attach is called without waiting for frame callbacks

If QWaylandWindow::attach was called before getting the frame callback, it
would overwrite mFrameCallback. Hence, all but the last frame callback would
still be alive after the QWaylandWindow destructor.

When the dangling callbacks got invoked the data pointer was statically casted
to the deleted QWaylandWindow, resulting in undefined behavior.

In this change we only delete frame callbacks from the render thread, avoiding
a race condition we fixed earlier. And we always destroy the frame callback
when adding a new one, ensuring that the destructor will clean up the only
remaining callback.

There's a test confirming that the crash has been fixed.

This fixes the flakiness of many of the qtbase auto tests.

Change-Id: Iecb08ab48216eac61b1ebc5c0e0664d4aac900c0
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Johan Klokkhammer Helsing 2017-08-30 14:00:51 +02:00 committed by Johan Helsing
parent 9041247dc7
commit 027a713154
3 changed files with 34 additions and 9 deletions

View File

@ -86,7 +86,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window)
, mMouseEventsInContentArea(false)
, mMousePressedInContentArea(Qt::NoButton)
, mWaitingForFrameSync(false)
, mFrameCallback(nullptr)
, mRequestResizeSent(false)
, mCanResize(true)
, mResizeDirty(false)
@ -475,12 +474,14 @@ void QWaylandWindow::requestResize()
void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y)
{
mFrameCallback = nullptr;
if (mFrameCallback) {
wl_callback_destroy(mFrameCallback);
mFrameCallback = nullptr;
}
if (buffer) {
auto callback = frame();
wl_callback_add_listener(callback, &QWaylandWindow::callbackListener, this);
mFrameCallback = callback;
mFrameCallback = frame();
wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this);
mWaitingForFrameSync = true;
buffer->setBusy();
@ -523,8 +524,6 @@ void QWaylandWindow::frameCallback(void *data, struct wl_callback *callback, uin
QWaylandWindow *self = static_cast<QWaylandWindow*>(data);
self->mWaitingForFrameSync = false;
wl_callback_destroy(callback);
self->mFrameCallback.testAndSetRelaxed(callback, nullptr);
if (self->mUpdateRequested) {
QWindowPrivate *w = QWindowPrivate::get(self->window());
self->mUpdateRequested = false;

View File

@ -53,7 +53,6 @@
#include <QtCore/QWaitCondition>
#include <QtCore/QMutex>
#include <QtCore/QAtomicPointer>
#include <QtGui/QIcon>
#include <QtCore/QVariant>
@ -222,7 +221,7 @@ protected:
WId mWindowId;
bool mWaitingForFrameSync;
QAtomicPointer<struct wl_callback> mFrameCallback;
struct ::wl_callback *mFrameCallback = nullptr;
QWaitCondition mFrameSyncWait;
QMutex mResizeLock;

View File

@ -142,6 +142,7 @@ private slots:
void backingStore();
void touchDrag();
void mouseDrag();
void dontCrashOnMultipleCommits();
private:
MockCompositor *compositor;
@ -333,6 +334,32 @@ void tst_WaylandClient::mouseDrag()
QTRY_VERIFY(window.dragStarted);
}
void tst_WaylandClient::dontCrashOnMultipleCommits()
{
auto window = new TestWindow();
window->show();
QRect rect(QPoint(), window->size());
QBackingStore backingStore(window);
backingStore.resize(rect.size());
backingStore.beginPaint(rect);
QPainter p(backingStore.paintDevice());
p.fillRect(rect, Qt::magenta);
p.end();
backingStore.endPaint();
backingStore.flush(rect);
backingStore.flush(rect);
backingStore.flush(rect);
compositor->processWaylandEvents();
delete window;
QTRY_VERIFY(!compositor->surface());
}
int main(int argc, char **argv)
{
setenv("XDG_RUNTIME_DIR", ".", 1);