From a38376018b6254383cd631a01ba2b5d9afe3bafb Mon Sep 17 00:00:00 2001 From: David LeBlanc Date: Wed, 7 Aug 2024 12:04:41 -0700 Subject: [PATCH 1/3] Change shift operators to throw on undefined behavior instead of just assert. --- SafeInt.hpp | 89 ++++++++++++++++++++++++++++++++++------------------- helpfile.md | 2 +- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/SafeInt.hpp b/SafeInt.hpp index 4b4e629..b74c25a 100644 --- a/SafeInt.hpp +++ b/SafeInt.hpp @@ -5374,6 +5374,20 @@ template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int } }; +template < typename T, typename U > +SAFE_INT_NODISCARD _CONSTEXPR14 bool valid_bitcount(U bits) +{ + if (!std::numeric_limits< U >::is_signed || bits >= 0) + { + if (bits < (int)safeint_internal::int_traits< T >::bitCount) + { + return true; + } + } + + return false; +} + /***************** External functions ****************************************/ // External functions that can be used where you only need to check one operation @@ -5988,25 +6002,26 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI // code path is exactly the same as for SafeInt< U, E > as rhs // Left shift - // Also, shifting > bitcount is undefined - trap in debug - #define ShiftAssert(x) SAFEINT_ASSERT(x) template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( U bits ) const SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || bits >= 0 ); - ShiftAssert( bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + return SafeInt< T, E >((T)(m_int << bits)); + } - return SafeInt< T, E >( (T)( m_int << bits ) ); + E::SafeIntOnOverflow(); } template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( SafeInt< U, E > bits ) const SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || (U)bits >= 0 ); - ShiftAssert( (U)bits < (int)safeint_internal::int_traits< T >::bitCount ); - - return SafeInt< T, E >( (T)( m_int << (U)bits ) ); + if (valid_bitcount(bits)) + { + return SafeInt< T, E >((T)(m_int << (U)bits)); + } + E::SafeIntOnOverflow(); } // Left shift assignment @@ -6014,61 +6029,73 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( U bits ) SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || bits >= 0 ); - ShiftAssert( bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + m_int <<= bits; + return *this; + } - m_int <<= bits; - return *this; + E::SafeIntOnOverflow(); } template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( SafeInt< U, E > bits ) SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || (U)bits >= 0 ); - ShiftAssert( (U)bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + m_int <<= (U)bits; + return *this; + } - m_int <<= (U)bits; - return *this; + E::SafeIntOnOverflow(); } // Right shift template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( U bits ) const SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || bits >= 0 ); - ShiftAssert( bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + return SafeInt< T, E >((T)(m_int >> bits)); + } - return SafeInt< T, E >( (T)( m_int >> bits ) ); + E::SafeIntOnOverflow(); } template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( SafeInt< U, E > bits ) const SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || (U)bits >= 0 ); - ShiftAssert( (U)bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + return SafeInt< T, E >((T)(m_int >> (U)bits)); + } - return SafeInt< T, E >( (T)(m_int >> (U)bits) ); + E::SafeIntOnOverflow(); } // Right shift assignment template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( U bits ) SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || bits >= 0 ); - ShiftAssert( bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + m_int >>= bits; + return *this; + } - m_int >>= bits; - return *this; + E::SafeIntOnOverflow(); } template < typename U > SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( SafeInt< U, E > bits ) SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< U >::is_signed || (U)bits >= 0 ); - ShiftAssert( (U)bits < (int)safeint_internal::int_traits< T >::bitCount ); + if (valid_bitcount(bits)) + { + m_int >>= (U)bits; + return *this; + } - m_int >>= (U)bits; - return *this; + E::SafeIntOnOverflow(); } // Bitwise operators diff --git a/helpfile.md b/helpfile.md index a9a4b40..7a5fe59 100644 --- a/helpfile.md +++ b/helpfile.md @@ -324,7 +324,7 @@ The shift operators have the following signatures: SafeInt< U, E > operator >>( U lhs, SafeInt< T, E > bits ) Note: -Shift operations where the right argument is larger than the number of bits in the left argument type minus 1 is implementation-defined behavior. SafeInt will attempt to help you avoid this by causing an assert in debug builds. If you prefer to have a stronger reaction, create a custom SAFEINT_ASSERT that throws or causes a fault in both debug and release builds. Shifting by a negative bit count also doesn't make sense, and is also trapped. +Shift operations where the right argument is larger than the number of bits in the left argument type minus 1 is implementation-defined behavior. Undefined behavior, such as shifting by a negative count or more bits than the type has will throw an exception. ### Methods SafeInt has a a small number of methods that help with common problems, such as aligning a value, getting a pointer to the raw integer type, and helping to manage pointer arithmetic. From be059f058b71dcda294338325a20e0a4b81db3e1 Mon Sep 17 00:00:00 2001 From: David LeBlanc Date: Thu, 8 Aug 2024 19:44:27 -0700 Subject: [PATCH 2/3] Build fixes --- SafeInt.hpp | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/SafeInt.hpp b/SafeInt.hpp index b74c25a..076f6f9 100644 --- a/SafeInt.hpp +++ b/SafeInt.hpp @@ -5374,10 +5374,31 @@ template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int } }; +template < typename U, int signed > class bits_not_negative; + +// Signed case +template < typename U > class bits_not_negative < U, true > +{ +public: + SAFE_INT_NODISCARD _CONSTEXPR14 static bool value(U bits) + { + return bits >= 0; + } +}; + +template < typename U > class bits_not_negative < U, false > +{ +public: + SAFE_INT_NODISCARD _CONSTEXPR14 static bool value(U) + { + return true; + } +}; + template < typename T, typename U > SAFE_INT_NODISCARD _CONSTEXPR14 bool valid_bitcount(U bits) { - if (!std::numeric_limits< U >::is_signed || bits >= 0) + if (bits_not_negative::is_signed>::value(bits)) { if (bits < (int)safeint_internal::int_traits< T >::bitCount) { @@ -6945,20 +6966,24 @@ SAFEINT_CONSTEXPR14 T*& operator >>=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW template < typename T, typename U, typename E > SAFEINT_CONSTEXPR14 SafeInt< U, E > operator <<( U lhs, SafeInt< T, E > bits ) SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< T >::is_signed || (T)bits >= 0 ); - ShiftAssert( (T)bits < (int)safeint_internal::int_traits< U >::bitCount ); + if (valid_bitcount(bits)) + { + return SafeInt< U, E >((U)(lhs << (T)bits)); + } - return SafeInt< U, E >( (U)( lhs << (T)bits ) ); + E::SafeIntOnOverflow(); } // Right shift template < typename T, typename U, typename E > SAFEINT_CONSTEXPR14 SafeInt< U, E > operator >>( U lhs, SafeInt< T, E > bits ) SAFEINT_NOTHROW { - ShiftAssert( !std::numeric_limits< T >::is_signed || (T)bits >= 0 ); - ShiftAssert( (T)bits < (int)safeint_internal::int_traits< U >::bitCount ); + if (valid_bitcount(bits)) + { + return SafeInt< U, E >((U)(lhs >> (T)bits)); + } - return SafeInt< U, E >( (U)( lhs >> (T)bits ) ); + E::SafeIntOnOverflow(); } // Bitwise operators From 988c3577504411c6517f9a745cabfd328a97ecf8 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Tue, 3 Dec 2024 11:56:30 +0100 Subject: [PATCH 3/3] More fixes. --- SafeInt.hpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/SafeInt.hpp b/SafeInt.hpp index 076f6f9..429c96c 100644 --- a/SafeInt.hpp +++ b/SafeInt.hpp @@ -5380,7 +5380,7 @@ template < typename U, int signed > class bits_not_negative; template < typename U > class bits_not_negative < U, true > { public: - SAFE_INT_NODISCARD _CONSTEXPR14 static bool value(U bits) + SAFE_INT_NODISCARD SAFEINT_CONSTEXPR14 static bool value(U bits) { return bits >= 0; } @@ -5389,14 +5389,14 @@ template < typename U > class bits_not_negative < U, true > template < typename U > class bits_not_negative < U, false > { public: - SAFE_INT_NODISCARD _CONSTEXPR14 static bool value(U) + SAFE_INT_NODISCARD SAFEINT_CONSTEXPR14 static bool value(U) { return true; } }; template < typename T, typename U > -SAFE_INT_NODISCARD _CONSTEXPR14 bool valid_bitcount(U bits) +SAFE_INT_NODISCARD SAFEINT_CONSTEXPR14 bool valid_bitcount(U bits) { if (bits_not_negative::is_signed>::value(bits)) { @@ -6025,7 +6025,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI // Left shift template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( U bits ) const SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( U bits ) const { if (valid_bitcount(bits)) { @@ -6036,7 +6036,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI } template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( SafeInt< U, E > bits ) const SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( SafeInt< U, E > bits ) const { if (valid_bitcount(bits)) { @@ -6048,7 +6048,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI // Left shift assignment template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( U bits ) SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( U bits ) { if (valid_bitcount(bits)) { @@ -6060,7 +6060,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI } template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( SafeInt< U, E > bits ) SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( SafeInt< U, E > bits ) { if (valid_bitcount(bits)) { @@ -6073,7 +6073,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI // Right shift template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( U bits ) const SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( U bits ) const { if (valid_bitcount(bits)) { @@ -6084,7 +6084,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI } template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( SafeInt< U, E > bits ) const SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( SafeInt< U, E > bits ) const { if (valid_bitcount(bits)) { @@ -6096,7 +6096,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI // Right shift assignment template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( U bits ) SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( U bits ) { if (valid_bitcount(bits)) { @@ -6108,7 +6108,7 @@ template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeI } template < typename U > - SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( SafeInt< U, E > bits ) SAFEINT_NOTHROW + SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( SafeInt< U, E > bits ) { if (valid_bitcount(bits)) {