Check for overflow in QDateTime::setMSecsSinceEpoch()

When adding an offset from UTC, arithmetic may overflow.  Likewise
when combining a date and time (that have been offset for UTC).  Also
check the return from epochMSecsToLocalTime(), as it can fail; and pay
attention to the status stored by setDateTime(), to notice when it
hits an overflow. Fixed some tests that only passed because we
neglected these checks. Extended a test to check we detect overflow in
a couple of cases close to the extremes.

Change-Id: I127a670302f94a07bb9b087b1b9c608b7c08785c
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Øystein Heskestad <oystein.heskestad@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2021-03-01 14:22:55 +01:00
parent fc6629b6bb
commit 451500f8f8
2 changed files with 41 additions and 18 deletions

View File

@ -4014,8 +4014,8 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
status |= QDateTimePrivate::ValidWhenMask;
break;
case Qt::OffsetFromUTC:
msecs += d->m_offsetFromUtc * MSECS_PER_SEC;
status |= QDateTimePrivate::ValidWhenMask;
if (!add_overflow(msecs, d->m_offsetFromUtc * MSECS_PER_SEC, &msecs))
status |= QDateTimePrivate::ValidWhenMask;
break;
case Qt::TimeZone:
Q_ASSERT(!d.isShort());
@ -4038,11 +4038,15 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
QDate dt;
QTime tm;
QDateTimePrivate::DaylightStatus dstStatus;
QDateTimePrivate::epochMSecsToLocalTime(msecs, &dt, &tm, &dstStatus);
setDateTime(d, dt, tm);
refreshZonedDateTime(d, spec); // FIXME: we do this again, below
msecs = getMSecs(d);
status = mergeDaylightStatus(getStatus(d), dstStatus);
if (QDateTimePrivate::epochMSecsToLocalTime(msecs, &dt, &tm, &dstStatus)) {
setDateTime(d, dt, tm);
status = getStatus(d);
}
if ((status & QDateTimePrivate::ValidDate) && (status & QDateTimePrivate::ValidTime)) {
refreshZonedDateTime(d, spec); // FIXME: we do this again, below
msecs = getMSecs(d);
status = mergeDaylightStatus(getStatus(d), dstStatus);
}
break;
}
}

View File

@ -573,6 +573,7 @@ void tst_QDateTime::setSecsSinceEpoch()
QCOMPARE(dt1.offsetFromUtc(), 60 * 60);
// Only testing UTC; see fromSecsSinceEpoch() for fuller test.
dt1.setTimeSpec(Qt::UTC);
const qint64 maxSeconds = std::numeric_limits<qint64>::max() / 1000;
dt1.setSecsSinceEpoch(maxSeconds);
QVERIFY(dt1.isValid());
@ -727,6 +728,17 @@ void tst_QDateTime::setMSecsSinceEpoch()
QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC);
QCOMPARE(dt, reference.addMSecs(msecs));
// Tests that we correctly recognize when we fall off the extremities:
if (msecs == std::numeric_limits<qint64>::max()) {
QDateTime off(QDate(1970, 1, 1).startOfDay(Qt::OffsetFromUTC, 1));
off.setMSecsSinceEpoch(msecs);
QVERIFY(!off.isValid());
} else if (msecs == std::numeric_limits<qint64>::min()) {
QDateTime off(QDate(1970, 1, 1).startOfDay(Qt::OffsetFromUTC, -1));
off.setMSecsSinceEpoch(msecs);
QVERIFY(!off.isValid());
}
if ((localTimeType == LocalTimeAheadOfUtc && msecs == std::numeric_limits<qint64>::max())
|| (localTimeType == LocalTimeBehindUtc && msecs == std::numeric_limits<qint64>::min())) {
QDateTime curt = QDate(1970, 1, 1).startOfDay(); // initially in short-form
@ -766,32 +778,39 @@ void tst_QDateTime::fromMSecsSinceEpoch()
QCOMPARE(dtUtc.date(), utc.date());
QCOMPARE(dtUtc.time(), utc.time());
QCOMPARE(dtOffset, utc);
QCOMPARE(dtOffset.offsetFromUtc(), 60*60);
if (msecs != Bound::max()) // Offset is positive, so overflows max
if (msecs == Bound::max()) { // Offset is positive, so overflows max
QVERIFY(!dtOffset.isValid());
} else {
QCOMPARE(dtOffset, utc);
QCOMPARE(dtOffset.offsetFromUtc(), 60*60);
QCOMPARE(dtOffset.time(), utc.time().addMSecs(60*60*1000));
}
if (zoneIsCET) {
QCOMPARE(dtLocal.toLocalTime(), cet);
QCOMPARE(dtUtc.toLocalTime(), cet);
QCOMPARE(dtOffset.toLocalTime(), cet);
if (msecs != Bound::max())
QCOMPARE(dtOffset.toLocalTime(), cet);
}
if (!localOverflow)
QCOMPARE(dtLocal.toMSecsSinceEpoch(), msecs);
QCOMPARE(dtUtc.toMSecsSinceEpoch(), msecs);
QCOMPARE(dtOffset.toMSecsSinceEpoch(), msecs);
if (msecs != Bound::max())
QCOMPARE(dtOffset.toMSecsSinceEpoch(), msecs);
if (!localOverflow)
QCOMPARE(qint64(dtLocal.toSecsSinceEpoch()), msecs / 1000);
QCOMPARE(qint64(dtUtc.toSecsSinceEpoch()), msecs / 1000);
QCOMPARE(qint64(dtOffset.toSecsSinceEpoch()), msecs / 1000);
if (msecs != Bound::max())
QCOMPARE(qint64(dtOffset.toSecsSinceEpoch()), msecs / 1000);
QDateTime reference(QDate(1970, 1, 1), QTime(0, 0), Qt::UTC);
if (!localOverflow)
QCOMPARE(dtLocal, reference.addMSecs(msecs));
QCOMPARE(dtUtc, reference.addMSecs(msecs));
QCOMPARE(dtOffset, reference.addMSecs(msecs));
if (msecs != Bound::max())
QCOMPARE(dtOffset, reference.addMSecs(msecs));
}
void tst_QDateTime::fromSecsSinceEpoch()
@ -815,10 +834,10 @@ void tst_QDateTime::fromSecsSinceEpoch()
QVERIFY(!QDateTime::fromSecsSinceEpoch(first - 1).isValid());
// Use an offset for which .toUTC()'s return would flip the validity:
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds, Qt::OffsetFromUTC, 7200).isValid());
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds + 1, Qt::OffsetFromUTC, -7200).isValid());
QVERIFY(QDateTime::fromSecsSinceEpoch(-maxSeconds, Qt::OffsetFromUTC, -7200).isValid());
QVERIFY(!QDateTime::fromSecsSinceEpoch(-maxSeconds - 1, Qt::OffsetFromUTC, 7200).isValid());
QVERIFY(QDateTime::fromSecsSinceEpoch(maxSeconds - 7200, Qt::OffsetFromUTC, 7200).isValid());
QVERIFY(!QDateTime::fromSecsSinceEpoch(maxSeconds - 7199, Qt::OffsetFromUTC, 7200).isValid());
QVERIFY(QDateTime::fromSecsSinceEpoch(7200 - maxSeconds, Qt::OffsetFromUTC, -7200).isValid());
QVERIFY(!QDateTime::fromSecsSinceEpoch(7199 - maxSeconds, Qt::OffsetFromUTC, -7200).isValid());
#if QT_CONFIG(timezone)
// As for offset, use zones each side of UTC: