Skip to content

Commit

Permalink
Fix (flaky) heap-use-after-free in QQuickItemGrabResult::render
Browse files Browse the repository at this point in the history
The full backtrace is below.

My understanding is that this line in TestHelper::addNewGuides

   VERIFY(imageGrabber.requestImage(canvas));

results in QQuickItem::grabToImage connecting to this signal:

    connect(window(), &QQuickWindow::afterRendering, result, &QQuickItemGrabResult::render, Qt::DirectConnection);

We then wait until the image is not null:

    TRY_VERIFY(imageGrabber.isReady());

This is important, as we should really be listening to the ready signal.

Meanwhile, on the render thread, the image is set at some point:

    d->image =  d->texture->toImage();

I'm guessing that, back in the main thread, we then detect that the image is not null,
store it, and destroy the QQuickItemGrabResult:

    const QImage originalCanvasGrab = imageGrabber.takeImage();

QQuickItemGrabResult::render() is still running though, so it tries to delete the texture
and set it to null:

    delete d->texture;
    d->texture = nullptr;

The latter causes the HUAF.

Fix it by listening to the ready signal, which is emitted via a posted event,
so we can be sure that it's safe to destroy the QQuickItemGrabResult.

=================================================================
==26184==ERROR: AddressSanitizer: heap-use-after-free on address 0x6110005356d0 at pc 0x00011a46f761 bp 0x700006e81610 sp 0x700006e81608
WRITE of size 8 at 0x6110005356d0 thread T5
    #0 0x11a46f760 in QQuickItemGrabResult::render() qquickitemgrabresult.cpp:294
    #1 0x11a47e648 in QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, void (QQuickItemGrabResult::*)()>::call(void (QQuickItemGrabResult::*)(), QQuickItemGrabResult*, void**) qobjectdefs_impl.h:171
    #2 0x11a47e34d in void QtPrivate::FunctionPointer<void (QQuickItemGrabResult::*)()>::call<QtPrivate::List<>, void>(void (QQuickItemGrabResult::*)(), QQuickItemGrabResult*, void**) qobjectdefs_impl.h:208
    #3 0x11a47def2 in QtPrivate::QSlotObject<void (QQuickItemGrabResult::*)(), QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) qobjectdefs_impl.h:419
    #4 0x11d620cd5 in QtPrivate::QSlotObjectBase::call(QObject*, void**) qobjectdefs_impl.h:399
    #5 0x11d7a84d0 in void doActivate<false>(QObject*, int, void**) qobject.cpp:3932
    #6 0x11d7a53a8 in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) qobject.cpp:3992
    #7 0x11a789314 in QQuickWindow::afterRendering() moc_qquickwindow.cpp:590
    #8 0x11a788d68 in QQuickWindowPrivate::renderSceneGraph(QSize const&, QSize const&) qquickwindow.cpp:668
    #9 0x11b39b84e in QSGRenderThread::syncAndRender() qsgthreadedrenderloop.cpp:769
    #10 0x11b39f607 in QSGRenderThread::run() qsgthreadedrenderloop.cpp:974
    #11 0x11df239da in QThreadPrivate::start(void*)::$_0::operator()() const qthread_unix.cpp:358
    #12 0x11df1c80c in void (anonymous namespace)::terminate_on_exception<QThreadPrivate::start(void*)::$_0>(QThreadPrivate::start(void*)::$_0&&) qthread_unix.cpp:294
    #13 0x11df1c446 in QThreadPrivate::start(void*) qthread_unix.cpp:317
    #14 0x7ff80ab714e0 in _pthread_start+0x7c (libsystem_pthread.dylib:x86_64+0x64e0)
    #15 0x7ff80ab6cf6a in thread_start+0xe (libsystem_pthread.dylib:x86_64+0x1f6a)

0x6110005356d0 is located 208 bytes inside of 240-byte region [0x611000535600,0x6110005356f0)
freed by thread T0 here:
    #0 0x11091c66d in wrap__ZdlPv+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x5766d)
    #1 0x11a473221 in QQuickItemGrabResultPrivate::~QQuickItemGrabResultPrivate() qquickitemgrabresult.cpp:73
    #2 0x11d7c7778 in QScopedPointerDeleter<QObjectData>::cleanup(QObjectData*) qscopedpointer.h:60
    #3 0x11d7c76dc in QScopedPointer<QObjectData, QScopedPointerDeleter<QObjectData> >::~QScopedPointer() qscopedpointer.h:116
    #4 0x11d788144 in QScopedPointer<QObjectData, QScopedPointerDeleter<QObjectData> >::~QScopedPointer() qscopedpointer.h:114
    #5 0x11d78941a in QObject::~QObject() qobject.cpp:1116
    #6 0x11a4753d4 in QQuickItemGrabResult::~QQuickItemGrabResult() qquickitemgrabresult.h:57
    #7 0x11a472ff4 in QQuickItemGrabResult::~QQuickItemGrabResult() qquickitemgrabresult.h:57
    #8 0x11a473018 in QQuickItemGrabResult::~QQuickItemGrabResult() qquickitemgrabresult.h:57
    #9 0x11a47ecb8 in QtSharedPointer::CustomDeleter<QQuickItemGrabResult, QtSharedPointer::NormalDeleter>::execute() qsharedpointer_impl.h:190
    #10 0x11a47eb70 in QtSharedPointer::ExternalRefCountWithCustomDeleter<QQuickItemGrabResult, QtSharedPointer::NormalDeleter>::deleter(QtSharedPointer::ExternalRefCountData*) qsharedpointer_impl.h:208
    #11 0x10e3d46fa in QtSharedPointer::ExternalRefCountData::destroy() qsharedpointer_impl.h:146
    #12 0x10e3d4649 in QSharedPointer<QQuickItemGrabResult>::deref(QtSharedPointer::ExternalRefCountData*) qsharedpointer_impl.h:477
    #13 0x10e3d4608 in QSharedPointer<QQuickItemGrabResult>::deref() qsharedpointer_impl.h:472
    #14 0x10e3d45b4 in QSharedPointer<QQuickItemGrabResult>::~QSharedPointer() qsharedpointer_impl.h:312
    #15 0x10e3d4074 in QSharedPointer<QQuickItemGrabResult>::~QSharedPointer() qsharedpointer_impl.h:312
    #16 0x10e3d4b4f in QSharedPointer<QQuickItemGrabResult>::clear() qsharedpointer_impl.h:416
    #17 0x10e3d4a24 in QSharedPointer<QQuickItemGrabResult>::reset() qsharedpointer_impl.h:383
    #18 0x10e23b7ee in ImageGrabber::takeImage() testhelper.h:78
    #19 0x10e5a1424 in TestHelper::addNewGuides(int, int, TestHelper::AddNewGuidesFlag) testhelper.cpp:1601
    #20 0x10e2def3c in tst_App::addAndDeleteMultipleGuides() tst_app.cpp:3484
    #21 0x10e3abed9 in tst_App::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) tst_app.moc:744
    #22 0x11d61f5ea in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const qmetaobject.cpp:2390
    #23 0x10f298aed in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const qmetaobject.h:126
    #24 0x10f296661 in QTest::TestMethods::invokeTestOnData(int) const qtestcase.cpp:966
    #25 0x10f299b96 in QTest::TestMethods::invokeTest(int, char const*, QTest::WatchDog*) const qtestcase.cpp:1210
    #26 0x10f2a002a in QTest::TestMethods::invokeTests(QObject*) const qtestcase.cpp:1552
    #27 0x10f2a23cf in QTest::qRun() qtestcase.cpp:2018
    #28 0x10f2a1047 in QTest::qExec(QObject*, int, char**) qtestcase.cpp:1920
    #29 0x10e3ab836 in main tst_app.cpp:7095

previously allocated by thread T0 here:
    #0 0x11091c24d in wrap__Znwm+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x5724d)
    #1 0x11a46cd45 in QQuickItemGrabResult::QQuickItemGrabResult(QObject*) qquickitemgrabresult.cpp:175
    #2 0x11a46cdec in QQuickItemGrabResult::QQuickItemGrabResult(QObject*) qquickitemgrabresult.cpp:176
    #3 0x11a470b1a in QQuickItemGrabResultPrivate::create(QQuickItem*, QSize const&) qquickitemgrabresult.cpp:326
    #4 0x11a470efd in QQuickItem::grabToImage(QSize const&) qquickitemgrabresult.cpp:360
    #5 0x10e23b2bf in ImageGrabber::requestImage(QQuickItem*) testhelper.h:64
    #6 0x10e59fb7b in TestHelper::addNewGuides(int, int, TestHelper::AddNewGuidesFlag) testhelper.cpp:1599
    #7 0x10e2def3c in tst_App::addAndDeleteMultipleGuides() tst_app.cpp:3484
    #8 0x10e3abed9 in tst_App::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) tst_app.moc:744
    #9 0x11d61f5ea in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const qmetaobject.cpp:2390
    #10 0x10f298aed in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const qmetaobject.h:126
    #11 0x10f296661 in QTest::TestMethods::invokeTestOnData(int) const qtestcase.cpp:966
    #12 0x10f299b96 in QTest::TestMethods::invokeTest(int, char const*, QTest::WatchDog*) const qtestcase.cpp:1210
    #13 0x10f2a002a in QTest::TestMethods::invokeTests(QObject*) const qtestcase.cpp:1552
    #14 0x10f2a23cf in QTest::qRun() qtestcase.cpp:2018
    #15 0x10f2a1047 in QTest::qExec(QObject*, int, char**) qtestcase.cpp:1920
    #16 0x10e3ab836 in main tst_app.cpp:7095
    #17 0x11139751d in start+0x1cd (dyld:x86_64+0x551d)

Thread T5 created by T0 here:
    #0 0x11090771c in wrap_pthread_create+0x5c (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4271c)
    #1 0x11df1e41f in QThread::start(QThread::Priority) qthread_unix.cpp:744
    #2 0x11b3a7030 in QSGThreadedRenderLoop::handleExposure(QQuickWindow*) qsgthreadedrenderloop.cpp:1319
    #3 0x11b3a5215 in QSGThreadedRenderLoop::exposureChanged(QQuickWindow*) qsgthreadedrenderloop.cpp:1244
    #4 0x11a781ffc in QQuickWindow::exposeEvent(QExposeEvent*) qquickwindow.cpp:214
    #5 0x12835b785 in QWindow::event(QEvent*) qwindow.cpp:2501
    #6 0x11a792d84 in QQuickWindow::event(QEvent*) qquickwindow.cpp:1552
    #7 0x11426e051 in QApplicationPrivate::notify_helper(QObject*, QEvent*) qapplication.cpp:3340
    #8 0x11427b41e in QApplication::notify(QObject*, QEvent*) qapplication.cpp:3291
    #9 0x11d5b2ac7 in QCoreApplication::notifyInternal2(QObject*, QEvent*) qcoreapplication.cpp:1067
    #10 0x11d5b5843 in QCoreApplication::sendSpontaneousEvent(QObject*, QEvent*) qcoreapplication.cpp:1497
    #11 0x128170b9f in QGuiApplicationPrivate::processExposeEvent(QWindowSystemInterfacePrivate::ExposeEvent*) qguiapplication.cpp:3174
    #12 0x128162f0b in QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*) qguiapplication.cpp:2078
    #13 0x1283af9c1 in bool QWindowSystemHelper<QWindowSystemInterface::SynchronousDelivery>::handleEvent<QWindowSystemInterfacePrivate::ExposeEvent, QWindow*, QRegion>(QWindow*, QRegion) qwindowsysteminterface.cpp:134
    #14 0x128394c88 in bool handleWindowSystemEvent<QWindowSystemInterfacePrivate::ExposeEvent, QWindowSystemInterface::SynchronousDelivery, QWindow*, QRegion>(QWindow*, QRegion) qwindowsysteminterface.cpp:166
    #15 0x128394b1c in bool QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(QWindow*, QRegion const&) qwindowsysteminterface.cpp:359
    #16 0x1128c0c8e in QCocoaWindow::handleExposeEvent(QRegion const&) qcocoawindow.mm:1444
    #17 0x1128f2ddd in -[QNSView(Drawing) displayLayer:] qnsview_drawing.mm:243
    #18 0x7ff811ce4950 in CA::Layer::display_if_needed(CA::Transaction*)+0x368 (QuartzCore:x86_64+0x20950)
    #19 0x7ff811e3b335 in CA::Context::commit_transaction(CA::Transaction*, double, double*)+0x27f (QuartzCore:x86_64+0x177335)
    #20 0x7ff811cc6230 in CA::Transaction::commit()+0x308 (QuartzCore:x86_64+0x2230)
    #21 0x7ff80d7c87f0 in __62+[CATransaction(NSCATransaction) NS_setFlushesWithDisplayLink]_block_invoke+0x11c (AppKit:x86_64+0x1aa7f0)
    #22 0x7ff80df0f687 in ___NSRunLoopObserverCreateWithHandler_block_invoke+0x28 (AppKit:x86_64+0x8f1687)
    #23 0x7ff80ac36e8f in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__+0x16 (CoreFoundation:x86_64h+0x7ee8f)
    #24 0x7ff80ac36d21 in __CFRunLoopDoObservers+0x21e (CoreFoundation:x86_64h+0x7ed21)
    #25 0x7ff80ac361b3 in __CFRunLoopRun+0x347 (CoreFoundation:x86_64h+0x7e1b3)
    #26 0x7ff80ac357ab in CFRunLoopRunSpecific+0x231 (CoreFoundation:x86_64h+0x7d7ab)
    #27 0x7ff8138bcce5 in RunCurrentEventLoopInMode+0x123 (HIToolbox:x86_64+0x2fce5)
    #28 0x7ff8138bc912 in ReceiveNextEventCommon+0x11a (HIToolbox:x86_64+0x2f912)
    #29 0x7ff8138bc7e4 in _BlockUntilNextEventMatchingListInModeWithFilter+0x45 (HIToolbox:x86_64+0x2f7e4)
    #30 0x7ff80d65c5cc in _DPSNextEvent+0x39e (AppKit:x86_64+0x3e5cc)
    #31 0x7ff80d65ac89 in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:]+0x571 (AppKit:x86_64+0x3cc89)
    #32 0x7ff80d64d338 in -[NSApplication run]+0x249 (AppKit:x86_64+0x2f338)
    #33 0x11284b68d in QCocoaEventDispatcherPrivate::ensureNSAppInitialized() qcocoaeventdispatcher.mm:608
    #34 0x11284a486 in QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) qcocoaeventdispatcher.mm:438
    #35 0x11d5b3eed in QCoreApplication::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) qcoreapplication.cpp:1296
    #36 0x12832fdf1 in bool QTest::qWaitFor<QTest::qWaitForWindowExposed(QWindow*, int)::$_1>(QTest::qWaitForWindowExposed(QWindow*, int)::$_1, int) qtestsupport_core.h:75
    #37 0x12832f522 in QTest::qWaitForWindowExposed(QWindow*, int) qtestsupport_gui.cpp:93
    #38 0x10e4c3a7a in TestHelper::initTestCase() testhelper.cpp:64
    #39 0x10e452a50 in TestHelper::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) moc_testhelper.cpp:74
    #40 0x11d61f5ea in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const qmetaobject.cpp:2390
    #41 0x10f298aed in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const qmetaobject.h:126
    #42 0x10f29fdca in QTest::TestMethods::invokeTests(QObject*) const qtestcase.cpp:1539
    #43 0x10f2a23cf in QTest::qRun() qtestcase.cpp:2018
    #44 0x10f2a1047 in QTest::qExec(QObject*, int, char**) qtestcase.cpp:1920
    #45 0x10e3ab836 in main tst_app.cpp:7095
    #46 0x11139751d in start+0x1cd (dyld:x86_64+0x551d)
  • Loading branch information
mitchcurtis committed Mar 21, 2022
1 parent 5acdafb commit 31c2699
Showing 1 changed file with 12 additions and 2 deletions.
14 changes: 12 additions & 2 deletions tests/shared/testhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,20 @@ class ImageGrabber
{
public:
bool requestImage(QQuickItem *item) {
ready = false;
if (connection)
QObject::disconnect(connection);

result = item->grabToImage();
return !result.isNull();
if (result.isNull())
return false;

connection = QObject::connect(result.data(), &QQuickItemGrabResult::ready, [this](){ ready = true; });
return true;
}

bool isReady() const {
return result && !result->image().isNull();
return ready;
}

QImage takeImage() {
Expand All @@ -80,6 +88,8 @@ class ImageGrabber
}

QSharedPointer<QQuickItemGrabResult> result;
QMetaObject::Connection connection;
bool ready = false;
};

class TestHelper : public QObject
Expand Down

0 comments on commit 31c2699

Please sign in to comment.