Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change shift operators to throw on undefined behavior instead of assert [UPDATED] #66

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Change shift operators to throw on undefined behavior instead of just…
… assert.
  • Loading branch information
dcleblanc authored and rsmmr committed Dec 3, 2024
commit a38376018b6254383cd631a01ba2b5d9afe3bafb
89 changes: 58 additions & 31 deletions SafeInt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -5988,87 +6002,100 @@ 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<T, U>(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<T, U>(bits))
{
return SafeInt< T, E >((T)(m_int << (U)bits));
}
E::SafeIntOnOverflow();
}

// Left 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<T, U>(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<T, U>(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<T, U>(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<T, U>(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<T, U>(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<T, U>(bits))
{
m_int >>= (U)bits;
return *this;
}

m_int >>= (U)bits;
return *this;
E::SafeIntOnOverflow();
}

// Bitwise operators
Expand Down
2 changes: 1 addition & 1 deletion helpfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down