aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickpixmapcache.cpp
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2024-09-17 09:11:52 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2024-10-03 21:46:02 +0200
commitc28fa0f768d512ca52dfd44249a19e99f55427db (patch)
tree1301eb0fb35df7fd417fc5a49734db97badf323a /src/quick/util/qquickpixmapcache.cpp
parentde22477708ed3700d2f36d0121008b1cac4b837e (diff)
Use QPointer for QQuickPixmapData::specialDevice; abort if deleted
QQuickPdfPageImage::load() calls carrierFile() to get a QPdfFile instance, a subclass of QIODevice, and calls QQuickPixmap::loadImageFromDevice() which saves the device to QQuickPixmapData::specialDevice, then calls QQuickPixmapReader::startJob() which sends a ProcessJobs event. The worker thread handles it in QQuickPixmapReader::processJob() and calls readImage(), passing a simple QIODevice *dev. readImage() relies on that pointer being valid until the work is done. However, QQuickPdfDocument::setSource() may have called deleteLater(). We can detect it, and in that case there is no point in continuing this render job. Also, change QPdfFile's thread affinity to the renderer thread, so that deletion cannot happen until after readImage() is done and the renderer thread has returned to its event loop. Fixes: QTBUG-128875 Pick-to: 6.8 6.7 6.5 Change-Id: I1e8bf526c5596ce561d7bf4f7dea8d50f3c0eb18 Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
Diffstat (limited to 'src/quick/util/qquickpixmapcache.cpp')
-rw-r--r--src/quick/util/qquickpixmapcache.cpp29
1 files changed, 22 insertions, 7 deletions
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
index 0bd7476465..5d7ff6efdd 100644
--- a/src/quick/util/qquickpixmapcache.cpp
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -303,7 +303,7 @@ class QQuickPixmapData
public:
QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &rs,
const QQuickImageProviderOptions &po, const QString &e)
- : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
+ : refCount(1), frameCount(1), frame(0), inCache(false), fromSpecialDevice(false), pixmapStatus(QQuickPixmap::Error),
url(u), errorString(e), requestRegion(r), requestSize(rs),
providerOptions(po), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr),
@@ -316,7 +316,7 @@ public:
QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
- : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
+ : refCount(1), frameCount(frameCount), frame(frame), inCache(false), fromSpecialDevice(false), pixmapStatus(QQuickPixmap::Loading),
url(u), requestRegion(r), requestSize(s),
providerOptions(po), appliedTransform(aTransform),
textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr),
@@ -330,7 +330,7 @@ public:
QQuickPixmapData(const QUrl &u, QQuickTextureFactory *texture,
const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
- : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
+ : refCount(1), frameCount(frameCount), frame(frame), inCache(false), fromSpecialDevice(false), pixmapStatus(QQuickPixmap::Ready),
url(u), implicitSize(s), requestRegion(r), requestSize(rs),
providerOptions(po), appliedTransform(aTransform),
textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
@@ -342,7 +342,7 @@ public:
}
QQuickPixmapData(QQuickTextureFactory *texture)
- : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready),
+ : refCount(1), frameCount(1), frame(0), inCache(false), fromSpecialDevice(false), pixmapStatus(QQuickPixmap::Ready),
appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
@@ -370,6 +370,7 @@ public:
int frame;
bool inCache:1;
+ bool fromSpecialDevice:1;
QQuickPixmap::Status pixmapStatus;
QUrl url;
@@ -381,7 +382,7 @@ public:
QQuickImageProviderOptions::AutoTransform appliedTransform;
QColorSpace targetColorSpace;
- QIODevice *specialDevice = nullptr;
+ QPointer<QIODevice> specialDevice;
// actual image data, after loading
QQuickTextureFactory *textureFactory;
@@ -1005,9 +1006,22 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
QString errorStr;
QSize readSize;
- if (runningJob->data && runningJob->data->specialDevice) {
+ if (runningJob->data && runningJob->data->fromSpecialDevice) {
+ auto specialDevice = runningJob->data->specialDevice;
+ if (specialDevice.isNull() || QObjectPrivate::get(specialDevice.data())->deleteLaterCalled) {
+ qCDebug(lcImg) << "readImage job aborted" << url;
+ return;
+ }
int frameCount;
- if (!readImage(url, runningJob->data->specialDevice, &image, &errorStr, &readSize, &frameCount,
+ // Ensure that specialDevice's thread affinity is _this_ thread, to avoid deleteLater()
+ // deleting prematurely, before readImage() is done. But this is only possible if it has already
+ // relinquished its initial thread affinity.
+ if (!specialDevice->thread()) {
+ qCDebug(lcQsgLeak) << specialDevice.data() << ": changing thread affinity so that"
+ << QThread::currentThread() << "will handle any deleteLater() calls";
+ specialDevice->moveToThread(QThread::currentThread());
+ }
+ if (!readImage(url, specialDevice.data(), &image, &errorStr, &readSize, &frameCount,
runningJob->requestRegion, runningJob->requestSize,
runningJob->providerOptions, nullptr, runningJob->data->frame)) {
errorCode = QQuickPixmapReply::Loading;
@@ -1958,6 +1972,7 @@ void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, co
d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
d->specialDevice = device;
+ d->fromSpecialDevice = true;
d->addToCache();
QQuickPixmapReader::readerMutex.lock();