Restore end-of-parse reporting to qstrntod()
Rework QSimpleParsedNumber to store a qsizetype whose sign serves as ok flag (positive is ok, zero and negative are not) and magnitude is the number of characters used. This replaces an endptr that was set to null to indicate !ok, but that deprived us of end-of-parse information, which is needed for number-parsing. In particular, JS's parsing of numbers accepts overflow (where qstrntod() flags it as invalid) as infinity; so qstrntod() does need to say how long the overflowing (but JS-valid, none the less) number-text was. Modify all callers of functions using this (recently-introduced) type and add tests that fail without this fix. Fixes: QTBUG-108628 Change-Id: I416cd213e1fb8101b1af5a6d43615b970a5db9b4 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>bb10
parent
a036bd1746
commit
6c435e5dd4
|
|
@ -60,12 +60,12 @@ static bool parseIp4Internal(IPv4Address &address, const char *ptr, bool acceptL
|
|||
ptr[1] != '.' && ptr[1] != '\0')
|
||||
return false;
|
||||
|
||||
auto [ll, endptr] = qstrntoull(ptr, stop - ptr, 0);
|
||||
auto [ll, used] = qstrntoull(ptr, stop - ptr, 0);
|
||||
quint32 x = ll;
|
||||
if (!endptr || endptr == ptr || ll != x)
|
||||
if (used <= 0 || ll != x)
|
||||
return false;
|
||||
|
||||
if (*endptr == '.' || dotCount == 3) {
|
||||
if (ptr[used] == '.' || dotCount == 3) {
|
||||
if (x & ~0xff)
|
||||
return false;
|
||||
address <<= 8;
|
||||
|
|
@ -80,13 +80,13 @@ static bool parseIp4Internal(IPv4Address &address, const char *ptr, bool acceptL
|
|||
}
|
||||
address |= x;
|
||||
|
||||
if (dotCount == 3 || *endptr == '\0')
|
||||
return *endptr == '\0';
|
||||
if (*endptr != '.')
|
||||
if (dotCount == 3 || ptr[used] == '\0')
|
||||
return ptr[used] == '\0';
|
||||
if (ptr[used] != '.')
|
||||
return false;
|
||||
|
||||
++dotCount;
|
||||
ptr = endptr + 1;
|
||||
ptr += used + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -174,16 +174,16 @@ const QChar *parseIp6(IPv6Address &address, const QChar *begin, const QChar *end
|
|||
continue;
|
||||
}
|
||||
|
||||
auto [ll, endptr] = qstrntoull(ptr, stop - ptr, 16);
|
||||
auto [ll, used] = qstrntoull(ptr, stop - ptr, 16);
|
||||
quint16 x = ll;
|
||||
|
||||
// Reject malformed fields:
|
||||
// - failed to parse
|
||||
// - too many hex digits
|
||||
if (!endptr || endptr > ptr + 4)
|
||||
if (used <= 0 || used > 4)
|
||||
return begin + (ptr - buffer.data());
|
||||
|
||||
if (*endptr == '.') {
|
||||
if (ptr[used] == '.') {
|
||||
// this could be an IPv4 address
|
||||
// it's only valid in the last element
|
||||
if (pos != 12)
|
||||
|
|
@ -203,11 +203,11 @@ const QChar *parseIp6(IPv6Address &address, const QChar *begin, const QChar *end
|
|||
address[pos++] = x >> 8;
|
||||
address[pos++] = x & 0xff;
|
||||
|
||||
if (*endptr == '\0')
|
||||
if (ptr[used] == '\0')
|
||||
break;
|
||||
if (*endptr != ':')
|
||||
return begin + (endptr - buffer.data());
|
||||
ptr = endptr + 1;
|
||||
if (ptr[used] != ':')
|
||||
return begin + (used + ptr - buffer.data());
|
||||
ptr += used + 1;
|
||||
}
|
||||
return pos == 16 ? nullptr : end;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -459,27 +459,31 @@ inline bool QStorageIterator::next()
|
|||
return false;
|
||||
mnt.mount_id = r.result;
|
||||
|
||||
r = qstrntoll(r.endptr, stop - r.endptr, 10);
|
||||
ptr += r.used;
|
||||
r = qstrntoll(ptr, stop - ptr, 10);
|
||||
if (!r.ok())
|
||||
return false;
|
||||
int parent_id = r.result;
|
||||
Q_UNUSED(parent_id);
|
||||
|
||||
r = qstrntoll(r.endptr, stop - r.endptr, 10);
|
||||
ptr += r.used;
|
||||
r = qstrntoll(ptr, stop - ptr, 10);
|
||||
if (!r.ok())
|
||||
return false;
|
||||
if (*r.endptr != ':')
|
||||
ptr += r.used;
|
||||
if (*ptr != ':')
|
||||
return false;
|
||||
int rdevmajor = r.result;
|
||||
r = qstrntoll(r.endptr + 1, stop - r.endptr - 1, 10);
|
||||
++ptr; // Skip over the ':'
|
||||
r = qstrntoll(ptr, stop - ptr, 10);
|
||||
if (!r.ok())
|
||||
return false;
|
||||
mnt.rdev = makedev(rdevmajor, r.result);
|
||||
|
||||
if (*r.endptr != ' ')
|
||||
ptr += r.used;
|
||||
if (*ptr != ' ')
|
||||
return false;
|
||||
|
||||
ptr = const_cast<char *>(r.endptr);
|
||||
mnt.subvolume = ++ptr;
|
||||
ptr = parseMangledPath(ptr);
|
||||
if (!ptr)
|
||||
|
|
|
|||
|
|
@ -4145,20 +4145,20 @@ qulonglong QLocaleData::stringToUnsLongLong(QStringView str, int base, bool *ok,
|
|||
|
||||
qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *ok)
|
||||
{
|
||||
auto [l, endptr] = qstrntoll(num.data(), num.size(), base);
|
||||
if (!endptr) {
|
||||
const qsizetype len = num.size();
|
||||
auto [l, used] = qstrntoll(num.data(), len, base);
|
||||
if (used <= 0) {
|
||||
if (ok != nullptr)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *const stop = num.end();
|
||||
if (endptr < stop && *endptr != '\0') {
|
||||
while (endptr < stop && ascii_isspace(*endptr))
|
||||
++endptr;
|
||||
if (used < len && num[used] != '\0') {
|
||||
while (used < len && ascii_isspace(num[used]))
|
||||
++used;
|
||||
}
|
||||
|
||||
if (endptr < stop && *endptr != '\0') {
|
||||
if (used < len && num[used] != '\0') {
|
||||
// we stopped at a non-digit character after converting some digits
|
||||
if (ok != nullptr)
|
||||
*ok = false;
|
||||
|
|
@ -4172,20 +4172,20 @@ qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *o
|
|||
|
||||
qulonglong QLocaleData::bytearrayToUnsLongLong(QByteArrayView num, int base, bool *ok)
|
||||
{
|
||||
auto [l, endptr] = qstrntoull(num.data(), num.size(), base);
|
||||
if (!endptr) {
|
||||
const qsizetype len = num.size();
|
||||
auto [l, used] = qstrntoull(num.data(), len, base);
|
||||
if (used <= 0) {
|
||||
if (ok != nullptr)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *const stop = num.end();
|
||||
if (endptr < stop && *endptr != '\0') {
|
||||
while (endptr < stop && ascii_isspace(*endptr))
|
||||
++endptr;
|
||||
if (used < len && num[used] != '\0') {
|
||||
while (used < len && ascii_isspace(num[used]))
|
||||
++used;
|
||||
}
|
||||
|
||||
if (endptr < stop && *endptr != '\0') {
|
||||
if (used < len && num[used] != '\0') {
|
||||
if (ok != nullptr)
|
||||
*ok = false;
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ void qt_doubleToAscii(double d, QLocaleData::DoubleForm form, int precision,
|
|||
auto r = qstrntoll(target.data() + eSign + 1, length - eSign - 1, 10);
|
||||
decpt = r.result + 1;
|
||||
Q_ASSERT(r.ok());
|
||||
Q_ASSERT(r.endptr - target.data() <= length);
|
||||
Q_ASSERT(r.used + eSign + 1 <= length);
|
||||
} else {
|
||||
// No 'e' found, so it's the 'f' form. Variants of snprintf generate numbers with
|
||||
// potentially multiple digits before the '.', but without decimal exponent then. So we
|
||||
|
|
@ -282,9 +282,9 @@ QSimpleParsedNumber<double> qt_asciiToDouble(const char *num, qsizetype numLen,
|
|||
char c2 = lowered(num[offset + 1]);
|
||||
char c3 = lowered(num[offset + 2]);
|
||||
if (c == 'i' && c2 == 'n' && c3 == 'f')
|
||||
return { negative ? -qt_inf() : qt_inf(), num + offset + 3 };
|
||||
return { negative ? -qt_inf() : qt_inf(), offset + 3 };
|
||||
else if (c == 'n' && c2 == 'a' && c3 == 'n' && !hasSign)
|
||||
return { qt_qnan(), num + 3 };
|
||||
return { qt_qnan(), 3 };
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -313,7 +313,7 @@ QSimpleParsedNumber<double> qt_asciiToDouble(const char *num, qsizetype numLen,
|
|||
return {};
|
||||
} else {
|
||||
// Overflow. That's not OK, but we still return infinity.
|
||||
return { d, nullptr };
|
||||
return { d, -processed };
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
|
@ -342,7 +342,7 @@ QSimpleParsedNumber<double> qt_asciiToDouble(const char *num, qsizetype numLen,
|
|||
return {};
|
||||
}
|
||||
}
|
||||
return { d, nullptr };
|
||||
return { d, -processed };
|
||||
}
|
||||
#endif // !defined(QT_NO_DOUBLECONVERSION) && !defined(QT_BOOTSTRAPPED)
|
||||
|
||||
|
|
@ -360,7 +360,7 @@ QSimpleParsedNumber<double> qt_asciiToDouble(const char *num, qsizetype numLen,
|
|||
}
|
||||
}
|
||||
}
|
||||
return { d, num + processed };
|
||||
return { d, processed };
|
||||
}
|
||||
|
||||
/* Detect base if 0 and, if base is hex or bin, skip over 0x/0b prefixes */
|
||||
|
|
@ -430,7 +430,7 @@ QSimpleParsedNumber<qulonglong> qstrntoull(const char *begin, qsizetype size, in
|
|||
const auto res = std::from_chars(prefix.next, stop, result, prefix.base);
|
||||
if (res.ec != std::errc{})
|
||||
return { };
|
||||
return { result, res.ptr == prefix.next ? begin : res.ptr };
|
||||
return { result, res.ptr == prefix.next ? 0 : res.ptr - begin };
|
||||
}
|
||||
|
||||
QSimpleParsedNumber<qlonglong> qstrntoll(const char *begin, qsizetype size, int base)
|
||||
|
|
@ -458,12 +458,12 @@ QSimpleParsedNumber<qlonglong> qstrntoll(const char *begin, qsizetype size, int
|
|||
unsigned long long check = 0;
|
||||
res = std::from_chars(prefix.next, stop, check, prefix.base);
|
||||
if (res.ec == std::errc{} && check + std::numeric_limits<long long>::min() == 0)
|
||||
return { std::numeric_limits<long long>::min(), res.ptr };
|
||||
return { std::numeric_limits<long long>::min(), res.ptr - begin };
|
||||
return { };
|
||||
}
|
||||
if (res.ec != std::errc{})
|
||||
return { };
|
||||
return { negate ? -result : result, res.ptr };
|
||||
return { negate ? -result : result, res.ptr - begin };
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
|
|
@ -558,7 +558,7 @@ double qstrntod(const char *s00, qsizetype len, const char **se, bool *ok)
|
|||
{
|
||||
auto r = qt_asciiToDouble(s00, len, TrailingJunkAllowed);
|
||||
if (se)
|
||||
*se = r.endptr ? r.endptr : s00;
|
||||
*se = s00 + (r.used < 0 ? -r.used : r.used);
|
||||
if (ok)
|
||||
*ok = r.ok();
|
||||
return r.result;
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ enum StrayCharacterMode {
|
|||
template <typename T> struct QSimpleParsedNumber
|
||||
{
|
||||
T result;
|
||||
const char *endptr;
|
||||
bool ok() { return endptr; }
|
||||
// When used < 0, -used is how much was used, but it was an error.
|
||||
qsizetype used;
|
||||
bool ok() const { return used > 0; }
|
||||
};
|
||||
|
||||
// API note: this function can't process a number with more than 2.1 billion digits
|
||||
|
|
|
|||
|
|
@ -1075,8 +1075,8 @@ static QString winIso639LangName(LCID id)
|
|||
|
||||
if (!lang_code.isEmpty()) {
|
||||
const QByteArray latin1 = std::move(lang_code).toLatin1();
|
||||
const auto [i, endptr] = qstrntoull(latin1.data(), latin1.size(), 16);
|
||||
if (endptr && *endptr == '\0') {
|
||||
const auto [i, used] = qstrntoull(latin1.data(), latin1.size(), 16);
|
||||
if (used > 0 && latin1[used] == '\0') {
|
||||
switch (i) {
|
||||
case 0x814:
|
||||
result = u"nn"_s; // Nynorsk
|
||||
|
|
@ -1117,8 +1117,8 @@ static QByteArray getWinLocaleName(LCID id)
|
|||
if (result == "C"
|
||||
|| (!result.isEmpty() && qt_splitLocaleName(QString::fromLocal8Bit(result)))) {
|
||||
// See if we have a Windows locale code instead of a locale name:
|
||||
auto [id, ok] = qstrntoll(result.data(), result.size(), 0);
|
||||
if (!ok || id == 0 || id < INT_MIN || id > INT_MAX) // Assume real locale name
|
||||
auto [id, used] = qstrntoll(result.data(), result.size(), 0);
|
||||
if (used <= 0 || id == 0 || id < INT_MIN || id > INT_MAX) // Assume real locale name
|
||||
return result;
|
||||
return winLangCodeToIsoName(int(id));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6901,9 +6901,9 @@ static int parse_field_width(const char *&c, qsizetype size)
|
|||
|
||||
// can't be negative - started with a digit
|
||||
// contains at least one digit
|
||||
auto [result, endp] = qstrntoull(c, size, 10);
|
||||
c = endp;
|
||||
if (!endp)
|
||||
auto [result, used] = qstrntoull(c, size, 10);
|
||||
c += used;
|
||||
if (used <= 0)
|
||||
return false;
|
||||
// preserve Qt 5.5 behavior of consuming all digits, no matter how many
|
||||
while (c < stop && qIsDigit(*c))
|
||||
|
|
|
|||
|
|
@ -394,26 +394,26 @@ static int parsePosixTime(const char *begin, const char *end)
|
|||
const int maxHour = 137; // POSIX's extended range.
|
||||
auto r = qstrntoll(begin, end - begin, 10);
|
||||
hour = r.result;
|
||||
if (!r.ok() || hour < -maxHour || hour > maxHour || r.endptr > begin + 2)
|
||||
if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
|
||||
return INT_MIN;
|
||||
begin = r.endptr;
|
||||
begin += r.used;
|
||||
if (begin < end && *begin == ':') {
|
||||
// minutes
|
||||
++begin;
|
||||
r = qstrntoll(begin, end - begin, 10);
|
||||
min = r.result;
|
||||
if (!r.ok() || min < 0 || min > 59 || r.endptr > begin + 2)
|
||||
if (!r.ok() || min < 0 || min > 59 || r.used > 2)
|
||||
return INT_MIN;
|
||||
|
||||
begin = r.endptr;
|
||||
begin += r.used;
|
||||
if (begin < end && *begin == ':') {
|
||||
// seconds
|
||||
++begin;
|
||||
r = qstrntoll(begin, end - begin, 10);
|
||||
sec = r.result;
|
||||
if (!r.ok() || sec < 0 || sec > 59 || r.endptr > begin + 2)
|
||||
if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
|
||||
return INT_MIN;
|
||||
begin = r.endptr;
|
||||
begin += r.used;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ private:
|
|||
const char *seedstr = getenv("QT_HASH_SEED");
|
||||
if (seedstr) {
|
||||
auto r = qstrntoll(seedstr, strlen(seedstr), 10);
|
||||
if (r.endptr == seedstr + strlen(seedstr)) {
|
||||
if (r.used > 0 && size_t(r.used) == strlen(seedstr)) {
|
||||
if (r.result) {
|
||||
// can't use qWarning here (reentrancy)
|
||||
fprintf(stderr, "QT_HASH_SEED: forced seed value is not 0; ignored.\n");
|
||||
|
|
|
|||
|
|
@ -409,12 +409,12 @@ static QVersionNumber from_string(QLatin1StringView string, qsizetype *suffixInd
|
|||
|
||||
do {
|
||||
// parsing as unsigned so a minus sign is rejected
|
||||
auto [value, end] = qstrntoull(start, endOfString - start, 10);
|
||||
if (!end || value > qulonglong(std::numeric_limits<int>::max()))
|
||||
auto [value, used] = qstrntoull(start, endOfString - start, 10);
|
||||
if (used <= 0 || value > qulonglong(std::numeric_limits<int>::max()))
|
||||
break;
|
||||
seg.append(int(value));
|
||||
start = end + 1;
|
||||
lastGoodEnd = end;
|
||||
start += used + 1;
|
||||
lastGoodEnd = start - 1;
|
||||
} while (start < endOfString && *lastGoodEnd == '.');
|
||||
|
||||
if (suffixIndex)
|
||||
|
|
|
|||
|
|
@ -1246,6 +1246,10 @@ void tst_QLocale::strtod_data()
|
|||
QTest::newRow("12456789012") << QString("12456789012") << 12456789012.0 << 11 << true;
|
||||
QTest::newRow("1.2456789012e10") << QString("1.2456789012e10") << 12456789012.0 << 15 << true;
|
||||
|
||||
// Overflow - fails but reports right length:
|
||||
QTest::newRow("1e2000") << QString("1e2000") << qInf() << 6 << false;
|
||||
QTest::newRow("-1e2000") << QString("-1e2000") << -qInf() << 7 << false;
|
||||
|
||||
// starts with junk, fails
|
||||
QTest::newRow("a0") << QString("a0") << 0.0 << 0 << false;
|
||||
QTest::newRow("a0.") << QString("a0.") << 0.0 << 0 << false;
|
||||
|
|
@ -1277,6 +1281,10 @@ void tst_QLocale::strtod_data()
|
|||
QTest::newRow("12456789012f") << QString("12456789012f") << 12456789012.0 << 11 << true;
|
||||
QTest::newRow("1.2456789012e10g") << QString("1.2456789012e10g") << 12456789012.0 << 15 << true;
|
||||
|
||||
// Overflow, ends with cruft - fails but reports right length:
|
||||
QTest::newRow("1e2000 cruft") << QString("1e2000 cruft") << qInf() << 6 << false;
|
||||
QTest::newRow("-1e2000 cruft") << QString("-1e2000 cruft") << -qInf() << 7 << false;
|
||||
|
||||
// "0x" prefix, success but only for the "0" before "x"
|
||||
QTest::newRow("0x0") << QString("0x0") << 0.0 << 1 << true;
|
||||
QTest::newRow("0x0.") << QString("0x0.") << 0.0 << 1 << true;
|
||||
|
|
@ -1309,9 +1317,9 @@ void tst_QLocale::strtod()
|
|||
QCOMPARE(actualOk, ok);
|
||||
QCOMPARE(static_cast<int>(end - numData.constData()), processed);
|
||||
|
||||
// make sure neither QByteArray, QString or QLocale also work
|
||||
// (but they don't support incomplete parsing)
|
||||
if (processed == num_str.size() || processed == 0) {
|
||||
// Make sure QByteArray, QString and QLocale also work.
|
||||
// (They don't support incomplete parsing, and give 0 for overflow.)
|
||||
if (ok && (processed == num_str.size() || processed == 0)) {
|
||||
actualOk = false;
|
||||
QCOMPARE(num_str.toDouble(&actualOk), num);
|
||||
QCOMPARE(actualOk, ok);
|
||||
|
|
|
|||
Loading…
Reference in New Issue