Skip to content

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

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
142 changes: 97 additions & 45 deletions SafeInt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5374,6 +5374,41 @@ 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 SAFEINT_CONSTEXPR14 static bool value(U bits)
{
return bits >= 0;
}
};

template < typename U > class bits_not_negative < U, false >
{
public:
SAFE_INT_NODISCARD SAFEINT_CONSTEXPR14 static bool value(U)
{
return true;
}
};

template < typename T, typename U >
SAFE_INT_NODISCARD SAFEINT_CONSTEXPR14 bool valid_bitcount(U bits)
{
if (bits_not_negative<U, std::numeric_limits< U >::is_signed>::value(bits))
{
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 +6023,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
SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( U bits ) const
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E > operator <<( SafeInt< U, E > bits ) const
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( U bits )
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator <<=( SafeInt< U, E > bits )
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( U bits ) const
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E > operator >>( SafeInt< U, E > bits ) const
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( U bits )
{
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
SAFEINT_CONSTEXPR14 SafeInt< T, E >& operator >>=( SafeInt< U, E > bits )
{
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 Expand Up @@ -6918,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<T, U>(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<T, U>(bits))
{
return SafeInt< U, E >((U)(lhs >> (T)bits));
}

return SafeInt< U, E >( (U)( lhs >> (T)bits ) );
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