Fix PR36914 - num_get::get(unsigned) incorrectly handles negative numbers.
authorEric Fiselier <eric@efcs.ca>
Thu, 29 Mar 2018 01:18:53 +0000 (01:18 +0000)
committerEric Fiselier <eric@efcs.ca>
Thu, 29 Mar 2018 01:18:53 +0000 (01:18 +0000)
This patch corrects num_get for unsigned types to support strings
with a leading `-` character. According to the standard the
number should be parsed as an unsigned integer and then
negated.

llvm-svn: 328751

libcxx/include/locale
libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/facet.num.get.members/test_min_max.pass.cpp
libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/facet.num.get.members/test_neg_one.pass.cpp [new file with mode: 0644]

index a86645d..52885b7 100644 (file)
@@ -768,10 +768,10 @@ __num_get_unsigned_integral(const char* __a, const char* __a_end,
 {
     if (__a != __a_end)
     {
-        if (*__a == '-')
-        {
-            __err = ios_base::failbit;
-            return 0;
+        const bool __negate = *__a == '-';
+        if (__negate && ++__a == __a_end) {
+          __err = ios_base::failbit;
+          return 0;
         }
         typename remove_reference<decltype(errno)>::type __save_errno = errno;
         errno = 0;
@@ -785,13 +785,14 @@ __num_get_unsigned_integral(const char* __a, const char* __a_end,
             __err = ios_base::failbit;
             return 0;
         }
-        else if (__current_errno == ERANGE ||
-                 numeric_limits<_Tp>::max() < __ll)
+        else if (__current_errno == ERANGE || numeric_limits<_Tp>::max() < __ll)
         {
             __err = ios_base::failbit;
             return numeric_limits<_Tp>::max();
         }
-        return static_cast<_Tp>(__ll);
+        _Tp __res = static_cast<_Tp>(__ll);
+        if (__negate) __res = -__res;
+        return __res;
     }
     __err = ios_base::failbit;
     return 0;
index 6ba5a89..e2218ff 100644 (file)
 
 using namespace std;
 
+template <class T>
+bool check_stream_failed(std::string const& val) {
+    istringstream ss(val);
+    T result;
+    return !(ss >> result);
+}
+
 template<typename T>
 void check_limits()
 {
+    const bool is_unsigned = std::is_unsigned<T>::value;
     T minv = numeric_limits<T>::min();
     T maxv = numeric_limits<T>::max();
 
@@ -36,17 +44,12 @@ void check_limits()
     assert(new_minv == minv);
     assert(new_maxv == maxv);
 
-    if(mins == "0")
-        mins = "-1";
-    else
-        mins[mins.size() - 1]++;
-
     maxs[maxs.size() - 1]++;
-
-    istringstream maxoss2(maxs), minoss2(mins);
-
-    assert(! (maxoss2 >> new_maxv));
-    assert(! (minoss2 >> new_minv));
+    assert(check_stream_failed<T>(maxs));
+    if (!is_unsigned) {
+        mins[mins.size() - 1]++;
+        assert(check_stream_failed<T>(mins));
+    }
 }
 
 int main(void)
diff --git a/libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/facet.num.get.members/test_neg_one.pass.cpp b/libcxx/test/std/localization/locale.categories/category.numeric/locale.num.get/facet.num.get.members/test_neg_one.pass.cpp
new file mode 100644 (file)
index 0000000..fa0641f
--- /dev/null
@@ -0,0 +1,159 @@
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// <locale>
+
+// class num_get<charT, InputIterator>
+
+// iter_type get(iter_type in, iter_type end, ios_base&,
+//               ios_base::iostate& err, unsigned int& v) const;
+
+#include <locale>
+#include <ios>
+#include <cassert>
+#include <streambuf>
+#include <sstream>
+#include <iostream>
+#include "test_iterators.h"
+
+typedef std::num_get<char, input_iterator<const char*> > F;
+
+class my_facet
+    : public F
+{
+public:
+    explicit my_facet(std::size_t refs = 0)
+        : F(refs) {}
+};
+
+template <class T>
+std::string make_neg_string(T value) {
+  std::ostringstream ss;
+  assert(ss << value);
+  std::string res = ss.str();
+  return '-' + res;
+}
+
+template <class T>
+void test_neg_one() {
+    const my_facet f(1);
+    std::ios ios(0);
+    T v = static_cast<T>(42);
+    {
+        const char str[] = "-1";
+        std::ios_base::iostate err = ios.goodbit;
+        input_iterator<const char*> iter =
+            f.get(input_iterator<const char*>(str),
+                  input_iterator<const char*>(str+sizeof(str)),
+                  ios, err, v);
+        assert(iter.base() == str+sizeof(str)-1);
+        assert(err == ios.goodbit);
+        assert(v == T(-1));
+    }
+    v = 42;
+    {
+        const char str[] = "-";
+        std::ios_base::iostate err = ios.goodbit;
+        input_iterator<const char*> iter =
+            f.get(input_iterator<const char*>(str),
+                  input_iterator<const char*>(str+sizeof(str)),
+                  ios, err, v);
+        assert(iter.base() == str+sizeof(str)-1);
+        assert(err == ios.failbit);
+        assert(v == 0);
+    }
+}
+
+template <class T>
+void test_negate() {
+    typedef typename std::make_signed<T>::type SignedT;
+    const my_facet f(1);
+    std::ios ios(0);
+    T v = 42;
+    {
+        T value = std::numeric_limits<SignedT>::max();
+        ++value;
+        std::string std_str = make_neg_string(value);
+        const char* str = std_str.data();
+        size_t size = std_str.size();
+        std::ios_base::iostate err = ios.goodbit;
+        input_iterator<const char*> iter =
+            f.get(input_iterator<const char*>(str),
+                  input_iterator<const char*>(str+size+1),
+                  ios, err, v);
+        assert(iter.base() == str+size);
+        assert(err == ios.goodbit);
+        T expected = -value;
+        assert(v == expected);
+    }
+    v = 42;
+    {
+        T value = std::numeric_limits<SignedT>::max();
+        ++value;
+        ++value;
+        std::string std_str = make_neg_string(value);
+        const char* str = std_str.data();
+        size_t size = std_str.size();
+        std::ios_base::iostate err = ios.goodbit;
+        input_iterator<const char*> iter =
+            f.get(input_iterator<const char*>(str),
+                  input_iterator<const char*>(str+size+1),
+                  ios, err, v);
+        assert(iter.base() == str+size);
+        assert(err == ios.goodbit);
+        T expected = -value;
+        assert(v == expected);
+    }
+    v = 42;
+    {
+        T value = std::numeric_limits<T>::max();
+        std::string std_str = make_neg_string(value);
+        const char* str = std_str.data();
+        size_t size = std_str.size();
+        std::ios_base::iostate err = ios.goodbit;
+        input_iterator<const char*> iter =
+            f.get(input_iterator<const char*>(str),
+                  input_iterator<const char*>(str+size+1),
+                  ios, err, v);
+        assert(iter.base() == str+size);
+        assert(err == ios.goodbit);
+        T expected = -value;
+        assert(v == expected);
+    }
+    v = 42;
+    {
+        std::string std_str = make_neg_string(std::numeric_limits<T>::max());
+        std_str.back()++;
+        const char* str = std_str.data();
+        size_t size = std_str.size();
+        std::ios_base::iostate err = ios.goodbit;
+        input_iterator<const char*> iter =
+            f.get(input_iterator<const char*>(str),
+                  input_iterator<const char*>(str+size+1),
+                  ios, err, v);
+        assert(iter.base() == str+size);
+        assert(err == ios.failbit);
+        assert(v == T(-1));
+    }
+}
+
+int main(void)
+{
+    test_neg_one<long>();
+    test_neg_one<long long>();
+    test_neg_one<unsigned short>();
+    test_neg_one<unsigned int>();
+    test_neg_one<unsigned long>();
+    test_neg_one<unsigned long long>();
+
+    test_negate<unsigned short>();
+    test_negate<unsigned int>();
+    test_negate<unsigned long>();
+    test_negate<unsigned long long>();
+}