libstdc++: Fix __numeric_traits_integer<__int20> [PR 97798]
authorJonathan Wakely <jwakely@redhat.com>
Thu, 12 Nov 2020 10:29:21 +0000 (10:29 +0000)
committerJonathan Wakely <jwakely@redhat.com>
Thu, 12 Nov 2020 12:10:10 +0000 (12:10 +0000)
The expression used to calculate the maximum value for an integer type
assumes that the number of bits in the value representation is always
sizeof(T) * CHAR_BIT. This is not true for the __int20 type on msp430,
which has only 20 bits in the value representation but 32 bits in the
object representation. This causes an integer overflow in a constant
expression, which is ill-formed.

This problem was already solved by DJ for std::numeric_limits<__int20>
by generalizing the helper macros to use a specified number of bits
instead of assuming sizeof(T) * CHAR_BIT. Then the INT_N_n types can
specify the number of bits using the __GLIBCXX_BITSIZE_INT_N_n macros
that the compiler defines.

I'm using a slightly different approach here. I've replaced the helper
macros entirely, and just expanded the calculations in the initializers
for the static data members. By reordering the data members we can reuse
__is_signed and __digits in the other initializers. This removes the
repetition of expanding __glibcxx_signed(T) and __glibcxx_digits(T)
multiple times in each initializer.

The __is_integer_nonstrict trait now defines a new constant, __width,
which is sizeof(T) * CHAR_BIT by default (defined as an enumerator so
that no storage is needed for a static data member). By specializing
__is_integer_nonstrict for the INT_N types that have padding bits, we
can provide the correct width via the __GLIBCXX_BITSIZE_INT_N_n macros.

libstdc++-v3/ChangeLog:

PR libstdc++/97798
* include/ext/numeric_traits.h (__glibcxx_signed)
(__glibcxx_digits, __glibcxx_min, __glibcxx_max): Remove
macros.
(__is_integer_nonstrict::__width): Define new constant.
(__numeric_traits_integer): Define constants in terms of each
other and __is_integer_nonstrict::__width, rather than the
removed macros.
(_GLIBCXX_INT_N_TRAITS): Macro to define explicit
specializations for non-standard integer types.

libstdc++-v3/include/ext/numeric_traits.h

index 585ecc0ba9f5e1149be93f79629eba58a506dfab..c29f9f21d1aaa981059bd3615971152c52f5bc11 100644 (file)
@@ -39,31 +39,23 @@ namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   // Compile time constants for builtin types.
-  // In C++98 std::numeric_limits member functions cannot be used for this.
-#define __glibcxx_signed(_Tp) ((_Tp)(-1) < 0)
-#define __glibcxx_digits(_Tp) \
-  (sizeof(_Tp) * __CHAR_BIT__ - __glibcxx_signed(_Tp))
-
-#define __glibcxx_min(_Tp) \
-  (__glibcxx_signed(_Tp) ? -__glibcxx_max(_Tp) - 1 : (_Tp)0)
-
-#define __glibcxx_max(_Tp) \
-  (__glibcxx_signed(_Tp) ? \
-   (((((_Tp)1 << (__glibcxx_digits(_Tp) - 1)) - 1) << 1) + 1) : ~(_Tp)0)
-
+  // In C++98 std::numeric_limits member functions are not constant expressions
+  // (that changed in C++11 with the addition of 'constexpr').
+  // Even for C++11, this header is smaller than <limits> and can be used
+  // when only is_signed, digits, min, or max values are needed for integers,
+  // or is_signed, digits10, max_digits10, or max_exponent10 for floats.
+
+  // Unlike __is_integer (and std::is_integral) this trait is true for
+  // non-standard built-in integer types such as __int128 and __int20.
   template<typename _Tp>
     struct __is_integer_nonstrict
     : public std::__is_integer<_Tp>
-    { };
-
-#if defined __STRICT_ANSI__ && defined __SIZEOF_INT128__
-  // __is_integer<__int128> is false, but we still want to allow it here.
-  template<> struct __is_integer_nonstrict<__int128>
-  { enum { __value = 1 }; typedef std::__true_type __type; };
+    {
+      using std::__is_integer<_Tp>::__value;
 
-  template<> struct __is_integer_nonstrict<unsigned __int128>
-  { enum { __value = 1 }; typedef std::__true_type __type; };
-#endif
+      // The number of bits in the value representation.
+      enum { __width = __value ? sizeof(_Tp) * __CHAR_BIT__ : 0 };
+    };
 
   template<typename _Value>
     struct __numeric_traits_integer
@@ -73,14 +65,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    "invalid specialization");
 #endif
 
-      // Only integers for initialization of member constant.
-      static const _Value __min = __glibcxx_min(_Value);
-      static const _Value __max = __glibcxx_max(_Value);
-
-      // NB: these two also available in std::numeric_limits as compile
-      // time constants, but <limits> is big and we avoid including it.
-      static const bool __is_signed = __glibcxx_signed(_Value);
-      static const int __digits = __glibcxx_digits(_Value);      
+      // NB: these two are also available in std::numeric_limits as compile
+      // time constants, but <limits> is big and we can avoid including it.
+      static const bool __is_signed = (_Value)(-1) < 0;
+      static const int __digits
+       = __is_integer_nonstrict<_Value>::__width - __is_signed;
+
+      // The initializers must be constants so that __max and __min are too.
+      static const _Value __max = __is_signed
+       ? (((((_Value)1 << (__digits - 1)) - 1) << 1) + 1)
+       : ~(_Value)0;
+      static const _Value __min = __is_signed ? -__max - 1 : (_Value)0;
     };
 
   template<typename _Value>
@@ -95,16 +90,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _Value>
     const int __numeric_traits_integer<_Value>::__digits;
 
+  // Enable __numeric_traits_integer for types where the __is_integer_nonstrict
+  // primary template doesn't give the right answer.
+#define _GLIBCXX_INT_N_TRAITS(T, WIDTH)                        \
+  template<> struct __is_integer_nonstrict<T>          \
+  {                                                    \
+    enum { __value = 1 };                              \
+    typedef std::__true_type __type;                   \
+    enum { __width = WIDTH };                          \
+  };                                                   \
+  template<> struct __is_integer_nonstrict<unsigned T> \
+  {                                                    \
+    enum { __value = 1 };                              \
+    typedef std::__true_type __type;                   \
+    enum { __width = WIDTH };                          \
+  };
+
+  // We need to specify the width for some __intNN types because they
+  // have padding bits, e.g. the object representation of __int20 has 32 bits,
+  // but its width (number of bits in the value representation) is only 20.
+#if defined __GLIBCXX_TYPE_INT_N_0 && __GLIBCXX_BITSIZE_INT_N_0 % __CHAR_BIT__
+  _GLIBCXX_INT_N_TRAITS(__GLIBCXX_TYPE_INT_N_0, __GLIBCXX_BITSIZE_INT_N_0)
+#endif
+#if defined __GLIBCXX_TYPE_INT_N_1 && __GLIBCXX_BITSIZE_INT_N_1 % __CHAR_BIT__
+  _GLIBCXX_INT_N_TRAITS(__GLIBCXX_TYPE_INT_N_1, __GLIBCXX_BITSIZE_INT_N_1)
+#endif
+#if defined __GLIBCXX_TYPE_INT_N_2 && __GLIBCXX_BITSIZE_INT_N_2 % __CHAR_BIT__
+  _GLIBCXX_INT_N_TRAITS(__GLIBCXX_TYPE_INT_N_2, __GLIBCXX_BITSIZE_INT_N_2)
+#endif
+#if defined __GLIBCXX_TYPE_INT_N_3 && __GLIBCXX_BITSIZE_INT_N_3 % __CHAR_BIT__
+  _GLIBCXX_INT_N_TRAITS(__GLIBCXX_TYPE_INT_N_3, __GLIBCXX_BITSIZE_INT_N_3)
+#endif
+
+#if defined __STRICT_ANSI__ && defined __SIZEOF_INT128__
+  // In strict modes __is_integer<__int128> is false,
+  // but we still want to define __numeric_traits_integer<__int128>.
+  _GLIBCXX_INT_N_TRAITS(__int128, 128)
+#endif
+
+#undef _GLIBCXX_INT_N_TRAITS
+
 #if __cplusplus >= 201103L
+  /// Convenience alias for __numeric_traits<integer-type>.
   template<typename _Tp>
     using __int_traits = __numeric_traits_integer<_Tp>;
 #endif
 
-#undef __glibcxx_signed
-#undef __glibcxx_digits
-#undef __glibcxx_min
-#undef __glibcxx_max
-
 #define __glibcxx_floating(_Tp, _Fval, _Dval, _LDval) \
   (std::__are_same<_Tp, float>::__value ? _Fval \
    : std::__are_same<_Tp, double>::__value ? _Dval : _LDval)
@@ -120,10 +151,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __glibcxx_floating(_Tp, __FLT_MAX_10_EXP__, __DBL_MAX_10_EXP__, \
                     __LDBL_MAX_10_EXP__)
 
+  // N.B. this only supports float, double and long double (no __float128 etc.)
   template<typename _Value>
     struct __numeric_traits_floating
     {
-      // Only floating point types. See N1822. 
+      // Only floating point types. See N1822.
       static const int __max_digits10 = __glibcxx_max_digits10(_Value);
 
       // See above comment...
@@ -159,4 +191,4 @@ _GLIBCXX_END_NAMESPACE_VERSION
 #undef __glibcxx_digits10
 #undef __glibcxx_max_exponent10
 
-#endif 
+#endif