diff options
author | Edward Welbourne <edward.welbourne@qt.io> | 2021-08-27 14:58:57 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2021-08-30 17:46:00 +0200 |
commit | 5644af6f8a800a1516360a42ba4c1a8dc61fc516 (patch) | |
tree | 0fb11871c40c7f389f95bc227699fdcda8b7889a | |
parent | 522ca997d3baab1b88f454bbeea9f357d3969dff (diff) |
Replace FreeBSD's strtou?ll() with std::from_chars()-based strntou?ll()
Remove third-party code in favor of STL. Implement (for now)
strtou?ll() as inlines on strntou?ll() calling strlen() for the size
parameter. (This is not entirely safe, as a string lacking
'\0'-termination but with at least some non-matching text after the
numeric portion would formerly be parsed just fine, but would now
produce a crash. However, strtou?ll() are internal and callers should
be ensuring '\0'-termination.)
Task-number: QTBUG-74286
Change-Id: I0c8ca7d4f6110367e93b4c0164854a82c5a545e1
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r-- | src/3rdparty/freebsd/0001-Patch-the-FreeBSD-strto-u-ll-functions-to-work-insid.patch | 158 | ||||
-rw-r--r-- | src/3rdparty/freebsd/LICENSE | 31 | ||||
-rw-r--r-- | src/3rdparty/freebsd/qt_attribution.json | 18 | ||||
-rw-r--r-- | src/3rdparty/freebsd/strtoll.c | 134 | ||||
-rw-r--r-- | src/3rdparty/freebsd/strtoull.c | 112 | ||||
-rw-r--r-- | src/corelib/text/qlocale_tools.cpp | 117 | ||||
-rw-r--r-- | src/corelib/text/qlocale_tools_p.h | 11 | ||||
-rw-r--r-- | tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp | 199 | ||||
-rw-r--r-- | tests/auto/corelib/text/qlocale/tst_qlocale.cpp | 3 | ||||
-rw-r--r-- | tests/auto/corelib/text/qstring/tst_qstring.cpp | 76 |
10 files changed, 346 insertions, 513 deletions
diff --git a/src/3rdparty/freebsd/0001-Patch-the-FreeBSD-strto-u-ll-functions-to-work-insid.patch b/src/3rdparty/freebsd/0001-Patch-the-FreeBSD-strto-u-ll-functions-to-work-insid.patch deleted file mode 100644 index 8fd012e4b1..0000000000 --- a/src/3rdparty/freebsd/0001-Patch-the-FreeBSD-strto-u-ll-functions-to-work-insid.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 81a2d1a38becdeed2cd8b963e190aedf197e39c6 Mon Sep 17 00:00:00 2001 -From: Thiago Macieira <thiago.macieira@intel.com> -Date: Thu, 2 Oct 2014 22:03:19 -0700 -Subject: [PATCH 1/1] Patch the FreeBSD strto(u)ll functions to work inside - QtCore - -Changes: - - remove the #includes and the SCCSID - - rename from strtoxx_l to qt_strtoxx (merging the two functions) - - remove __restrict - - remove the locale_t parameter and use ascii_isspace instead of isspace_l - - fix compilation with -Wcast-qual (requires C++) - - src/3rdparty/freebsd/strtoll.c | 27 +++------------------------ - src/3rdparty/freebsd/strtoull.c | 27 +++------------------------ - 2 files changed, 6 insertions(+), 48 deletions(-) - -diff --git a/src/3rdparty/freebsd/strtoll.c b/src/3rdparty/freebsd/strtoll.c -index c87aefb1cd..89da83425d 100644 ---- a/src/3rdparty/freebsd/strtoll.c -+++ b/src/3rdparty/freebsd/strtoll.c -@@ -1,6 +1,4 @@ - /*- -- * SPDX-License-Identifier: BSD-3-Clause -- * - * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. - * -@@ -34,18 +32,6 @@ - * SUCH DAMAGE. - */ - --#if defined(LIBC_SCCS) && !defined(lint) --static char sccsid[] = "@(#)strtoq.c 8.1 (Berkeley) 6/4/93"; --#endif /* LIBC_SCCS and not lint */ --#include <sys/cdefs.h> --__FBSDID("$FreeBSD$"); -- --#include <limits.h> --#include <errno.h> --#include <ctype.h> --#include <stdlib.h> --#include "xlocale_private.h" -- - /* - * Convert a string to a long long integer. - * -@@ -53,15 +39,13 @@ __FBSDID("$FreeBSD$"); - * alphabets and digits are each contiguous. - */ - long long --strtoll_l(const char * __restrict nptr, char ** __restrict endptr, int base, -- locale_t locale) -+qt_strtoll(const char * nptr, char **endptr, int base) - { - const char *s; - unsigned long long acc; - char c; - unsigned long long cutoff; - int neg, any, cutlim; -- FIX_LOCALE(locale); - - /* - * Skip white space and pick up leading +/- sign if any. -@@ -71,7 +55,7 @@ strtoll_l(const char * __restrict nptr, char ** __restrict endptr, int base, - s = nptr; - do { - c = *s++; -- } while (isspace_l((unsigned char)c, locale)); -+ } while (ascii_isspace(c)); - if (c == '-') { - neg = 1; - c = *s++; -@@ -145,11 +129,6 @@ noconv: - } else if (neg) - acc = -acc; - if (endptr != NULL) -- *endptr = (char *)(any ? s - 1 : nptr); -+ *endptr = const_cast<char *>(any ? s - 1 : nptr); - return (acc); - } --long long --strtoll(const char * __restrict nptr, char ** __restrict endptr, int base) --{ -- return strtoll_l(nptr, endptr, base, __get_locale()); --} -diff --git a/src/3rdparty/freebsd/strtoull.c b/src/3rdparty/freebsd/strtoull.c -index 58a9b23b56..cf151691ad 100644 ---- a/src/3rdparty/freebsd/strtoull.c -+++ b/src/3rdparty/freebsd/strtoull.c -@@ -1,6 +1,4 @@ - /*- -- * SPDX-License-Identifier: BSD-3-Clause -- * - * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. - * -@@ -34,18 +32,6 @@ - * SUCH DAMAGE. - */ - --#if defined(LIBC_SCCS) && !defined(lint) --static char sccsid[] = "@(#)strtouq.c 8.1 (Berkeley) 6/4/93"; --#endif /* LIBC_SCCS and not lint */ --#include <sys/cdefs.h> --__FBSDID("$FreeBSD$"); -- --#include <limits.h> --#include <errno.h> --#include <ctype.h> --#include <stdlib.h> --#include "xlocale_private.h" -- - /* - * Convert a string to an unsigned long long integer. - * -@@ -53,15 +39,13 @@ __FBSDID("$FreeBSD$"); - * alphabets and digits are each contiguous. - */ - unsigned long long --strtoull_l(const char * __restrict nptr, char ** __restrict endptr, int base, -- locale_t locale) -+qt_strtoull(const char * nptr, char **endptr, int base) - { - const char *s; - unsigned long long acc; - char c; - unsigned long long cutoff; - int neg, any, cutlim; -- FIX_LOCALE(locale); - - /* - * See strtoq for comments as to the logic used. -@@ -69,7 +53,7 @@ strtoull_l(const char * __restrict nptr, char ** __restrict endptr, int base, - s = nptr; - do { - c = *s++; -- } while (isspace_l((unsigned char)c, locale)); -+ } while (ascii_isspace(c)); - if (c == '-') { - neg = 1; - c = *s++; -@@ -123,11 +107,6 @@ noconv: - } else if (neg) - acc = -acc; - if (endptr != NULL) -- *endptr = (char *)(any ? s - 1 : nptr); -+ *endptr = const_cast<char *>(any ? s - 1 : nptr); - return (acc); - } --unsigned long long --strtoull(const char * __restrict nptr, char ** __restrict endptr, int base) --{ -- return strtoull_l(nptr, endptr, base, __get_locale()); --} --- -2.25.1 - diff --git a/src/3rdparty/freebsd/LICENSE b/src/3rdparty/freebsd/LICENSE deleted file mode 100644 index 5bb30318eb..0000000000 --- a/src/3rdparty/freebsd/LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Copyright (c) 1992, 1993 - The Regents of the University of California. All rights reserved. - -Copyright (c) 2011 The FreeBSD Foundation -All rights reserved. -Portions of this software were developed by David Chisnall -under sponsorship from the FreeBSD Foundation. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. Neither the name of the University nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff --git a/src/3rdparty/freebsd/qt_attribution.json b/src/3rdparty/freebsd/qt_attribution.json deleted file mode 100644 index 644880a90d..0000000000 --- a/src/3rdparty/freebsd/qt_attribution.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Id": "freebsd", - "Name": "FreeBSD strtoll and strtoull", - "QDocModule": "qtcore", - "QtUsage": "Used in Qt Core.", - "Files": "strtoll.c strtoull.c", - - "Description": "strtoll() and strtoull() are functions for converting a string to (unsigned) long long integer.", - "Homepage": "https://github.com/freebsd/freebsd/", - "DownloadLocation": "https://github.com/freebsd/freebsd/tree/master/lib/libc/stdlib", - "Version": "upstream has complicated with std locales; do not update", - "Version": "18b29f3fb8abee5d57ed8f4a44f806bec7e0eeff", - "License": "BSD 3-clause \"New\" or \"Revised\" License", - "LicenseId": "BSD-3-Clause", - "LicenseFile": "LICENSE", - "Copyright": "Copyright (c) 1992, 1993 The Regents of the University of California. -Copyright (c) 2011 The FreeBSD Foundation" -} diff --git a/src/3rdparty/freebsd/strtoll.c b/src/3rdparty/freebsd/strtoll.c deleted file mode 100644 index 89da83425d..0000000000 --- a/src/3rdparty/freebsd/strtoll.c +++ /dev/null @@ -1,134 +0,0 @@ -/*- - * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. - * - * Copyright (c) 2011 The FreeBSD Foundation - * All rights reserved. - * Portions of this software were developed by David Chisnall - * under sponsorship from the FreeBSD Foundation. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Convert a string to a long long integer. - * - * Assumes that the upper and lower case - * alphabets and digits are each contiguous. - */ -long long -qt_strtoll(const char * nptr, char **endptr, int base) -{ - const char *s; - unsigned long long acc; - char c; - unsigned long long cutoff; - int neg, any, cutlim; - - /* - * Skip white space and pick up leading +/- sign if any. - * If base is 0, allow 0x for hex and 0 for octal, else - * assume decimal; if base is already 16, allow 0x. - */ - s = nptr; - do { - c = *s++; - } while (ascii_isspace(c)); - if (c == '-') { - neg = 1; - c = *s++; - } else { - neg = 0; - if (c == '+') - c = *s++; - } - if ((base == 0 || base == 16) && - c == '0' && (*s == 'x' || *s == 'X') && - ((s[1] >= '0' && s[1] <= '9') || - (s[1] >= 'A' && s[1] <= 'F') || - (s[1] >= 'a' && s[1] <= 'f'))) { - c = s[1]; - s += 2; - base = 16; - } - if (base == 0) - base = c == '0' ? 8 : 10; - acc = any = 0; - if (base < 2 || base > 36) - goto noconv; - - /* - * Compute the cutoff value between legal numbers and illegal - * numbers. That is the largest legal value, divided by the - * base. An input number that is greater than this value, if - * followed by a legal input character, is too big. One that - * is equal to this value may be valid or not; the limit - * between valid and invalid numbers is then based on the last - * digit. For instance, if the range for quads is - * [-9223372036854775808..9223372036854775807] and the input base - * is 10, cutoff will be set to 922337203685477580 and cutlim to - * either 7 (neg==0) or 8 (neg==1), meaning that if we have - * accumulated a value > 922337203685477580, or equal but the - * next digit is > 7 (or 8), the number is too big, and we will - * return a range error. - * - * Set 'any' if any `digits' consumed; make it negative to indicate - * overflow. - */ - cutoff = neg ? (unsigned long long)-(LLONG_MIN + LLONG_MAX) + LLONG_MAX - : LLONG_MAX; - cutlim = cutoff % base; - cutoff /= base; - for ( ; ; c = *s++) { - if (c >= '0' && c <= '9') - c -= '0'; - else if (c >= 'A' && c <= 'Z') - c -= 'A' - 10; - else if (c >= 'a' && c <= 'z') - c -= 'a' - 10; - else - break; - if (c >= base) - break; - if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) - any = -1; - else { - any = 1; - acc *= base; - acc += c; - } - } - if (any < 0) { - acc = neg ? LLONG_MIN : LLONG_MAX; - errno = ERANGE; - } else if (!any) { -noconv: - errno = EINVAL; - } else if (neg) - acc = -acc; - if (endptr != NULL) - *endptr = const_cast<char *>(any ? s - 1 : nptr); - return (acc); -} diff --git a/src/3rdparty/freebsd/strtoull.c b/src/3rdparty/freebsd/strtoull.c deleted file mode 100644 index cf151691ad..0000000000 --- a/src/3rdparty/freebsd/strtoull.c +++ /dev/null @@ -1,112 +0,0 @@ -/*- - * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. - * - * Copyright (c) 2011 The FreeBSD Foundation - * All rights reserved. - * Portions of this software were developed by David Chisnall - * under sponsorship from the FreeBSD Foundation. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Convert a string to an unsigned long long integer. - * - * Assumes that the upper and lower case - * alphabets and digits are each contiguous. - */ -unsigned long long -qt_strtoull(const char * nptr, char **endptr, int base) -{ - const char *s; - unsigned long long acc; - char c; - unsigned long long cutoff; - int neg, any, cutlim; - - /* - * See strtoq for comments as to the logic used. - */ - s = nptr; - do { - c = *s++; - } while (ascii_isspace(c)); - if (c == '-') { - neg = 1; - c = *s++; - } else { - neg = 0; - if (c == '+') - c = *s++; - } - if ((base == 0 || base == 16) && - c == '0' && (*s == 'x' || *s == 'X') && - ((s[1] >= '0' && s[1] <= '9') || - (s[1] >= 'A' && s[1] <= 'F') || - (s[1] >= 'a' && s[1] <= 'f'))) { - c = s[1]; - s += 2; - base = 16; - } - if (base == 0) - base = c == '0' ? 8 : 10; - acc = any = 0; - if (base < 2 || base > 36) - goto noconv; - - cutoff = ULLONG_MAX / base; - cutlim = ULLONG_MAX % base; - for ( ; ; c = *s++) { - if (c >= '0' && c <= '9') - c -= '0'; - else if (c >= 'A' && c <= 'Z') - c -= 'A' - 10; - else if (c >= 'a' && c <= 'z') - c -= 'a' - 10; - else - break; - if (c >= base) - break; - if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) - any = -1; - else { - any = 1; - acc *= base; - acc += c; - } - } - if (any < 0) { - acc = ULLONG_MAX; - errno = ERANGE; - } else if (!any) { -noconv: - errno = EINVAL; - } else if (neg) - acc = -acc; - if (endptr != NULL) - *endptr = const_cast<char *>(any ? s - 1 : nptr); - return (acc); -} diff --git a/src/corelib/text/qlocale_tools.cpp b/src/corelib/text/qlocale_tools.cpp index 15933c3644..c144dba938 100644 --- a/src/corelib/text/qlocale_tools.cpp +++ b/src/corelib/text/qlocale_tools.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** @@ -54,6 +54,7 @@ #include <time.h> #include <limits> +#include <charconv> #if defined(Q_OS_LINUX) && !defined(__UCLIBC__) # include <fenv.h> @@ -72,13 +73,6 @@ QT_BEGIN_NAMESPACE -QT_WARNING_PUSH - /* "unary minus operator applied to unsigned type, result still unsigned" */ -QT_WARNING_DISABLE_MSVC(4146) -#include "../../3rdparty/freebsd/strtoull.c" -#include "../../3rdparty/freebsd/strtoll.c" -QT_WARNING_POP - QT_CLOCALE_HOLDER void qt_doubleToAscii(double d, QLocaleData::DoubleForm form, int precision, char *buf, int bufSize, @@ -416,50 +410,99 @@ double qt_asciiToDouble(const char *num, qsizetype numLen, bool &ok, int &proces return d; } +/* Detect base if 0 and, if base is hex, skip over 0x prefix */ +static auto scanPrefix(const char *p, const char *stop, int base) +{ + if (p < stop && *p >= '0' && *p <= '9') { + if (*p == '0') { + const char *x = p + 1; + if (x < stop && (*x == 'x' || *x == 'X')) { + if (base == 0) + base = 16; + if (base == 16) + p += 2; + } else if (base == 0) { + base = 8; + } + } else if (base == 0) { + base = 10; + } + Q_ASSERT(base); + } + struct R + { + const char *next; + int base; + }; + return R{p, base}; +} + unsigned long long -qstrtoull(const char * nptr, const char **endptr, int base, bool *ok) +qstrntoull(const char *begin, qsizetype size, const char **endptr, int base, bool *ok) { - // strtoull accepts negative numbers. We don't. - // Use a different variable so we pass the original nptr to strtoul - // (we need that so endptr may be nptr in case of failure) - const char *begin = nptr; - while (ascii_isspace(*begin)) - ++begin; - if (*begin == '-') { + const char *p = begin, *const stop = begin + size; + while (p < stop && ascii_isspace(*p)) + ++p; + unsigned long long result = 0; + if (p >= stop || *p == '-') { *ok = false; - return 0; + if (endptr) + *endptr = begin; + return result; } - - *ok = true; - errno = 0; - char *endptr2 = nullptr; - unsigned long long result = qt_strtoull(nptr, &endptr2, base); - if (endptr) - *endptr = endptr2; - if ((result == 0 || result == std::numeric_limits<unsigned long long>::max()) - && (errno || endptr2 == nptr)) { + const auto prefix = scanPrefix(*p == '+' ? p + 1 : p, stop, base); + if (!prefix.base || prefix.next >= stop) { + if (endptr) + *endptr = begin; *ok = false; return 0; } + + const auto res = std::from_chars(prefix.next, stop, result, prefix.base); + *ok = res.ec == std::errc{}; + if (endptr) + *endptr = res.ptr == prefix.next ? begin : res.ptr; return result; } long long -qstrtoll(const char * nptr, const char **endptr, int base, bool *ok) +qstrntoll(const char *begin, qsizetype size, const char **endptr, int base, bool *ok) { - *ok = true; - errno = 0; - char *endptr2 = nullptr; - long long result = qt_strtoll(nptr, &endptr2, base); - if (endptr) - *endptr = endptr2; - if ((result == 0 || result == std::numeric_limits<long long>::min() - || result == std::numeric_limits<long long>::max()) - && (errno || nptr == endptr2)) { + const char *p = begin, *const stop = begin + size; + while (p < stop && ascii_isspace(*p)) + ++p; + // Frustratingly, std::from_chars() doesn't cope with a 0x prefix that might + // be between the sign and digits, so we have to handle that for it, which + // means we can't use its ability to read LLONG_MIN directly; see below. + const bool negate = p < stop && *p == '-'; + if (negate || (p < stop && *p == '+')) + ++p; + + const auto prefix = scanPrefix(p, stop, base); + if (!prefix.base || prefix.next >= stop) { + if (endptr) + *endptr = begin; *ok = false; return 0; } - return result; + + long long result = 0; + auto res = std::from_chars(prefix.next, stop, result, prefix.base); + *ok = res.ec == std::errc{}; + if (negate && res.ec == std::errc::result_out_of_range) { + // Maybe LLONG_MIN: + 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) { + *ok = true; + if (endptr) + *endptr = res.ptr; + return std::numeric_limits<long long>::min(); + } + } + if (endptr) + *endptr = res.ptr == prefix.next ? begin : res.ptr; + return negate && *ok ? -result : result; } template <typename Char> diff --git a/src/corelib/text/qlocale_tools_p.h b/src/corelib/text/qlocale_tools_p.h index b6ffff8224..27d849ea7a 100644 --- a/src/corelib/text/qlocale_tools_p.h +++ b/src/corelib/text/qlocale_tools_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -117,8 +117,13 @@ inline double qstrtod(const char *s00, char const **se, bool *ok) return qstrntod(s00, len, se, ok); } -qlonglong qstrtoll(const char *nptr, const char **endptr, int base, bool *ok); -qulonglong qstrtoull(const char *nptr, const char **endptr, int base, bool *ok); +qlonglong qstrntoll(const char *nptr, qsizetype size, const char **endptr, int base, bool *ok); +qulonglong qstrntoull(const char *nptr, qsizetype size, const char **endptr, int base, bool *ok); + +inline qlonglong qstrtoll(const char *nptr, const char **endptr, int base, bool *ok) +{ return qstrntoll(nptr, strlen(nptr), endptr, base, ok); } +inline qulonglong qstrtoull(const char *nptr, const char **endptr, int base, bool *ok) +{ return qstrntoull(nptr, strlen(nptr), endptr, base, ok); } QT_END_NAMESPACE diff --git a/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp b/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp index 3b7e0efb00..ff04eca14d 100644 --- a/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp +++ b/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp @@ -36,6 +36,7 @@ #include <private/qtools_p.h> #include "../shared/test_number_shared.h" +#include <limits> class tst_QByteArray : public QObject { @@ -1674,6 +1675,20 @@ void tst_QByteArray::toULong() QCOMPARE(b, ok); } +static QByteArray decNext(QByteArray &&big) +{ + // Increments a decimal digit-string (ignoring sign, so decrements if + // negative); only intended for taking a boundary value just out of range, + // so big is never a string of only 9s (that'd be one less than a power of + // ten, which cannot be a power of two, as odd, or one less than one, as the + // power of ten isn't a power of two). + int i = big.size() - 1; + while (big.at(i) == '9') + big[i--] = '0'; + big[i] += 1; + return big; +} + void tst_QByteArray::toLongLong_data() { QTest::addColumn<QByteArray>("str"); @@ -1689,10 +1704,14 @@ void tst_QByteArray::toLongLong_data() << 7679359922672374856LL << true; QTest::newRow("in range dec neg") << QByteArray("-7679359922672374856") << 10 << -7679359922672374856LL << true; - QTest::newRow("in range hex") << QByteArray("6A929129A5421448") << 16 << 0x6A929129A5421448LL - << true; - QTest::newRow("in range hex neg") << QByteArray("-6A929129A5421448") << 16 - << -0x6A929129A5421448LL << true; + QTest::newRow("in range hex") + << QByteArray("6A929129A5421448") << 16 << 0x6A929129A5421448LL << true; + QTest::newRow("in range hex prefix") + << QByteArray("0x6A929129A5421448") << 16 << 0x6A929129A5421448LL << true; + QTest::newRow("in range hex neg") + << QByteArray("-6A929129A5421448") << 16 << -0x6A929129A5421448LL << true; + QTest::newRow("in range hex prefix neg") + << QByteArray("-0x6A929129A5421448") << 16 << -0x6A929129A5421448LL << true; QTest::newRow("Fibonacci's last int64") << QByteArray("7540113804746346429") << 10 << 7540113804746346429LL << true; @@ -1700,6 +1719,8 @@ void tst_QByteArray::toLongLong_data() << 0xABCFFFFFFF123LL << true; QTest::newRow("trailing spaces") << QByteArray("9876543210\t\r \n") << 10 << 9876543210LL << true; + QTest::newRow("space after plus") << QByteArray("+ 12") << 10 << 0LL << false; + QTest::newRow("space after minus") << QByteArray("- 12") << 10 << 0LL << false; QTest::newRow("leading junk") << QByteArray("q12345") << 10 << 0LL << false; QTest::newRow("trailing junk") << QByteArray("abc12345t") << 16 << 0LL << false; @@ -1716,13 +1737,86 @@ void tst_QByteArray::toLongLong_data() QTest::newRow("base 3") << QByteArray("12012") << 3 << 140LL << true; QTest::newRow("neg base 3") << QByteArray("-201") << 3 << -19LL << true; - QTest::newRow("max dec") << QByteArray("9223372036854775807") << 10 << 9223372036854775807LL - << true; - QTest::newRow("mix hex") << QByteArray("-7FFFFFFFFFFFFFFF") << 16 << -0x7FFFFFFFFFFFFFFFLL - << true; - - QTest::newRow("max + 1 dec") << QByteArray("9223372036854775808") << 10 << 0LL << false; - QTest::newRow("min - 1 hex") << QByteArray("-8000000000000001") << 16 << 0LL << false; + // Boundary values, first in every base: + using LL = std::numeric_limits<qlonglong>; + for (int b = 0; b <= 36; ++b) { + if (b == 1) // bases 0 and 2 through 36 are allowed + ++b; + QTest::addRow("max base %d", b) + << QByteArray::number(LL::max(), b ? b : 10) << b << LL::max() << true; + QTest::addRow("min base %d", b) + << QByteArray::number(LL::min(), b ? b : 10) << b << LL::min() << true; + } + // Check leading zeros don't hit any buffer-too-big problems: + QTest::newRow("many-0 max dec") + << (QByteArray(512, '0') + QByteArray::number(LL::max())) << 10 << LL::max() << true; + + // Special bases (and let's include some leading space, too !), first decimal: + QTest::newRow("max dec, base 0") << QByteArray::number(LL::max()) << 0 << LL::max() << true; + QTest::newRow("max space dec") + << ("\t\r\n\f\v " + QByteArray::number(LL::max())) << 10 << LL::max() << true; + QTest::newRow("max space dec, base 0") + << ("\t\r\n\f\v " + QByteArray::number(LL::max())) << 0 << LL::max() << true; + QTest::newRow("min dec, base 0") << QByteArray::number(LL::min()) << 0 << LL::min() << true; + QTest::newRow("min space dec") + << ("\t\r\n\f\v " + QByteArray::number(LL::min())) << 10 << LL::min() << true; + QTest::newRow("min space dec, base 0") + << ("\t\r\n\f\v " + QByteArray::number(LL::min())) << 0 << LL::min() << true; + + // Hex with prefix: + QTest::newRow("max 0x base 0") + << ("0x" + QByteArray::number(LL::max(), 16)) << 0 << LL::max() << true; + QTest::newRow("max +0x base 0") + << ("+0x" + QByteArray::number(LL::max(), 16)) << 0 << LL::max() << true; + QTest::newRow("max space 0x base 0") + << ("\t\r\n\f\v 0x" + QByteArray::number(LL::max(), 16)) << 0 << LL::max() << true; + QTest::newRow("max space +0x base 0") + << ("\t\r\n\f\v +0x" + QByteArray::number(LL::max(), 16)) << 0 << LL::max() << true; + QByteArray big = QByteArray::number(LL::min(), 16); + big.insert(1, "0x"); // after sign + QTest::newRow("min hex prefix") << big << 16 << LL::min() << true; + QTest::newRow("min 0x base 0") << big << 0 << LL::min() << true; + big.prepend("\t\r\n\f\v "); + QTest::newRow("min space hex prefix") << big << 16 << LL::min() << true; + QTest::newRow("min space 0x base 0") << big << 0 << LL::min() << true; + + // Octal with prefix: + QTest::newRow("max octal base 0") + << ('0' + QByteArray::number(LL::max(), 8)) << 0 << LL::max() << true; + QTest::newRow("max +octal base 0") + << ("+0" + QByteArray::number(LL::max(), 8)) << 0 << LL::max() << true; + QTest::newRow("max space octal base 0") + << ("\t\r\n\f\v 0" + QByteArray::number(LL::max(), 8)) << 0 << LL::max() << true; + QTest::newRow("max space +octal base 0") + << ("\t\r\n\f\v +0" + QByteArray::number(LL::max(), 8)) << 0 << LL::max() << true; + big = QByteArray::number(LL::min(), 8); + big.insert(1, '0'); // after sign + QTest::newRow("min octal prefix") << big << 8 << LL::min() << true; + QTest::newRow("min octal base 0") << big << 0 << LL::min() << true; + big.prepend("\t\r\n\f\v "); + QTest::newRow("min space octal prefix") << big << 8 << LL::min() << true; + QTest::newRow("min space octal base 0") << big << 0 << LL::min() << true; + + // Values *just* out of range: + QTest::newRow("max + 1 dec") << decNext(QByteArray::number(LL::max())) << 10 << 0LL << false; + QTest::newRow("max + 1 dec base 0") + << decNext(QByteArray::number(LL::max())) << 0 << 0LL << false; + QTest::newRow("min - 1 dec") << decNext(QByteArray::number(LL::min())) << 10 << 0LL << false; + QTest::newRow("min - 1 dec base 0") + << decNext(QByteArray::number(LL::min())) << 0 << 0LL << false; + // For hex and octal, we know the last digit of min is 0 and skipping its sign gets max+1: + big = QByteArray::number(LL::min(), 8); + QTest::newRow("max + 1 oct") << big.sliced(1) << 8 << 0LL << false; + big[big.size() - 1] = '1'; + QTest::newRow("min - 1 oct") << big << 8 << 0LL << false; + big.insert(1, '0'); // after minus sign + QTest::newRow("min - 1 octal base 0") << big << 0 << 0LL << false; + big = QByteArray::number(LL::min(), 16); + QTest::newRow("max + 1 hex") << big.sliced(1) << 16 << 0LL << false; + big[big.size() - 1] = '1'; + QTest::newRow("min - 1 hex") << big << 16 << 0LL << false; + big.insert(1, "0x"); // after minus sign + QTest::newRow("min - 1, 0x base 0") << big << 0 << 0LL << false; } void tst_QByteArray::toLongLong() @@ -1749,14 +1843,81 @@ void tst_QByteArray::toULongLong_data() QTest::addColumn<qulonglong>("result"); QTest::addColumn<bool>("ok"); - QTest::newRow("null") << QByteArray() << 10 << (qulonglong)0 << false; - QTest::newRow("empty") << QByteArray("") << 10 << (qulonglong)0 << false; - QTest::newRow("out of base bound") << QByteArray("c") << 10 << (qulonglong)0 << false; - - QTest::newRow("leading spaces") << QByteArray(" \n\r\t100") << 10 << qulonglong(100) << true; - QTest::newRow("trailing spaces") << QByteArray("100 \n\r\t") << 10 << qulonglong(100) << true; - QTest::newRow("leading junk") << QByteArray("x100") << 10 << qulonglong(0) << false; - QTest::newRow("trailing junk") << QByteArray("100x") << 10 << qulonglong(0) << false; + QTest::newRow("null") << QByteArray() << 10 << 0ULL << false; + QTest::newRow("empty") << QByteArray("") << 10 << 0ULL << false; + QTest::newRow("out of base bound") << QByteArray("c") << 10 << 0ULL << false; + + QTest::newRow("in range dec") + << QByteArray("7679359922672374856") << 10 << 7679359922672374856ULL << true; + QTest::newRow("in range hex") + << QByteArray("6A929129A5421448") << 16 << 0x6A929129A5421448ULL << true; + QTest::newRow("in range hex prefix") + << QByteArray("0x6A929129A5421448") << 16 << 0x6A929129A5421448ULL << true; + + QTest::newRow("leading spaces") << QByteArray(" \n\r\t100") << 10 << 100ULL << true; + QTest::newRow("trailing spaces") << QByteArray("100 \n\r\t") << 10 << 100ULL << true; + QTest::newRow("leading plus") << QByteArray("+100") << 10 << 100ULL << true; + QTest::newRow("space after plus") << QByteArray("+ 12") << 10 << 0ULL << false; + QTest::newRow("leading minus") << QByteArray("-100") << 10 << 0ULL << false; + QTest::newRow("leading junk") << QByteArray("x100") << 10 << 0ULL << false; + QTest::newRow("trailing junk") << QByteArray("100x") << 10 << 0ULL << false; + + QTest::newRow("dec, base 0") << QByteArray("9876543210") << 0 << 9876543210ULL << true; + QTest::newRow("hex, base 0") << QByteArray("0x9876543210") << 0 << 0x9876543210ULL << true; + QTest::newRow("oct, base 0") << QByteArray("07654321234567") << 0 << 07654321234567ULL << true; + QTest::newRow("base 3") << QByteArray("12012") << 3 << 140ULL << true; + + // Boundary values, first in every base: + using ULL = std::numeric_limits<qulonglong>; + for (int b = 0; b <= 36; ++b) { + if (b == 1) // bases 0 and 2 through 36 are allowed + ++b; + QTest::addRow("max base %d", b) + << QByteArray::number(ULL::max(), b ? b : 10) << b << ULL::max() << true; + } + // Check leading zeros don't hit any buffer-too-big problems: + QTest::newRow("many-0 max dec") + << (QByteArray(512, '0') + QByteArray::number(ULL::max())) << 10 << ULL::max() << true; + + // Special bases (and let's include some leading space, too !), first decimal: + QTest::newRow("max dec, base 0") << QByteArray::number(ULL::max()) << 0 << ULL::max() << true; + QTest::newRow("max space dec") + << ("\t\r\n\f\v " + QByteArray::number(ULL::max())) << 10 << ULL::max() << true; + QTest::newRow("max space dec, base 0") + << ("\t\r\n\f\v " + QByteArray::number(ULL::max())) << 0 << ULL::max() << true; + + // Hex with prefix: + QTest::newRow("max 0x base 0") + << ("0x" + QByteArray::number(ULL::max(), 16)) << 0 << ULL::max() << true; + QTest::newRow("max +0x base 0") + << ("+0x" + QByteArray::number(ULL::max(), 16)) << 0 << ULL::max() << true; + QTest::newRow("max space 0x base 0") + << ("\t\r\n\f\v 0x" + QByteArray::number(ULL::max(), 16)) << 0 << ULL::max() << true; + QTest::newRow("max space +0x base 0") + << ("\t\r\n\f\v +0x" + QByteArray::number(ULL::max(), 16)) << 0 << ULL::max() << true; + + // Octal with prefix: + QTest::newRow("max octal base 0") + << ('0' + QByteArray::number(ULL::max(), 8)) << 0 << ULL::max() << true; + QTest::newRow("max +octal base 0") + << ("+0" + QByteArray::number(ULL::max(), 8)) << 0 << ULL::max() << true; + QTest::newRow("max space octal base 0") + << ("\t\r\n\f\v 0" + QByteArray::number(ULL::max(), 8)) << 0 << ULL::max() << true; + QTest::newRow("max space +octal base 0") + << ("\t\r\n\f\v +0" + QByteArray::number(ULL::max(), 8)) << 0 << ULL::max() << true; + + // Values *just* out of range: + QTest::newRow("max + 1 dec") << decNext(QByteArray::number(ULL::max())) << 10 << 0ULL << false; + QTest::newRow("max + 1 dec base 0") + << decNext(QByteArray::number(ULL::max())) << 0 << 0ULL << false; + auto big = QByteArray::number(ULL::max(), 8).replace('7', '0'); + // Number of bits is a power of two, so not a multiple of three; so (only) + // first digit of max wasn't 7: + big[0] += 1; + QTest::newRow("max + 1 oct") << big << 8 << 0ULL << false; + // Number of bits is a multiple of four, so every digit of max is 'f'. + big = '1' + QByteArray::number(ULL::max(), 16).replace('f', '0'); + QTest::newRow("max + 1 hex") << big << 16 << 0ULL << false; } void tst_QByteArray::toULongLong() diff --git a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp index 89cb46163a..631ca9ff0a 100644 --- a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp @@ -1319,6 +1319,9 @@ void tst_QLocale::long_long_conversion_data() QTest::newRow("C 12345,67") << QString("C") << "12345,67" << false << (qlonglong) 0; QTest::newRow("C 123456,7") << QString("C") << "123456,7" << false << (qlonglong) 0; QTest::newRow("C 1,234,567") << QString("C") << "1,234,567" << true << (qlonglong) 1234567; + using LL = std::numeric_limits<qlonglong>; + QTest::newRow("C LLONG_MIN") << QString("C") << QString::number(LL::min()) << true << LL::min(); + QTest::newRow("C LLONG_MAX") << QString("C") << QString::number(LL::max()) << true << LL::max(); QTest::newRow("de_DE 1") << QString("de_DE") << "1" << true << (qlonglong) 1; QTest::newRow("de_DE 1.") << QString("de_DE") << "1." << false << (qlonglong) 0; diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index c2a7b93c7d..a377834025 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -3897,7 +3897,7 @@ void tst_QString::toLong() void tst_QString::toULongLong() { QString str; - bool ok; + bool ok = true; QCOMPARE(str.toULongLong(), Q_UINT64_C(0)); QCOMPARE(str.toULongLong(&ok), Q_UINT64_C(0)); @@ -3917,6 +3917,15 @@ void tst_QString::toULongLong() QCOMPARE( str.toULongLong( 0 ), Q_UINT64_C(0) ); QCOMPARE( str.toULongLong( &ok ), Q_UINT64_C(0) ); QVERIFY( !ok ); + + // Check limits round-trip in every base: + using ULL = std::numeric_limits<qulonglong>; + for (int b = 0; b <= 36; ++b) { + if (b == 1) // 0 and 2 through 36 are valid bases + ++b; + QCOMPARE(QString::number(ULL::max(), b ? b : 10).toULongLong(&ok, b), ULL::max()); + QVERIFY(ok); + } } void tst_QString::toLongLong() @@ -3969,6 +3978,71 @@ void tst_QString::toLongLong() } } } + + // Check bounds. + // First in every base, with no prefix: + using LL = std::numeric_limits<qlonglong>; + for (int b = 0; b <= 36; ++b) { + if (b == 1) // 0 and 2 through 36 are valid bases + ++b; + QCOMPARE(QString::number(LL::max(), b ? b : 10).toLongLong(&ok, b), LL::max()); + QVERIFY(ok); + QCOMPARE(QString::number(LL::min(), b ? b : 10).toLongLong(&ok, b), LL::min()); + QVERIFY(ok); + } + + // Then in base 16 or 0 with 0x prefix: + auto big = QString::number(LL::min(), 16); + big.insert(1, u"0x"); // after the minus sign + big.prepend(u"\t\r\n\f\v "); + QCOMPARE(big.toLongLong(&ok, 16), LL::min()); + QVERIFY(ok); + QCOMPARE(big.toLongLong(&ok, 0), LL::min()); + QVERIFY(ok); + big = QString::number(LL::max(), 16); + big.prepend(u"\t\r\n\f\v 0x"); + QCOMPARE(big.toLongLong(&ok, 16), LL::max()); + QVERIFY(ok); + QCOMPARE(big.toLongLong(&ok, 0), LL::max()); + QVERIFY(ok); + big.insert(6, u'+'); + QCOMPARE(big.toLongLong(&ok, 16), LL::max()); + QVERIFY(ok); + QCOMPARE(big.toLongLong(&ok, 0), LL::max()); + QVERIFY(ok); + + // Next octal: + big = QString::number(LL::min(), 8); + big.insert(1, u'0'); // after the minus sign + big.prepend(u"\t\r\n\f\v "); + QCOMPARE(big.toLongLong(&ok, 8), LL::min()); + QVERIFY(ok); + QCOMPARE(big.toLongLong(&ok, 0), LL::min()); + QVERIFY(ok); + big = QString::number(LL::max(), 8); + big.prepend(u"\t\r\n\f\v 0"); + QCOMPARE(big.toLongLong(&ok, 8), LL::max()); + QVERIFY(ok); + QCOMPARE(big.toLongLong(&ok, 0), LL::max()); + QVERIFY(ok); + big.insert(6, u'+'); + QCOMPARE(big.toLongLong(&ok, 8), LL::max()); + QVERIFY(ok); + QCOMPARE(big.toLongLong(&ok, 0), LL::max()); + QVERIFY(ok); + + // Finally decimal for base 0: + big = QString::number(LL::min(), 10); + big.prepend(u"\t\r\n\f\v "); + QCOMPARE(big.toLongLong(&ok, 0), LL::min()); + QVERIFY(ok); + big = QString::number(LL::max(), 10); + big.prepend(u"\t\r\n\f\v "); + QCOMPARE(big.toLongLong(&ok, 0), LL::max()); + QVERIFY(ok); + big.insert(6, u'+'); + QCOMPARE(big.toLongLong(&ok, 0), LL::max()); + QVERIFY(ok); } //////////////////////////////////////////////////////////////////////////// |