summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKonstantin Shegunov <kshegunov@gmail.com>2018-08-01 00:50:14 +0300
committerKonstantin Shegunov <kshegunov@gmail.com>2019-05-08 17:19:44 +0000
commitc6bee8e4b2c48775247c67ef8e7569f623655c61 (patch)
treea3bfc28da2b37935129ebf16425de58336e3a0e6
parent8adcfa8b240876236f08ad826e6d77ff1f5e9a96 (diff)
Fix integer overflows in QDeadlineTimer
If the deadline is far in the future, the conversions to nanoseconds or internal arithmetic may overflow and give an invalid object, thus the deadline may end up in the past. Added a test to the testlib selftest for sleep. [ChangeLog][QtCore][QDeadlineTimer] Fixed integer overflows leading to immediate timeouts. Task-number: QTBUG-69750 Change-Id: I9814eccdf9f9b3add9ca66ec3e27e10cd5ad54a8 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/kernel/qdeadlinetimer.cpp440
-rw-r--r--src/corelib/kernel/qdeadlinetimer.h3
-rw-r--r--tests/auto/corelib/kernel/qdeadlinetimer/tst_qdeadlinetimer.cpp79
-rw-r--r--tests/auto/testlib/selftests/expected_sleep.lightxml4
-rw-r--r--tests/auto/testlib/selftests/expected_sleep.tap9
-rw-r--r--tests/auto/testlib/selftests/expected_sleep.teamcity2
-rw-r--r--tests/auto/testlib/selftests/expected_sleep.txt3
-rw-r--r--tests/auto/testlib/selftests/expected_sleep.xml4
-rw-r--r--tests/auto/testlib/selftests/expected_sleep.xunitxml3
-rw-r--r--tests/auto/testlib/selftests/sleep/tst_sleep.cpp19
10 files changed, 500 insertions, 66 deletions
diff --git a/src/corelib/kernel/qdeadlinetimer.cpp b/src/corelib/kernel/qdeadlinetimer.cpp
index 6aa886cfe1..49874b94b4 100644
--- a/src/corelib/kernel/qdeadlinetimer.cpp
+++ b/src/corelib/kernel/qdeadlinetimer.cpp
@@ -39,18 +39,294 @@
#include "qdeadlinetimer.h"
#include "qdeadlinetimer_p.h"
+#include "private/qnumeric_p.h"
QT_BEGIN_NAMESPACE
-Q_DECL_CONST_FUNCTION static inline QPair<qint64, qint64> toSecsAndNSecs(qint64 nsecs)
+namespace {
+ class TimeReference
+ {
+ enum : unsigned {
+ umega = 1000 * 1000,
+ ugiga = umega * 1000
+ };
+
+ enum : qint64 {
+ kilo = 1000,
+ mega = kilo * 1000,
+ giga = mega * 1000
+ };
+
+ public:
+ enum RoundingStrategy {
+ RoundDown,
+ RoundUp,
+ RoundDefault = RoundDown
+ };
+
+ static constexpr qint64 Min = std::numeric_limits<qint64>::min();
+ static constexpr qint64 Max = std::numeric_limits<qint64>::max();
+
+ inline TimeReference(qint64 = 0, unsigned = 0);
+ inline void updateTimer(qint64 &, unsigned &);
+
+ inline bool addNanoseconds(qint64);
+ inline bool addMilliseconds(qint64);
+ bool addSecsAndNSecs(qint64, qint64);
+
+ inline bool subtract(const qint64, const unsigned);
+
+ inline bool toMilliseconds(qint64 *, RoundingStrategy = RoundDefault) const;
+ inline bool toNanoseconds(qint64 *) const;
+
+ inline void saturate(bool toMax);
+ static bool sign(qint64, qint64);
+
+ private:
+ bool adjust(const qint64, const unsigned, qint64 = 0);
+
+ private:
+ qint64 secs;
+ unsigned nsecs;
+ };
+}
+
+inline TimeReference::TimeReference(qint64 t1, unsigned t2)
+ : secs(t1), nsecs(t2)
+{
+}
+
+inline void TimeReference::updateTimer(qint64 &t1, unsigned &t2)
+{
+ t1 = secs;
+ t2 = nsecs;
+}
+
+inline void TimeReference::saturate(bool toMax)
+{
+ secs = toMax ? Max : Min;
+}
+
+/*!
+ * \internal
+ *
+ * Determines the sign of a (seconds, nanoseconds) pair
+ * for differentiating overflow from underflow. It doesn't
+ * deal with equality as it shouldn't ever be called in that case.
+ *
+ * Returns true if the pair represents a positive time offset
+ * false otherwise.
+ */
+bool TimeReference::sign(qint64 secs, qint64 nsecs)
+{
+ if (secs > 0) {
+ if (nsecs > 0)
+ return true;
+ } else {
+ if (nsecs < 0)
+ return false;
+ }
+
+ // They are different in sign
+ secs += nsecs / giga;
+ if (secs > 0)
+ return true;
+ else if (secs < 0)
+ return false;
+
+ // We should never get over|underflow out of
+ // the case: secs * giga == -nsecs
+ // So the sign of nsecs is the deciding factor
+ Q_ASSERT(nsecs % giga != 0);
+ return nsecs > 0;
+}
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
+inline bool TimeReference::addNanoseconds(qint64 arg)
+{
+ return addSecsAndNSecs(arg / giga, arg % giga);
+}
+
+inline bool TimeReference::addMilliseconds(qint64 arg)
+{
+ return addSecsAndNSecs(arg / kilo, (arg % kilo) * mega);
+}
+
+/*!
+ * \internal
+ *
+ * Adds \a t1 addSecs seconds and \a addNSecs nanoseconds to the
+ * time reference. The arguments are normalized to seconds (qint64)
+ * and nanoseconds (unsigned) before the actual calculation is
+ * delegated to adjust(). If the nanoseconds are negative the
+ * owed second used for the normalization is passed on to adjust()
+ * as third argument.
+ *
+ * Returns true if operation was successful, false on over|underflow
+ */
+bool TimeReference::addSecsAndNSecs(qint64 addSecs, qint64 addNSecs)
+{
+ // Normalize the arguments
+ if (qAbs(addNSecs) >= giga) {
+ if (add_overflow<qint64>(addSecs, addNSecs / giga, &addSecs))
+ return false;
+
+ addNSecs %= giga;
+ }
+
+ if (addNSecs < 0)
+ return adjust(addSecs, ugiga - unsigned(-addNSecs), -1);
+
+ return adjust(addSecs, unsigned(addNSecs));
+}
+
+/*!
+ * \internal
+ *
+ * Adds \a t1 seconds and \a t2 nanoseconds to the internal members.
+ * Takes into account the additional \a carrySeconds we may owe or need to carry over.
+ *
+ * Returns true if operation was successful, false on over|underflow
+ */
+bool TimeReference::adjust(const qint64 t1, const unsigned t2, qint64 carrySeconds)
+{
+ Q_STATIC_ASSERT(QDeadlineTimerNanosecondsInT2);
+ nsecs += t2;
+ if (nsecs >= ugiga) {
+ nsecs -= ugiga;
+ carrySeconds++;
+ }
+
+ // We don't worry about the order of addition, because the result returned by
+ // callers of this function is unchanged regardless of us over|underflowing.
+ // If we do, we do so by no more than a second, thus saturating the timer to
+ // Forever has the same effect as if we did the arithmetic exactly and salvaged
+ // the overflow.
+ return !add_overflow<qint64>(secs, t1, &secs) && !add_overflow<qint64>(secs, carrySeconds, &secs);
+}
+
+/*!
+ * \internal
+ *
+ * Subtracts \a t1 seconds and \a t2 nanoseconds from the time reference.
+ * When normalizing the nanoseconds to a positive number the owed seconds is
+ * passed as third argument to adjust() as the seconds may over|underflow
+ * if we do the calculation directly. There is little sense to check the
+ * seconds for over|underflow here in case we are going to need to carry
+ * over a second _after_ we add the nanoseconds.
+ *
+ * Returns true if operation was successful, false on over|underflow
+ */
+inline bool TimeReference::subtract(const qint64 t1, const unsigned t2)
+{
+ Q_ASSERT(t2 < ugiga);
+ return adjust(-t1, ugiga - t2, -1);
+}
+
+/*!
+ * \internal
+ *
+ * Converts the time reference to milliseconds.
+ *
+ * Checks are done without making use of mul_overflow because it may
+ * not be implemented on some 32bit platforms.
+ *
+ * Returns true if operation was successful, false on over|underflow
+ */
+inline bool TimeReference::toMilliseconds(qint64 *result, RoundingStrategy rounding) const
+{
+ static constexpr qint64 maxSeconds = Max / kilo;
+ static constexpr qint64 minSeconds = Min / kilo;
+ if (secs > maxSeconds || secs < minSeconds)
+ return false;
+
+ unsigned ns = rounding == RoundDown ? nsecs : nsecs + umega - 1;
+
+ return !add_overflow<qint64>(secs * kilo, ns / umega, result);
+}
+
+/*!
+ * \internal
+ *
+ * Converts the time reference to nanoseconds.
+ *
+ * Checks are done without making use of mul_overflow because it may
+ * not be implemented on some 32bit platforms.
+ *
+ * Returns true if operation was successful, false on over|underflow
+ */
+inline bool TimeReference::toNanoseconds(qint64 *result) const
+{
+ static constexpr qint64 maxSeconds = Max / giga;
+ static constexpr qint64 minSeconds = Min / giga;
+ if (secs > maxSeconds || secs < minSeconds)
+ return false;
+
+ return !add_overflow<qint64>(secs * giga, nsecs, result);
+}
+#else
+inline bool TimeReference::addNanoseconds(qint64 arg)
+{
+ return adjust(arg, 0);
+}
+
+inline bool TimeReference::addMilliseconds(qint64 arg)
+{
+ static constexpr qint64 maxMilliseconds = Max / mega;
+ if (qAbs(arg) > maxMilliseconds)
+ return false;
+
+ return addNanoseconds(arg * mega);
+}
+
+inline bool TimeReference::addSecsAndNSecs(qint64 addSecs, qint64 addNSecs)
{
- qint64 secs = nsecs / (1000*1000*1000);
- if (nsecs < 0)
- --secs;
- nsecs -= secs * 1000*1000*1000;
- return qMakePair(secs, nsecs);
+ static constexpr qint64 maxSeconds = Max / giga;
+ static constexpr qint64 minSeconds = Min / giga;
+ if (addSecs > maxSeconds || addSecs < minSeconds || add_overflow<qint64>(addSecs * giga, addNSecs, &addNSecs))
+ return false;
+
+ return addNanoseconds(addNSecs);
}
+inline bool TimeReference::adjust(const qint64 t1, const unsigned t2, qint64 carrySeconds)
+{
+ Q_STATIC_ASSERT(!QDeadlineTimerNanosecondsInT2);
+ Q_UNUSED(t2);
+ Q_UNUSED(carrySeconds);
+
+ return !add_overflow<qint64>(secs, t1, &secs);
+}
+
+inline bool TimeReference::subtract(const qint64 t1, const unsigned t2)
+{
+ Q_UNUSED(t2);
+
+ return addNanoseconds(-t1);
+}
+
+inline bool TimeReference::toMilliseconds(qint64 *result, RoundingStrategy rounding) const
+{
+ // Force QDeadlineTimer to treat the border cases as
+ // over|underflow and saturate the results returned to the user.
+ // We don't want to get valid milliseconds out of saturated timers.
+ if (secs == Max || secs == Min)
+ return false;
+
+ *result = secs / mega;
+ if (rounding == RoundUp && secs > *result * mega)
+ (*result)++;
+
+ return true;
+}
+
+inline bool TimeReference::toNanoseconds(qint64 *result) const
+{
+ *result = secs;
+ return true;
+}
+#endif
+
/*!
\class QDeadlineTimer
\inmodule QtCore
@@ -262,10 +538,17 @@ QDeadlineTimer::QDeadlineTimer(qint64 msecs, Qt::TimerType type) Q_DECL_NOTHROW
*/
void QDeadlineTimer::setRemainingTime(qint64 msecs, Qt::TimerType timerType) Q_DECL_NOTHROW
{
- if (msecs == -1)
+ if (msecs == -1) {
*this = QDeadlineTimer(Forever, timerType);
- else
- setPreciseRemainingTime(0, msecs * 1000 * 1000, timerType);
+ return;
+ }
+
+ *this = current(timerType);
+
+ TimeReference ref(t1, t2);
+ if (!ref.addMilliseconds(msecs))
+ ref.saturate(msecs > 0);
+ ref.updateTimer(t1, t2);
}
/*!
@@ -287,16 +570,10 @@ void QDeadlineTimer::setPreciseRemainingTime(qint64 secs, qint64 nsecs, Qt::Time
}
*this = current(timerType);
- if (QDeadlineTimerNanosecondsInT2) {
- t1 += secs + toSecsAndNSecs(nsecs).first;
- t2 += toSecsAndNSecs(nsecs).second;
- if (t2 > 1000*1000*1000) {
- t2 -= 1000*1000*1000;
- ++t1;
- }
- } else {
- t1 += secs * 1000 * 1000 * 1000 + nsecs;
- }
+ TimeReference ref(t1, t2);
+ if (!ref.addSecsAndNSecs(secs, nsecs))
+ ref.saturate(TimeReference::sign(secs, nsecs));
+ ref.updateTimer(t1, t2);
}
/*!
@@ -391,8 +668,22 @@ void QDeadlineTimer::setTimerType(Qt::TimerType timerType)
*/
qint64 QDeadlineTimer::remainingTime() const Q_DECL_NOTHROW
{
- qint64 ns = remainingTimeNSecs();
- return ns <= 0 ? ns : (ns + 999999) / (1000 * 1000);
+ if (isForever())
+ return -1;
+
+ QDeadlineTimer now = current(timerType());
+ TimeReference ref(t1, t2);
+
+ qint64 msecs;
+ if (!ref.subtract(now.t1, now.t2))
+ return 0; // We can only underflow here
+
+ // If we fail the conversion, t1 < now.t1 means we underflowed,
+ // thus the deadline had long expired
+ if (!ref.toMilliseconds(&msecs, TimeReference::RoundUp))
+ return t1 < now.t1 ? 0 : -1;
+
+ return msecs < 0 ? 0 : msecs;
}
/*!
@@ -414,14 +705,23 @@ qint64 QDeadlineTimer::remainingTimeNSecs() const Q_DECL_NOTHROW
/*!
\internal
Same as remainingTimeNSecs, but may return negative remaining times. Does
- not deal with Forever.
+ not deal with Forever. In case of underflow the result is saturated to
+ the minimum possible value, on overflow - the maximum possible value.
*/
qint64 QDeadlineTimer::rawRemainingTimeNSecs() const Q_DECL_NOTHROW
{
QDeadlineTimer now = current(timerType());
- if (QDeadlineTimerNanosecondsInT2)
- return (t1 - now.t1) * (1000*1000*1000) + t2 - now.t2;
- return t1 - now.t1;
+ TimeReference ref(t1, t2);
+
+ qint64 nsecs;
+ if (!ref.subtract(now.t1, now.t2))
+ return TimeReference::Min; // We can only underflow here
+
+ // If we fail the conversion, t1 < now.t1 means we underflowed,
+ // thus the deadline had long expired
+ if (!ref.toNanoseconds(&nsecs))
+ return t1 < now.t1 ? TimeReference::Min : TimeReference::Max;
+ return nsecs;
}
/*!
@@ -447,8 +747,13 @@ qint64 QDeadlineTimer::rawRemainingTimeNSecs() const Q_DECL_NOTHROW
qint64 QDeadlineTimer::deadline() const Q_DECL_NOTHROW
{
if (isForever())
- return t1;
- return deadlineNSecs() / (1000 * 1000);
+ return TimeReference::Max;
+
+ qint64 result;
+ if (!TimeReference(t1, t2).toMilliseconds(&result))
+ return t1 < 0 ? TimeReference::Min : TimeReference::Max;
+
+ return result;
}
/*!
@@ -457,7 +762,8 @@ qint64 QDeadlineTimer::deadline() const Q_DECL_NOTHROW
same as QElapsedTimer::msecsSinceReference(). The value will be in the past
if this QDeadlineTimer has expired.
- If this QDeadlineTimer never expires, this function returns
+ If this QDeadlineTimer never expires or the number of nanoseconds until the
+ deadline can't be accommodated in the return type, this function returns
\c{std::numeric_limits<qint64>::max()}.
This function can be used to calculate the amount of time a timer is
@@ -474,10 +780,13 @@ qint64 QDeadlineTimer::deadline() const Q_DECL_NOTHROW
qint64 QDeadlineTimer::deadlineNSecs() const Q_DECL_NOTHROW
{
if (isForever())
- return t1;
- if (QDeadlineTimerNanosecondsInT2)
- return t1 * 1000 * 1000 * 1000 + t2;
- return t1;
+ return TimeReference::Max;
+
+ qint64 result;
+ if (!TimeReference(t1, t2).toNanoseconds(&result))
+ return t1 < 0 ? TimeReference::Min : TimeReference::Max;
+
+ return result;
}
/*!
@@ -487,18 +796,25 @@ qint64 QDeadlineTimer::deadlineNSecs() const Q_DECL_NOTHROW
timerType. If the value is in the past, this QDeadlineTimer will be marked
as expired.
- If \a msecs is \c{std::numeric_limits<qint64>::max()}, this QDeadlineTimer
- will be set to never expire.
+ If \a msecs is \c{std::numeric_limits<qint64>::max()} or the deadline is
+ beyond a representable point in the future, this QDeadlineTimer will be set
+ to never expire.
\sa setPreciseDeadline(), deadline(), deadlineNSecs(), setRemainingTime()
*/
void QDeadlineTimer::setDeadline(qint64 msecs, Qt::TimerType timerType) Q_DECL_NOTHROW
{
- if (msecs == (std::numeric_limits<qint64>::max)()) {
- setPreciseDeadline(msecs, 0, timerType); // msecs == MAX implies Forever
- } else {
- setPreciseDeadline(msecs / 1000, msecs % 1000 * 1000 * 1000, timerType);
+ if (msecs == TimeReference::Max) {
+ *this = QDeadlineTimer(Forever, timerType);
+ return;
}
+
+ type = timerType;
+
+ TimeReference ref;
+ if (!ref.addMilliseconds(msecs))
+ ref.saturate(msecs > 0);
+ ref.updateTimer(t1, t2);
}
/*!
@@ -516,14 +832,13 @@ void QDeadlineTimer::setDeadline(qint64 msecs, Qt::TimerType timerType) Q_DECL_N
void QDeadlineTimer::setPreciseDeadline(qint64 secs, qint64 nsecs, Qt::TimerType timerType) Q_DECL_NOTHROW
{
type = timerType;
- if (secs == (std::numeric_limits<qint64>::max)() || nsecs == (std::numeric_limits<qint64>::max)()) {
- *this = QDeadlineTimer(Forever, timerType);
- } else if (QDeadlineTimerNanosecondsInT2) {
- t1 = secs + toSecsAndNSecs(nsecs).first;
- t2 = toSecsAndNSecs(nsecs).second;
- } else {
- t1 = secs * (1000*1000*1000) + nsecs;
- }
+
+ // We don't pass the seconds to the constructor, because we don't know
+ // at this point if t1 holds the seconds or nanoseconds; it's platform specific.
+ TimeReference ref;
+ if (!ref.addSecsAndNSecs(secs, nsecs))
+ ref.saturate(TimeReference::sign(secs, nsecs));
+ ref.updateTimer(t1, t2);
}
/*!
@@ -536,18 +851,14 @@ void QDeadlineTimer::setPreciseDeadline(qint64 secs, qint64 nsecs, Qt::TimerType
*/
QDeadlineTimer QDeadlineTimer::addNSecs(QDeadlineTimer dt, qint64 nsecs) Q_DECL_NOTHROW
{
- if (dt.isForever() || nsecs == (std::numeric_limits<qint64>::max)()) {
- dt = QDeadlineTimer(Forever, dt.timerType());
- } else if (QDeadlineTimerNanosecondsInT2) {
- dt.t1 += toSecsAndNSecs(nsecs).first;
- dt.t2 += toSecsAndNSecs(nsecs).second;
- if (dt.t2 > 1000*1000*1000) {
- dt.t2 -= 1000*1000*1000;
- ++dt.t1;
- }
- } else {
- dt.t1 += nsecs;
- }
+ if (dt.isForever())
+ return dt;
+
+ TimeReference ref(dt.t1, dt.t2);
+ if (!ref.addNanoseconds(nsecs))
+ ref.saturate(nsecs > 0);
+ ref.updateTimer(dt.t1, dt.t2);
+
return dt;
}
@@ -656,6 +967,19 @@ QDeadlineTimer QDeadlineTimer::addNSecs(QDeadlineTimer dt, qint64 nsecs) Q_DECL_
To add times of precision greater than 1 millisecond, use addNSecs().
*/
+QDeadlineTimer operator+(QDeadlineTimer dt, qint64 msecs)
+{
+ if (dt.isForever())
+ return dt;
+
+ TimeReference ref(dt.t1, dt.t2);
+ if (!ref.addMilliseconds(msecs))
+ ref.saturate(msecs > 0);
+ ref.updateTimer(dt.t1, dt.t2);
+
+ return dt;
+}
+
/*!
\fn QDeadlineTimer operator+(qint64 msecs, QDeadlineTimer dt)
\relates QDeadlineTimer
diff --git a/src/corelib/kernel/qdeadlinetimer.h b/src/corelib/kernel/qdeadlinetimer.h
index 1a4ee04a96..f9ad4063e5 100644
--- a/src/corelib/kernel/qdeadlinetimer.h
+++ b/src/corelib/kernel/qdeadlinetimer.h
@@ -108,8 +108,7 @@ public:
friend bool operator>=(QDeadlineTimer d1, QDeadlineTimer d2) Q_DECL_NOTHROW
{ return !(d1 < d2); }
- friend QDeadlineTimer operator+(QDeadlineTimer dt, qint64 msecs)
- { return QDeadlineTimer::addNSecs(dt, msecs * 1000 * 1000); }
+ friend Q_CORE_EXPORT QDeadlineTimer operator+(QDeadlineTimer dt, qint64 msecs);
friend QDeadlineTimer operator+(qint64 msecs, QDeadlineTimer dt)
{ return dt + msecs; }
friend QDeadlineTimer operator-(QDeadlineTimer dt, qint64 msecs)
diff --git a/tests/auto/corelib/kernel/qdeadlinetimer/tst_qdeadlinetimer.cpp b/tests/auto/corelib/kernel/qdeadlinetimer/tst_qdeadlinetimer.cpp
index 6ab24d2480..4ca68550b9 100644
--- a/tests/auto/corelib/kernel/qdeadlinetimer/tst_qdeadlinetimer.cpp
+++ b/tests/auto/corelib/kernel/qdeadlinetimer/tst_qdeadlinetimer.cpp
@@ -29,6 +29,7 @@
#include <QtCore/QString>
#include <QtCore/QTime>
#include <QtCore/QDeadlineTimer>
+#include <QtCore/QElapsedTimer>
#include <QtTest/QtTest>
#if QT_HAS_INCLUDE(<chrono>)
@@ -50,6 +51,7 @@ private Q_SLOTS:
void current();
void deadlines();
void setDeadline();
+ void overflow();
void expire();
void stdchrono();
};
@@ -417,6 +419,83 @@ void tst_QDeadlineTimer::setDeadline()
QCOMPARE(deadline.deadlineNSecs(), nsec);
}
+void tst_QDeadlineTimer::overflow()
+{
+ QFETCH_GLOBAL(Qt::TimerType, timerType);
+ // Check the constructor for overflows (should also cover saturating the result of the deadline() method if overflowing)
+ QDeadlineTimer now = QDeadlineTimer::current(timerType), deadline(std::numeric_limits<qint64>::max() - 1, timerType);
+ QVERIFY(deadline.isForever() || deadline.deadline() >= now.deadline());
+
+ // Check the setDeadline with milliseconds (should also cover implicitly setting the nanoseconds as qint64 max)
+ deadline.setDeadline(std::numeric_limits<qint64>::max() - 1, timerType);
+ QVERIFY(deadline.isForever() || deadline.deadline() >= now.deadline());
+
+ // Check the setRemainingTime with milliseconds (should also cover implicitly setting the nanoseconds as qint64 max)
+ deadline.setRemainingTime(std::numeric_limits<qint64>::max() - 1, timerType);
+ QVERIFY(deadline.isForever() || deadline.deadline() >= now.deadline());
+
+ // Check that the deadline gets saturated when the arguments of setPreciseDeadline are large
+ deadline.setPreciseDeadline(std::numeric_limits<qint64>::max() - 1, std::numeric_limits<qint64>::max() - 1, timerType);
+ QCOMPARE(deadline.deadline(), std::numeric_limits<qint64>::max());
+ QVERIFY(deadline.isForever());
+
+ // Check that remainingTime gets saturated if we overflow
+ deadline.setPreciseRemainingTime(std::numeric_limits<qint64>::max() - 1, std::numeric_limits<qint64>::max() - 1, timerType);
+ QCOMPARE(deadline.remainingTime(), qint64(-1));
+ QVERIFY(deadline.isForever());
+
+ // Check that we saturate the getter for nanoseconds
+ deadline.setPreciseDeadline(std::numeric_limits<qint64>::max() - 1, 0, timerType);
+ QCOMPARE(deadline.deadlineNSecs(), std::numeric_limits<qint64>::max());
+
+ // Check that adding nanoseconds and overflowing is consistent and saturates the timer
+ deadline = QDeadlineTimer::addNSecs(deadline, std::numeric_limits<qint64>::max() - 1);
+ QVERIFY(deadline.isForever());
+
+ // Make sure forever is forever, regardless of us subtracting time from it
+ deadline = QDeadlineTimer(QDeadlineTimer::Forever, timerType);
+ deadline = QDeadlineTimer::addNSecs(deadline, -10000);
+ QVERIFY(deadline.isForever());
+
+ // Make sure we get the correct result when moving the deadline back and forth in time
+ QDeadlineTimer current = QDeadlineTimer::current(timerType);
+ QDeadlineTimer takenNSecs = QDeadlineTimer::addNSecs(current, -1000);
+ QVERIFY(takenNSecs.deadlineNSecs() - current.deadlineNSecs() == -1000);
+ QDeadlineTimer addedNSecs = QDeadlineTimer::addNSecs(current, 1000);
+ QVERIFY(addedNSecs.deadlineNSecs() - current.deadlineNSecs() == 1000);
+
+ // Make sure the calculation goes as expected when we need to subtract nanoseconds
+ // We make use of an additional timer to be certain that
+ // even when the environment is under load we can track the
+ // time needed to do the calls
+ static constexpr qint64 nsExpected = 1000 * 1000 * 1000 - 1000; // 1s - 1000ns, what we pass to setPreciseRemainingTime() later
+
+ QElapsedTimer callTimer;
+ callTimer.start();
+
+ deadline = QDeadlineTimer::current(timerType);
+ qint64 nsDeadline = deadline.deadlineNSecs();
+ // We adjust in relation to current() here, so we expect the difference to be a tad over the exact number.
+ // However we are tracking the elapsed time, so it shouldn't be a problem.
+ deadline.setPreciseRemainingTime(1, -1000, timerType);
+ qint64 difference = (deadline.deadlineNSecs() - nsDeadline) - nsExpected;
+ QVERIFY(difference >= 0); // Should always be true, but just in case
+ QVERIFY(difference <= callTimer.nsecsElapsed()); // Ideally difference should be 0 exactly
+
+ // Make sure setRemainingTime underflows gracefully
+ deadline.setPreciseRemainingTime(std::numeric_limits<qint64>::min() / 10, 0, timerType);
+ QVERIFY(!deadline.isForever()); // On Win/macOS the above underflows, make sure we don't saturate to Forever
+ QVERIFY(deadline.remainingTime() == 0);
+ // If the timer is saturated we don't want to get a valid number of milliseconds
+ QVERIFY(deadline.deadline() == std::numeric_limits<qint64>::min());
+
+ // Check that the conversion to milliseconds and nanoseconds underflows gracefully
+ deadline.setPreciseDeadline(std::numeric_limits<qint64>::min() / 10, 0, timerType);
+ QVERIFY(!deadline.isForever()); // On Win/macOS the above underflows, make sure we don't saturate to Forever
+ QVERIFY(deadline.deadline() == std::numeric_limits<qint64>::min());
+ QVERIFY(deadline.deadlineNSecs() == std::numeric_limits<qint64>::min());
+}
+
void tst_QDeadlineTimer::expire()
{
QFETCH_GLOBAL(Qt::TimerType, timerType);
diff --git a/tests/auto/testlib/selftests/expected_sleep.lightxml b/tests/auto/testlib/selftests/expected_sleep.lightxml
index 97b65d1259..78e1c44cf2 100644
--- a/tests/auto/testlib/selftests/expected_sleep.lightxml
+++ b/tests/auto/testlib/selftests/expected_sleep.lightxml
@@ -11,6 +11,10 @@
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
</TestFunction>
+<TestFunction name="wait">
+<Incident type="pass" file="" line="0" />
+ <Duration msecs="0"/>
+</TestFunction>
<TestFunction name="cleanupTestCase">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
diff --git a/tests/auto/testlib/selftests/expected_sleep.tap b/tests/auto/testlib/selftests/expected_sleep.tap
index 7caef214d2..65edefb9ba 100644
--- a/tests/auto/testlib/selftests/expected_sleep.tap
+++ b/tests/auto/testlib/selftests/expected_sleep.tap
@@ -2,8 +2,9 @@ TAP version 13
# tst_Sleep
ok 1 - initTestCase()
ok 2 - sleep()
-ok 3 - cleanupTestCase()
-1..3
-# tests 3
-# pass 3
+ok 3 - wait()
+ok 4 - cleanupTestCase()
+1..4
+# tests 4
+# pass 4
# fail 0
diff --git a/tests/auto/testlib/selftests/expected_sleep.teamcity b/tests/auto/testlib/selftests/expected_sleep.teamcity
index 36b2445ccc..45a503bb54 100644
--- a/tests/auto/testlib/selftests/expected_sleep.teamcity
+++ b/tests/auto/testlib/selftests/expected_sleep.teamcity
@@ -3,6 +3,8 @@
##teamcity[testFinished name='initTestCase()' flowId='tst_Sleep']
##teamcity[testStarted name='sleep()' flowId='tst_Sleep']
##teamcity[testFinished name='sleep()' flowId='tst_Sleep']
+##teamcity[testStarted name='wait()' flowId='tst_Sleep']
+##teamcity[testFinished name='wait()' flowId='tst_Sleep']
##teamcity[testStarted name='cleanupTestCase()' flowId='tst_Sleep']
##teamcity[testFinished name='cleanupTestCase()' flowId='tst_Sleep']
##teamcity[testSuiteFinished name='tst_Sleep' flowId='tst_Sleep']
diff --git a/tests/auto/testlib/selftests/expected_sleep.txt b/tests/auto/testlib/selftests/expected_sleep.txt
index d18c3212bb..d1701d2bed 100644
--- a/tests/auto/testlib/selftests/expected_sleep.txt
+++ b/tests/auto/testlib/selftests/expected_sleep.txt
@@ -2,6 +2,7 @@
Config: Using QtTest library
PASS : tst_Sleep::initTestCase()
PASS : tst_Sleep::sleep()
+PASS : tst_Sleep::wait()
PASS : tst_Sleep::cleanupTestCase()
-Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 0ms
+Totals: 4 passed, 0 failed, 0 skipped, 0 blacklisted, 0ms
********* Finished testing of tst_Sleep *********
diff --git a/tests/auto/testlib/selftests/expected_sleep.xml b/tests/auto/testlib/selftests/expected_sleep.xml
index 5729c0ac9d..94bb25ba8d 100644
--- a/tests/auto/testlib/selftests/expected_sleep.xml
+++ b/tests/auto/testlib/selftests/expected_sleep.xml
@@ -13,6 +13,10 @@
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
</TestFunction>
+<TestFunction name="wait">
+<Incident type="pass" file="" line="0" />
+ <Duration msecs="0"/>
+</TestFunction>
<TestFunction name="cleanupTestCase">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
diff --git a/tests/auto/testlib/selftests/expected_sleep.xunitxml b/tests/auto/testlib/selftests/expected_sleep.xunitxml
index 138486fc02..e4ed66bcb8 100644
--- a/tests/auto/testlib/selftests/expected_sleep.xunitxml
+++ b/tests/auto/testlib/selftests/expected_sleep.xunitxml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<testsuite errors="0" failures="0" tests="3" name="tst_Sleep">
+<testsuite errors="0" failures="0" tests="4" name="tst_Sleep">
<properties>
<property value="@INSERT_QT_VERSION_HERE@" name="QTestVersion"/>
<property value="@INSERT_QT_VERSION_HERE@" name="QtVersion"/>
@@ -7,6 +7,7 @@
</properties>
<testcase result="pass" name="initTestCase"/>
<testcase result="pass" name="sleep"/>
+ <testcase result="pass" name="wait"/>
<testcase result="pass" name="cleanupTestCase"/>
<system-err/>
</testsuite>
diff --git a/tests/auto/testlib/selftests/sleep/tst_sleep.cpp b/tests/auto/testlib/selftests/sleep/tst_sleep.cpp
index afe6e22120..b7b141afd0 100644
--- a/tests/auto/testlib/selftests/sleep/tst_sleep.cpp
+++ b/tests/auto/testlib/selftests/sleep/tst_sleep.cpp
@@ -36,6 +36,7 @@ class tst_Sleep: public QObject
private slots:
void sleep();
+ void wait();
};
void tst_Sleep::sleep()
@@ -53,6 +54,24 @@ void tst_Sleep::sleep()
QVERIFY(t.elapsed() > 1000 * 10);
}
+void tst_Sleep::wait()
+{
+ QElapsedTimer t;
+ t.start();
+
+ QTest::qWait(1);
+ QVERIFY(t.elapsed() >= 1);
+
+ QTest::qWait(10);
+ QVERIFY(t.elapsed() >= 11);
+
+ QTest::qWait(100);
+ QVERIFY(t.elapsed() >= 111);
+
+ QTest::qWait(1000);
+ QVERIFY(t.elapsed() >= 1111);
+}
+
QTEST_MAIN(tst_Sleep)
#include "tst_sleep.moc"