From 443b9e4d3706f7cd6aa15fb66a5d474b9303b571 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 27 Sep 2020 10:29:46 -0400 Subject: [PATCH] Introduce AsyncTask::runThenCallback * Fixes #5295 * Introduce a callback approach to AsyncTask instead of entering multiple QEventLoop in the stack. This has additional benefits including not returning to the original stack location and potentially operating on deleted objects (use after free). --- src/core/AsyncTask.h | 22 ++++++++++++++++++++++ src/core/FileWatcher.cpp | 15 +++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/core/AsyncTask.h b/src/core/AsyncTask.h index f74d7c738..b113134d9 100644 --- a/src/core/AsyncTask.h +++ b/src/core/AsyncTask.h @@ -58,6 +58,28 @@ namespace AsyncTask return waitForFuture(QtConcurrent::run(task)); } + /** + * Run a given task then call the defined callback. Prevents event loop blocking and + * ensures the validity of the follow-on task through the context. If the context is + * deleted, the callback will not be processed preventing use after free errors. + * + * @param task std::function object to run + * @param context QObject responsible for calling this function + * @param callback std::function object to run after the task completess + */ + template + void runThenCallback(FunctionObject task, QObject* context, FunctionObject2 callback) + { + typedef QFutureWatcher::type> FutureWatcher; + auto future = QtConcurrent::run(task); + auto watcher = new FutureWatcher(context); + QObject::connect(watcher, &QFutureWatcherBase::finished, context, [=]() { + watcher->deleteLater(); + callback(future.result()); + }); + watcher->setFuture(future); + } + }; // namespace AsyncTask #endif // KEEPASSXC_ASYNCTASK_HPP diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp index 2d37734aa..430e60274 100644 --- a/src/core/FileWatcher.cpp +++ b/src/core/FileWatcher.cpp @@ -118,13 +118,16 @@ void FileWatcher::checkFileChanged() // Prevent reentrance m_ignoreFileChange = true; - auto checksum = AsyncTask::runAndWaitForFuture([this]() -> QByteArray { return calculateChecksum(); }); - if (checksum != m_fileChecksum) { - m_fileChecksum = checksum; - m_fileChangeDelayTimer.start(0); - } + AsyncTask::runThenCallback([=] { return calculateChecksum(); }, + this, + [=](QByteArray checksum) { + if (checksum != m_fileChecksum) { + m_fileChecksum = checksum; + m_fileChangeDelayTimer.start(0); + } - m_ignoreFileChange = false; + m_ignoreFileChange = false; + }); } QByteArray FileWatcher::calculateChecksum()