From eb0ff770e29715deb8b2e6f5da736e0c1e8f8d07 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Wed, 24 Jun 2020 11:45:01 +0100 Subject: [PATCH] libstdc++: Fix std::from_chars to ignore leading zeros in base 2 The parser for binary numbers returned an error if the entire string contains more digits than the result type. Leading zeros should be ignored. libstdc++-v3/ChangeLog: * include/std/charconv (__from_chars_binary): Ignore leading zeros. * testsuite/20_util/from_chars/1.cc: Check "0x1" for all bases, not just 10 and 16. * testsuite/20_util/from_chars/3.cc: New test. --- libstdc++-v3/include/std/charconv | 8 +- .../testsuite/20_util/from_chars/1.cc | 19 +++-- .../testsuite/20_util/from_chars/3.cc | 79 +++++++++++++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/from_chars/3.cc diff --git a/libstdc++-v3/include/std/charconv b/libstdc++-v3/include/std/charconv index 8fbf64058ee..b725e5d2afd 100644 --- a/libstdc++-v3/include/std/charconv +++ b/libstdc++-v3/include/std/charconv @@ -417,7 +417,11 @@ namespace __detail static_assert(is_unsigned<_Tp>::value, "implementation bug"); const ptrdiff_t __len = __last - __first; - int __i = 0; + ptrdiff_t __i = 0; + while (__i < __len && __first[__i] == '0') + ++__i; + const ptrdiff_t __leading_zeroes = __i; + while (__i < __len) { const unsigned char __c = (unsigned)__first[__i] - '0'; @@ -428,7 +432,7 @@ namespace __detail __i++; } __first += __i; - return __i <= __detail::__int_limits<_Tp>::digits; + return (__i - __leading_zeroes) <= __detail::__int_limits<_Tp>::digits; } /// std::from_chars implementation for integers in bases 3 to 10. diff --git a/libstdc++-v3/testsuite/20_util/from_chars/1.cc b/libstdc++-v3/testsuite/20_util/from_chars/1.cc index 916025bc7c6..ad5d50e67b3 100644 --- a/libstdc++-v3/testsuite/20_util/from_chars/1.cc +++ b/libstdc++-v3/testsuite/20_util/from_chars/1.cc @@ -31,7 +31,8 @@ check_from_chars(I expected, std::string s, int base = 0, char term = '\0') std::from_chars_result r = base == 0 ? std::from_chars(begin, end, val) : std::from_chars(begin, end, val, base); - return r.ec == std::errc{} && (r.ptr == end || *r.ptr == term) && val == expected; + return r.ec == std::errc{} && (r.ptr == end || *r.ptr == term) + && val == expected; } #include @@ -52,10 +53,18 @@ void test02() { // "0x" parsed as "0" not as hex prefix: - VERIFY( check_from_chars(0, "0x1", 10, 'x') ); - VERIFY( check_from_chars(0, "0X1", 10, 'X') ); - VERIFY( check_from_chars(0, "0x1", 16, 'x') ); - VERIFY( check_from_chars(0, "0X1", 16, 'X') ); + for (int base = 2; base < 34; ++base) + { + VERIFY( check_from_chars(0, "0x1", base, 'x') ); + VERIFY( check_from_chars(0, "0X1", base, 'X') ); + } + + VERIFY( check_from_chars(1123, "0x1", 34) ); + VERIFY( check_from_chars(1123, "0X1", 34) ); + VERIFY( check_from_chars(1156, "0x1", 35) ); + VERIFY( check_from_chars(1156, "0X1", 35) ); + VERIFY( check_from_chars(1189, "0x1", 36) ); + VERIFY( check_from_chars(1189, "0X1", 36) ); VERIFY( check_from_chars(1155, "xx", 34) ); VERIFY( check_from_chars(1155, "XX", 34) ); diff --git a/libstdc++-v3/testsuite/20_util/from_chars/3.cc b/libstdc++-v3/testsuite/20_util/from_chars/3.cc new file mode 100644 index 00000000000..9d4a77f5c31 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/from_chars/3.cc @@ -0,0 +1,79 @@ +// Copyright (C) 2020 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-do run { target c++14 } } + +#include +#include +#include + +#ifdef DEBUG +#include +#endif + +long long +read(const char* first, const char* last, int base) +{ + long long val = 0; + long long place = 1; + while (last > first) + { + val += (*--last - '0') * place; + place *= base; + } + return val; +} + +void +test01() +{ + std::from_chars_result res; + long long val; + for (auto s : { "10001", "10010", "10011", "10101", "10110", "10111", + "11001", "11010", "11011", "11101", "11110", "11111" }) + { + std::string ss[2] = { s, std::string(64, '0') + s }; + for (const auto& str : ss) + { + const char* first = str.data(); + for (int base = 2; base < 37; ++base) + { + const char* last = str.data() + str.length(); + for (size_t n = 0; n < ss[0].length(); ++n) + { +#ifdef DEBUG + printf("Parsing \"%.*s\" in base %d\n", int(last - first), first, + base); +#endif + res = std::from_chars(first, last, val, base); + VERIFY( res.ptr == last ); + VERIFY( res.ec == std::errc{} ); + VERIFY( val == read(first, last, base) ); + // Test again with shorter string to check from_chars doesn't read + // the digits past the last pointer. + --last; + } + } + } + } +} + +int +main() +{ + test01(); +} -- 2.30.2