libstdc++: Fix std::from_chars to ignore leading zeros in base 2
authorJonathan Wakely <jwakely@redhat.com>
Wed, 24 Jun 2020 10:45:01 +0000 (11:45 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Wed, 24 Jun 2020 11:35:59 +0000 (12:35 +0100)
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
libstdc++-v3/testsuite/20_util/from_chars/1.cc
libstdc++-v3/testsuite/20_util/from_chars/3.cc [new file with mode: 0644]

index 8fbf64058ee47a047a64ff05a0e254518271beb0..b725e5d2afd4ba5741b8e83ee328400e544e7e48 100644 (file)
@@ -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.
index 916025bc7c6f6d5e623cf4be37d2d8c4ecb636bb..ad5d50e67b3984d0d4abe82e2461c296848da2b4 100644 (file)
@@ -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 <climits>
@@ -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 (file)
index 0000000..9d4a77f
--- /dev/null
@@ -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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do run { target c++14 } }
+
+#include <charconv>
+#include <string>
+#include <testsuite_hooks.h>
+
+#ifdef DEBUG
+#include <stdio.h>
+#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();
+}