diff mbox series

Change std::ceil2 to be undefined if the result can't be represented

Message ID 20190625094005.GA30147@redhat.com
State New
Headers show
Series Change std::ceil2 to be undefined if the result can't be represented | expand

Commit Message

Jonathan Wakely June 25, 2019, 9:40 a.m. UTC
* include/std/bit (__ceil2): Make unrepresentable results undefined,
	as per P1355R2. Add debug assertion. Perform one left shift, not two,
	so that out of range values cause undefined behaviour. Ensure that
	shift will still be undefined if left operand is promoted.
	* testsuite/26_numerics/bit/bit.pow.two/ceil2.cc: Replace checks for
	unrepresentable values with checks that they are not core constant
	expressions.
	* testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc: New test.

I'm not committing this yet, because P1355 hasn't been accepted into
the draft, but here's a patch to implement it (this reverses the
changes in r263986, and adds special handling for types that undergo
integer promotion).

The goal is that undefined shifts are detectable in three ways, even
if the type is promoted:

* In constant expressions they make the program ill-formed.
* At runtime they cause UBSan errors.
* At runtime they abort when _GLIBCXX_ASSERTIONS is defined.
commit fd8d9b7898083c8806d2cd300f78739d2afc3503
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Jun 14 13:32:39 2019 +0100

    Change std::ceil2 to be undefined if the result can't be represented
    
            * include/std/bit (__ceil2): Make unrepresentable results undefined,
            as per P1355R2. Add debug assertion. Perform one left shift, not two,
            so that out of range values cause undefined behaviour. Ensure that
            shift will still be undefined if left operand is promoted.
            * testsuite/26_numerics/bit/bit.pow.two/ceil2.cc: Replace checks for
            unrepresentable values with checks that they are not core constant
            expressions.
            * testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc: New test.

Comments

Jonathan Wakely July 22, 2019, 4:54 p.m. UTC | #1
On 25/06/19 10:40 +0100, Jonathan Wakely wrote:
>	* include/std/bit (__ceil2): Make unrepresentable results undefined,
>	as per P1355R2. Add debug assertion. Perform one left shift, not two,
>	so that out of range values cause undefined behaviour. Ensure that
>	shift will still be undefined if left operand is promoted.
>	* testsuite/26_numerics/bit/bit.pow.two/ceil2.cc: Replace checks for
>	unrepresentable values with checks that they are not core constant
>	expressions.
>	* testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc: New test.
>
>I'm not committing this yet, because P1355 hasn't been accepted into
>the draft, but here's a patch to implement it (this reverses the
>changes in r263986, and adds special handling for types that undergo
>integer promotion).

This has now been committed to trunk.

I'll backport it to gcc-9-branch soon too.


>The goal is that undefined shifts are detectable in three ways, even
>if the type is promoted:
>
>* In constant expressions they make the program ill-formed.
>* At runtime they cause UBSan errors.
>* At runtime they abort when _GLIBCXX_ASSERTIONS is defined.


>commit fd8d9b7898083c8806d2cd300f78739d2afc3503
>Author: Jonathan Wakely <jwakely@redhat.com>
>Date:   Fri Jun 14 13:32:39 2019 +0100
>
>    Change std::ceil2 to be undefined if the result can't be represented
>
>            * include/std/bit (__ceil2): Make unrepresentable results undefined,
>            as per P1355R2. Add debug assertion. Perform one left shift, not two,
>            so that out of range values cause undefined behaviour. Ensure that
>            shift will still be undefined if left operand is promoted.
>            * testsuite/26_numerics/bit/bit.pow.two/ceil2.cc: Replace checks for
>            unrepresentable values with checks that they are not core constant
>            expressions.
>            * testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc: New test.
>
>diff --git a/libstdc++-v3/include/std/bit b/libstdc++-v3/include/std/bit
>index e0c53e53756..eb0a7578b8d 100644
>--- a/libstdc++-v3/include/std/bit
>+++ b/libstdc++-v3/include/std/bit
>@@ -197,9 +197,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       constexpr auto _Nd = numeric_limits<_Tp>::digits;
>       if (__x == 0 || __x == 1)
>         return 1;
>-      const unsigned __n = _Nd - std::__countl_zero((_Tp)(__x - 1u));
>-      const _Tp __y_2 = (_Tp)1u << (__n - 1u);
>-      return __y_2 << 1u;
>+      auto __shift_exponent = _Nd - std::__countl_zero((_Tp)(__x - 1u));
>+      // If the shift exponent equals _Nd then the correct result is not
>+      // representable as a value of _Tp, and so the result is undefined.
>+      // Want that undefined behaviour to be detected in constant expressions,
>+      // by UBSan, and by debug assertions.
>+#ifdef _GLIBCXX_HAVE_BUILTIN_IS_CONSTANT_EVALUATED
>+      if (!__builtin_is_constant_evaluated())
>+	__glibcxx_assert( __shift_exponent != numeric_limits<_Tp>::digits );
>+#endif
>+      using __promoted_type = decltype(__x << 1);
>+      if _GLIBCXX17_CONSTEXPR (!is_same<__promoted_type, _Tp>::value)
>+	{
>+	  // If __x undergoes integral promotion then shifting by _Nd is
>+	  // not undefined. In order to make the shift undefined, so that
>+	  // it is diagnosed in constant expressions and by UBsan, we also
>+	  // need to "promote" the shift exponent to be too large for the
>+	  // promoted type.
>+	  const int __extra_exp = sizeof(__promoted_type) / sizeof(_Tp) / 2;
>+	  __shift_exponent |= (__shift_exponent & _Nd) << __extra_exp;
>+	}
>+      return (_Tp)1u << __shift_exponent;
>     }
>
>   template<typename _Tp>
>diff --git a/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc
>index 6ffb5f70edb..788c008129e 100644
>--- a/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc
>+++ b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc
>@@ -20,6 +20,21 @@
>
> #include <bit>
>
>+template<typename T>
>+  constexpr T max = std::numeric_limits<T>::max();
>+// Largest representable power of two (i.e. has most significant bit set)
>+template<typename T>
>+  constexpr T maxpow2 = T(1) << (std::numeric_limits<T>::digits - 1);
>+
>+// Detect whether std::ceil2(N) is a constant expression.
>+template<auto N, typename = void>
>+  struct ceil2_valid
>+  : std::false_type { };
>+
>+template<auto N>
>+  struct ceil2_valid<N, std::void_t<char[(std::ceil2(N), 1)]>>
>+  : std::true_type { };
>+
> template<typename UInt>
> constexpr auto
> test(UInt x)
>@@ -55,13 +70,18 @@ test(UInt x)
>     static_assert( std::ceil2(UInt(3) << 64) == (UInt(4) << 64) );
>   }
>
>-  constexpr UInt msb = UInt(1) << (std::numeric_limits<UInt>::digits - 1);
>+  constexpr UInt msb = maxpow2<UInt>;
>+  static_assert( ceil2_valid<msb>() );
>   static_assert( std::ceil2( msb ) == msb );
>-  // Larger values cannot be represented so the return value is unspecified,
>-  // but must still be valid in constant expressions, i.e. not undefined.
>-  static_assert( std::ceil2( UInt(msb + 1) ) != 77 );
>-  static_assert( std::ceil2( UInt(msb + 2) ) != 77 );
>-  static_assert( std::ceil2( UInt(msb + 77) ) != 77 );
>+  static_assert( std::ceil2( UInt(msb - 1) ) == msb );
>+  static_assert( std::ceil2( UInt(msb - 2) ) == msb );
>+  static_assert( std::ceil2( UInt(msb - 3) ) == msb );
>+
>+  // P1355R2: not a constant expression if the result is not representable
>+  static_assert( !ceil2_valid<UInt(msb + 1)>() );
>+  static_assert( !ceil2_valid<max<UInt>>() );
>+  static_assert( !ceil2_valid<UInt(max<UInt> - 1)>() );
>+  static_assert( !ceil2_valid<UInt(max<UInt> - 2)>() );
>
>   return true;
> }
>@@ -114,3 +134,7 @@ static_assert( test( (__GLIBCXX_TYPE_INT_N_1)0 ).did_not_match() );
> static_assert( test( (unsigned __GLIBCXX_TYPE_INT_N_2)0 ) );
> static_assert( test( (__GLIBCXX_TYPE_INT_N_2)0 ).did_not_match() );
> #endif
>+#if defined(__GLIBCXX_TYPE_INT_N_3)
>+static_assert( test( (unsigned __GLIBCXX_TYPE_INT_N_3)0 ) );
>+static_assert( test( (__GLIBCXX_TYPE_INT_N_3)0 ).did_not_match() );
>+#endif
>diff --git a/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc
>new file mode 100644
>index 00000000000..8e107065a92
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc
>@@ -0,0 +1,74 @@
>+// Copyright (C) 2019 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-options "-std=gnu++2a -D_GLIBCXX_ASSERTIONS" }
>+// { dg-do run { target c++2a } }
>+// { dg-xfail-run-if "__glibcxx_assert in ceil2 should fail" { *-*-* } }
>+
>+#include <bit>
>+
>+// P1355R2: not a constant expression if the result is not representable
>+
>+template<auto N, typename = void>
>+  struct ceil2_valid
>+  : std::false_type { };
>+
>+template<auto N>
>+  struct ceil2_valid<N, std::void_t<char[(std::ceil2(N), 1)]>>
>+  : std::true_type { };
>+
>+template<typename T>
>+  constexpr T max = std::numeric_limits<T>::max();
>+template<typename T>
>+  constexpr T maxpow2 = T(1) << (std::numeric_limits<T>::digits - 1);
>+
>+static_assert( ceil2_valid<maxpow2<unsigned char>>() );
>+static_assert( !ceil2_valid<maxpow2<unsigned char> + (unsigned char)1>() );
>+
>+static_assert( !ceil2_valid<max<unsigned char>>() );
>+static_assert( !ceil2_valid<max<unsigned char> - (unsigned char)1>() );
>+
>+static_assert( ceil2_valid<maxpow2<unsigned short>>() );
>+static_assert( !ceil2_valid<maxpow2<unsigned short> + (unsigned short)1>() );
>+static_assert( !ceil2_valid<max<unsigned short>>() );
>+static_assert( !ceil2_valid<max<unsigned short> - (unsigned short)1>() );
>+
>+static_assert( ceil2_valid<maxpow2<unsigned int>>() );
>+static_assert( !ceil2_valid<maxpow2<unsigned int> + 1u>() );
>+static_assert( !ceil2_valid<max<unsigned int>>() );
>+static_assert( !ceil2_valid<max<unsigned int> - 1u>() );
>+
>+static_assert( ceil2_valid<maxpow2<unsigned long>>() );
>+static_assert( !ceil2_valid<maxpow2<unsigned long> + 1ul>() );
>+static_assert( !ceil2_valid<max<unsigned long>>() );
>+static_assert( !ceil2_valid<max<unsigned long> - 1ul>() );
>+
>+static_assert( ceil2_valid<maxpow2<unsigned long long>>() );
>+static_assert( !ceil2_valid<maxpow2<unsigned long long> + 1ull>() );
>+static_assert( !ceil2_valid<max<unsigned long long>>() );
>+static_assert( !ceil2_valid<max<unsigned long long> - 1ull>() );
>+
>+void
>+test01()
>+{
>+  std::ceil2( maxpow2<unsigned> + 1u ); // should fail __glibcxx_assert
>+}
>+
>+int main()
>+{
>+  test01();
>+}
diff mbox series

Patch

diff --git a/libstdc++-v3/include/std/bit b/libstdc++-v3/include/std/bit
index e0c53e53756..eb0a7578b8d 100644
--- a/libstdc++-v3/include/std/bit
+++ b/libstdc++-v3/include/std/bit
@@ -197,9 +197,27 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       constexpr auto _Nd = numeric_limits<_Tp>::digits;
       if (__x == 0 || __x == 1)
         return 1;
-      const unsigned __n = _Nd - std::__countl_zero((_Tp)(__x - 1u));
-      const _Tp __y_2 = (_Tp)1u << (__n - 1u);
-      return __y_2 << 1u;
+      auto __shift_exponent = _Nd - std::__countl_zero((_Tp)(__x - 1u));
+      // If the shift exponent equals _Nd then the correct result is not
+      // representable as a value of _Tp, and so the result is undefined.
+      // Want that undefined behaviour to be detected in constant expressions,
+      // by UBSan, and by debug assertions.
+#ifdef _GLIBCXX_HAVE_BUILTIN_IS_CONSTANT_EVALUATED
+      if (!__builtin_is_constant_evaluated())
+	__glibcxx_assert( __shift_exponent != numeric_limits<_Tp>::digits );
+#endif
+      using __promoted_type = decltype(__x << 1);
+      if _GLIBCXX17_CONSTEXPR (!is_same<__promoted_type, _Tp>::value)
+	{
+	  // If __x undergoes integral promotion then shifting by _Nd is
+	  // not undefined. In order to make the shift undefined, so that
+	  // it is diagnosed in constant expressions and by UBsan, we also
+	  // need to "promote" the shift exponent to be too large for the
+	  // promoted type.
+	  const int __extra_exp = sizeof(__promoted_type) / sizeof(_Tp) / 2;
+	  __shift_exponent |= (__shift_exponent & _Nd) << __extra_exp;
+	}
+      return (_Tp)1u << __shift_exponent;
     }
 
   template<typename _Tp>
diff --git a/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc
index 6ffb5f70edb..788c008129e 100644
--- a/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc
+++ b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2.cc
@@ -20,6 +20,21 @@ 
 
 #include <bit>
 
+template<typename T>
+  constexpr T max = std::numeric_limits<T>::max();
+// Largest representable power of two (i.e. has most significant bit set)
+template<typename T>
+  constexpr T maxpow2 = T(1) << (std::numeric_limits<T>::digits - 1);
+
+// Detect whether std::ceil2(N) is a constant expression.
+template<auto N, typename = void>
+  struct ceil2_valid
+  : std::false_type { };
+
+template<auto N>
+  struct ceil2_valid<N, std::void_t<char[(std::ceil2(N), 1)]>>
+  : std::true_type { };
+
 template<typename UInt>
 constexpr auto
 test(UInt x)
@@ -55,13 +70,18 @@  test(UInt x)
     static_assert( std::ceil2(UInt(3) << 64) == (UInt(4) << 64) );
   }
 
-  constexpr UInt msb = UInt(1) << (std::numeric_limits<UInt>::digits - 1);
+  constexpr UInt msb = maxpow2<UInt>;
+  static_assert( ceil2_valid<msb>() );
   static_assert( std::ceil2( msb ) == msb );
-  // Larger values cannot be represented so the return value is unspecified,
-  // but must still be valid in constant expressions, i.e. not undefined.
-  static_assert( std::ceil2( UInt(msb + 1) ) != 77 );
-  static_assert( std::ceil2( UInt(msb + 2) ) != 77 );
-  static_assert( std::ceil2( UInt(msb + 77) ) != 77 );
+  static_assert( std::ceil2( UInt(msb - 1) ) == msb );
+  static_assert( std::ceil2( UInt(msb - 2) ) == msb );
+  static_assert( std::ceil2( UInt(msb - 3) ) == msb );
+
+  // P1355R2: not a constant expression if the result is not representable
+  static_assert( !ceil2_valid<UInt(msb + 1)>() );
+  static_assert( !ceil2_valid<max<UInt>>() );
+  static_assert( !ceil2_valid<UInt(max<UInt> - 1)>() );
+  static_assert( !ceil2_valid<UInt(max<UInt> - 2)>() );
 
   return true;
 }
@@ -114,3 +134,7 @@  static_assert( test( (__GLIBCXX_TYPE_INT_N_1)0 ).did_not_match() );
 static_assert( test( (unsigned __GLIBCXX_TYPE_INT_N_2)0 ) );
 static_assert( test( (__GLIBCXX_TYPE_INT_N_2)0 ).did_not_match() );
 #endif
+#if defined(__GLIBCXX_TYPE_INT_N_3)
+static_assert( test( (unsigned __GLIBCXX_TYPE_INT_N_3)0 ) );
+static_assert( test( (__GLIBCXX_TYPE_INT_N_3)0 ).did_not_match() );
+#endif
diff --git a/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc
new file mode 100644
index 00000000000..8e107065a92
--- /dev/null
+++ b/libstdc++-v3/testsuite/26_numerics/bit/bit.pow.two/ceil2_neg.cc
@@ -0,0 +1,74 @@ 
+// Copyright (C) 2019 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-options "-std=gnu++2a -D_GLIBCXX_ASSERTIONS" }
+// { dg-do run { target c++2a } }
+// { dg-xfail-run-if "__glibcxx_assert in ceil2 should fail" { *-*-* } }
+
+#include <bit>
+
+// P1355R2: not a constant expression if the result is not representable
+
+template<auto N, typename = void>
+  struct ceil2_valid
+  : std::false_type { };
+
+template<auto N>
+  struct ceil2_valid<N, std::void_t<char[(std::ceil2(N), 1)]>>
+  : std::true_type { };
+
+template<typename T>
+  constexpr T max = std::numeric_limits<T>::max();
+template<typename T>
+  constexpr T maxpow2 = T(1) << (std::numeric_limits<T>::digits - 1);
+
+static_assert( ceil2_valid<maxpow2<unsigned char>>() );
+static_assert( !ceil2_valid<maxpow2<unsigned char> + (unsigned char)1>() );
+
+static_assert( !ceil2_valid<max<unsigned char>>() );
+static_assert( !ceil2_valid<max<unsigned char> - (unsigned char)1>() );
+
+static_assert( ceil2_valid<maxpow2<unsigned short>>() );
+static_assert( !ceil2_valid<maxpow2<unsigned short> + (unsigned short)1>() );
+static_assert( !ceil2_valid<max<unsigned short>>() );
+static_assert( !ceil2_valid<max<unsigned short> - (unsigned short)1>() );
+
+static_assert( ceil2_valid<maxpow2<unsigned int>>() );
+static_assert( !ceil2_valid<maxpow2<unsigned int> + 1u>() );
+static_assert( !ceil2_valid<max<unsigned int>>() );
+static_assert( !ceil2_valid<max<unsigned int> - 1u>() );
+
+static_assert( ceil2_valid<maxpow2<unsigned long>>() );
+static_assert( !ceil2_valid<maxpow2<unsigned long> + 1ul>() );
+static_assert( !ceil2_valid<max<unsigned long>>() );
+static_assert( !ceil2_valid<max<unsigned long> - 1ul>() );
+
+static_assert( ceil2_valid<maxpow2<unsigned long long>>() );
+static_assert( !ceil2_valid<maxpow2<unsigned long long> + 1ull>() );
+static_assert( !ceil2_valid<max<unsigned long long>>() );
+static_assert( !ceil2_valid<max<unsigned long long> - 1ull>() );
+
+void
+test01()
+{
+  std::ceil2( maxpow2<unsigned> + 1u ); // should fail __glibcxx_assert
+}
+
+int main()
+{
+  test01();
+}