diff options
author | Christian Heimlich <chris@pcserenity.com> | 2020-09-19 05:53:26 -0400 |
---|---|---|
committer | Christian Heimlich <chris@pcserenity.com> | 2020-09-21 03:19:55 -0400 |
commit | 10bc093d4fa1889b7a5f6151c9bcc79aa31ecfc5 (patch) | |
tree | 245187c0b9ad23103172e92b9ac53b5523d496c4 | |
parent | 169f332eab5dc047112e9db11556d0757a86b400 (diff) |
Add reentrancy guard for processEvents() in QProgressDialog::setValue()
Current implementation of QProgressDialog always calls
QCoreApplication::processEvents() when the user calls
QProgressDialog::setValue() if the PD is modal. For most cases this is
fine, but when using a Qt::WindowModal PD with setValue() connected to
a signal in another thread using Qt::QueuedConnection a reentrancy
issue is present if setValue() is triggered too frequently as the
execution of its previous call may not have finished. If this happens
too many times in a row a stack overflow will occur.
Current documentation notes this potential issue but offers no way it
avoid it while still using QProgressDialog (user must implement a
custom dialog) without resorting to using Qt::BlockingQueuedConnection,
which unnecessarily reduces performance.
Introduces the boolean reentrancy guard "processingEvents" that is
checked before calling QCoreApplication::processEvents() in a modal
PD when setValue() is used. It is set before the first call to
processEvents() and cleared after that call returns. This ensures that
only one invocation of processEvents() is possible from within
setValue() at a time, and thereby minimizes iterations of the main event
loop and eliminates the aforementioned stack overflow condition.
See - https://forum.qt.io/topic/118292/
Fixes: QTBUG-10561
Pick-to: 5.15
Change-Id: Ifa9b91cbb66881981356954ead0906bdc91fab60
Reviewed-by: Samuel Gaist <samuel.gaist@idiap.ch>
-rw-r--r-- | src/widgets/dialogs/qprogressdialog.cpp | 7 | ||||
-rw-r--r-- | tests/auto/widgets/dialogs/qprogressdialog/tst_qprogressdialog.cpp | 24 |
2 files changed, 30 insertions, 1 deletions
diff --git a/src/widgets/dialogs/qprogressdialog.cpp b/src/widgets/dialogs/qprogressdialog.cpp index aaa4430c39..485e5cae49 100644 --- a/src/widgets/dialogs/qprogressdialog.cpp +++ b/src/widgets/dialogs/qprogressdialog.cpp @@ -51,6 +51,7 @@ #include "qpushbutton.h" #include "qtimer.h" #include "qelapsedtimer.h" +#include "qscopedvaluerollback.h" #include <private/qdialog_p.h> #include <limits.h> @@ -71,6 +72,7 @@ public: shown_once(false), cancellation_flag(false), setValue_called(false), + processingEvents(false), showTime(defaultShowTime), #ifndef QT_NO_SHORTCUT escapeShortcut(nullptr), @@ -94,6 +96,7 @@ public: bool shown_once; bool cancellation_flag; bool setValue_called; + bool processingEvents; QElapsedTimer starttime; int showTime; bool autoClose; @@ -660,8 +663,10 @@ void QProgressDialog::setValue(int progress) d->bar->setValue(progress); if (d->shown_once) { - if (isModal()) + if (isModal() && !d->processingEvents) { + const QScopedValueRollback guard(d->processingEvents, true); QCoreApplication::processEvents(); + } } else { if ((!d->setValue_called && progress == 0 /* for compat with Qt < 5.4 */) || progress == minimum()) { d->starttime.start(); diff --git a/tests/auto/widgets/dialogs/qprogressdialog/tst_qprogressdialog.cpp b/tests/auto/widgets/dialogs/qprogressdialog/tst_qprogressdialog.cpp index 6f527e7b6b..2149ee7c44 100644 --- a/tests/auto/widgets/dialogs/qprogressdialog/tst_qprogressdialog.cpp +++ b/tests/auto/widgets/dialogs/qprogressdialog/tst_qprogressdialog.cpp @@ -53,6 +53,7 @@ private Q_SLOTS: void QTBUG_31046(); void settingCustomWidgets(); void i18n(); + void setValueReentrancyGuard(); }; void tst_QProgressDialog::cleanup() @@ -291,5 +292,28 @@ void tst_QProgressDialog::i18n() QVERIFY(!btn->text().startsWith(xxx)); } +void tst_QProgressDialog::setValueReentrancyGuard() +{ + // Tests setValue() of window modal QProgressBar with + // Qt::QueuedConnection: + // This test crashes with a stack overflow if the boolean + // guard "processingEvents" that prevents reentranct calls + // to QCoreApplication::processEvents() within setValue() + // has not been implemented + + constexpr int steps = 100; // Should be at least 50 to test for crash + + QProgressDialog dlg("Testing setValue reentrancy guard...", QString(), 0, steps); + dlg.setWindowModality(Qt::WindowModal); + dlg.setMinimumDuration(0); + dlg.setAutoReset(false); + + // Simulate a quick work loop + for (int i = 0; i <= steps; ++i) + QMetaObject::invokeMethod(&dlg, "setValue", Qt::QueuedConnection, Q_ARG(int, i)); + + QTRY_COMPARE(dlg.value(), steps); +} + QTEST_MAIN(tst_QProgressDialog) #include "tst_qprogressdialog.moc" |