diff options
Diffstat (limited to 'src/inc/safemath.h')
-rw-r--r-- | src/inc/safemath.h | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/src/inc/safemath.h b/src/inc/safemath.h new file mode 100644 index 0000000000..073c998548 --- /dev/null +++ b/src/inc/safemath.h @@ -0,0 +1,872 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// --------------------------------------------------------------------------- +// safemath.h +// +// overflow checking infrastructure +// --------------------------------------------------------------------------- + + +#ifndef SAFEMATH_H_ +#define SAFEMATH_H_ + +// This file is included from several places outside the CLR, so we can't +// pull in files like DebugMacros.h. However, we assume that the standard +// clrtypes (UINT32 etc.) are defined. +#include "debugmacrosext.h" + +#ifndef _ASSERTE_SAFEMATH +#ifdef _ASSERTE +// Use _ASSERTE if we have it (should always be the case in the CLR) +#define _ASSERTE_SAFEMATH _ASSERTE +#else +// Otherwise (eg. we're being used from a tool like SOS) there isn't much +// we can rely on that is both available everywhere and rotor-safe. In +// several other tools we just take the recourse of disabling asserts, +// we'll do the same here. +// Ideally we'd have a collection of common utilities available evererywhere. +#define _ASSERTE_SAFEMATH(a) +#endif +#endif + +#include "static_assert.h" + +#ifdef PAL_STDCPP_COMPAT +#include <type_traits> +#else +#include "clr_std/type_traits" +#endif + +//================================================================== +// Semantics: if val can be represented as the exact same value +// when cast to Dst type, then FitsIn<Dst>(val) will return true; +// otherwise FitsIn returns false. +// +// Dst and Src must both be integral types. +// +// It's important to note that most of the conditionals in this +// function are based on static type information and as such will +// be optimized away. In particular, the case where the signs are +// identical will result in no code branches. + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:6326) // PREfast warning: Potential comparison of a constant with another constant +#endif // _PREFAST_ + +template <typename Dst, typename Src> +inline bool FitsIn(Src val) +{ +#ifdef _MSC_VER + static_assert_no_msg(!__is_class(Dst)); + static_assert_no_msg(!__is_class(Src)); +#endif + + if (std::is_signed<Src>::value == std::is_signed<Dst>::value) + { // Src and Dst are equally signed + if (sizeof(Src) <= sizeof(Dst)) + { // No truncation is possible + return true; + } + else + { // Truncation is possible, requiring runtime check + return val == (Src)((Dst)val); + } + } + else if (std::is_signed<Src>::value) + { // Src is signed, Dst is unsigned +#ifdef __GNUC__ + // Workaround for GCC warning: "comparison is always + // false due to limited range of data type." + if (!(val == 0 || val > 0)) +#else + if (val < 0) +#endif + { // A negative number cannot be represented by an unsigned type + return false; + } + else + { + if (sizeof(Src) <= sizeof(Dst)) + { // No truncation is possible + return true; + } + else + { // Truncation is possible, requiring runtime check + return val == (Src)((Dst)val); + } + } + } + else + { // Src is unsigned, Dst is signed + if (sizeof(Src) < sizeof(Dst)) + { // No truncation is possible. Note that Src is strictly + // smaller than Dst. + return true; + } + else + { // Truncation is possible, requiring runtime check +#ifdef __GNUC__ + // Workaround for GCC warning: "comparison is always + // true due to limited range of data type." If in fact + // Dst were unsigned we'd never execute this code + // anyway. + return ((Dst)val > 0 || (Dst)val == 0) && +#else + return ((Dst)val >= 0) && +#endif + (val == (Src)((Dst)val)); + } + } +} + +// Requires that Dst is an integral type, and that DstMin and DstMax are the +// minimum and maximum values of that type, respectively. Returns "true" iff +// "val" can be represented in the range [DstMin..DstMax] (allowing loss of precision, but +// not truncation). +template <INT64 DstMin, UINT64 DstMax> +inline bool FloatFitsInIntType(float val) +{ + float DstMinF = static_cast<float>(DstMin); + float DstMaxF = static_cast<float>(DstMax); + return DstMinF <= val && val <= DstMaxF; +} + +template <INT64 DstMin, UINT64 DstMax> +inline bool DoubleFitsInIntType(double val) +{ + double DstMinD = static_cast<double>(DstMin); + double DstMaxD = static_cast<double>(DstMax); + return DstMinD <= val && val <= DstMaxD; +} + +#ifdef _PREFAST_ +#pragma warning(pop) +#endif //_PREFAST_ + +#define ovadd_lt(a, b, rhs) (((a) + (b) < (rhs) ) && ((a) + (b) >= (a))) +#define ovadd_le(a, b, rhs) (((a) + (b) <= (rhs) ) && ((a) + (b) >= (a))) +#define ovadd_gt(a, b, rhs) (((a) + (b) > (rhs) ) || ((a) + (b) < (a))) +#define ovadd_ge(a, b, rhs) (((a) + (b) >= (rhs) ) || ((a) + (b) < (a))) + +#define ovadd3_gt(a, b, c, rhs) (((a) + (b) + (c) > (rhs)) || ((a) + (b) < (a)) || ((a) + (b) + (c) < (c))) + + + //----------------------------------------------------------------------------- +// +// Liberally lifted from the Office example on MSDN and modified. +// http://msdn.microsoft.com/library/en-us/dncode/html/secure01142004.asp +// +// Modified to track an overflow bit instead of throwing exceptions. In most +// cases the Visual C++ optimizer (Whidbey beta1 - v14.00.40607) is able to +// optimize the bool away completely. +// Note that using a sentinal value (IntMax for example) to represent overflow +// actually results in poorer code-gen. +// +// This has also been simplified significantly to remove functionality we +// don't currently want (division, implicit conversions, many additional operators etc.) +// +// Example: +// unsafe: UINT32 bufSize = headerSize + elementCount * sizeof(void*); +// becomes: +// S_UINT32 bufSize = S_UINT32(headerSize) + S_UINT32(elementCount) * +// S_UINT32( sizeof(void*) ); +// if( bufSize.IsOverflow() ) { <overflow-error> } +// else { use bufSize.Value() } +// or: +// UINT32 tmp, bufSize; +// if( !ClrSafeInt<UINT32>::multiply( elementCount, sizeof(void*), tmp ) || +// !ClrSafeInt<UINT32>::addition( tmp, headerSize, bufSize ) ) +// { <overflow-error> } +// else { use bufSize } +// +//----------------------------------------------------------------------------- +// TODO: Any way to prevent unintended instantiations? This is only designed to +// work with unsigned integral types (signed types will work but we probably +// don't need signed support). +template<typename T> class ClrSafeInt +{ +public: + // Default constructor - 0 value by default + ClrSafeInt() : + m_value(0), + m_overflow(false) + COMMA_INDEBUG( m_checkedOverflow( false ) ) + { + } + + // Value constructor + // This is explicit because otherwise it would be harder to + // differentiate between checked and unchecked usage of an operator. + // I.e. si + x + y vs. si + ( x + y ) + // + // Set the m_checkedOverflow bit to true since this is being initialized + // with a constant value and we know that it is valid. A scenario in + // which this is useful is when an overflow causes a fallback value to + // be used: + // if (val.IsOverflow()) + // val = ClrSafeInt<T>(some_value); + explicit ClrSafeInt( T v ) : + m_value(v), + m_overflow(false) + COMMA_INDEBUG( m_checkedOverflow( true ) ) + { + } + + template <typename U> + explicit ClrSafeInt(U u) : + m_value(0), + m_overflow(false) + COMMA_INDEBUG( m_checkedOverflow( false ) ) + { + if (!FitsIn<T>(u)) + { + m_overflow = true; + } + else + { + m_value = (T)u; + } + } + + template <typename U> + ClrSafeInt(ClrSafeInt<U> u) : + m_value(0), + m_overflow(false) + COMMA_INDEBUG( m_checkedOverflow( false ) ) + { + if (u.IsOverflow() || !FitsIn<T>(u.Value())) + { + m_overflow = true; + } + else + { + m_value = (T)u.Value(); + } + } + + // Note: compiler-generated copy constructor and assignment operator + // are correct for our purposes. + + // Note: The MS compiler will sometimes silently perform value-destroying + // conversions when calling the operators below. + // Eg. "ClrSafeInt<unsigned> s(0); s += int(-1);" will result in s + // having the value 0xffffffff without generating a compile-time warning. + // Narrowing conversions are generally level 4 warnings so may or may not + // be visible. + // + // In the original SafeInt class, all operators have an + // additional overload that takes an arbitrary type U and then safe + // conversions are performed (resulting in overflow whenever the value + // cannot be preserved). + // We could do the same thing, but currently don't because: + // - we don't believe there are common cases where this would result in a + // security hole. + // - the extra complexity isn't worth the benefits + // - it would prevent compiler warnings in the cases we do get warnings for. + + + // true if there has been an overflow leading up to the creation of this + // value, false otherwise. + // Note that in debug builds we track whether our client called this, + // so we should not be calling this method ourselves from within this class. + inline bool IsOverflow() const + { + INDEBUG( m_checkedOverflow = true; ) + return m_overflow; + } + + // Get the value of this integer. + // Must only be called when IsOverflow()==false. If this is called + // on overflow we'll assert in Debug and return 0 in release. + inline T Value() const + { + _ASSERTE_SAFEMATH( m_checkedOverflow ); // Ensure our caller first checked the overflow bit + _ASSERTE_SAFEMATH( !m_overflow ); + return m_value; + } + + // force the value into the overflow state. + inline void SetOverflow() + { + INDEBUG( this->m_checkedOverflow = false; ) + this->m_overflow = true; + // incase someone manages to call Value in release mode - should be optimized out + this->m_value = 0; + } + + + // + // OPERATORS + // + + // Addition and multiplication. Only permitted when both sides are explicitly + // wrapped inside of a ClrSafeInt and when the types match exactly. + // If we permitted a RHS of type 'T', then there would be differences + // in correctness between mathematically equivalent expressions such as + // "si + x + y" and "si + ( x + y )". Unfortunately, not permitting this + // makes expressions involving constants tedius and ugly since the constants + // must be wrapped in ClrSafeInt instances. If we become confident that + // our tools (PreFast) will catch all integer overflows, then we can probably + // safely add this. + inline ClrSafeInt<T> operator +(ClrSafeInt<T> rhs) const + { + ClrSafeInt<T> result; // value is initialized to 0 + if( this->m_overflow || + rhs.m_overflow || + !addition( this->m_value, rhs.m_value, result.m_value ) ) + { + result.m_overflow = true; + } + + return result; + } + + inline ClrSafeInt<T> operator -(ClrSafeInt<T> rhs) const + { + ClrSafeInt<T> result; // value is initialized to 0 + if( this->m_overflow || + rhs.m_overflow || + !subtraction( this->m_value, rhs.m_value, result.m_value ) ) + { + result.m_overflow = true; + } + + return result; + } + + inline ClrSafeInt<T> operator *(ClrSafeInt<T> rhs) const + { + ClrSafeInt<T> result; // value is initialized to 0 + if( this->m_overflow || + rhs.m_overflow || + !multiply( this->m_value, rhs.m_value, result.m_value ) ) + { + result.m_overflow = true; + } + + return result; + } + + // Accumulation operators + // Here it's ok to have versions that take a value of type 'T', however we still + // don't allow any mixed-type operations. + inline ClrSafeInt<T>& operator +=(ClrSafeInt<T> rhs) + { + INDEBUG( this->m_checkedOverflow = false; ) + if( this->m_overflow || + rhs.m_overflow || + !ClrSafeInt<T>::addition( this->m_value, rhs.m_value, this->m_value ) ) + { + this->SetOverflow(); + } + return *this; + } + + inline ClrSafeInt<T>& operator +=(T rhs) + { + INDEBUG( this->m_checkedOverflow = false; ) + if( this->m_overflow || + !ClrSafeInt<T>::addition( this->m_value, rhs, this->m_value ) ) + { + this->SetOverflow(); + } + return *this; + } + + inline ClrSafeInt<T>& operator *=(ClrSafeInt<T> rhs) + { + INDEBUG( this->m_checkedOverflow = false; ) + if( this->m_overflow || + rhs.m_overflow || + !ClrSafeInt<T>::multiply( this->m_value, rhs.m_value, this->m_value ) ) + { + this->SetOverflow(); + } + return *this; + } + + inline ClrSafeInt<T>& operator *=(T rhs) + { + INDEBUG( this->m_checkedOverflow = false; ) + if( this->m_overflow || + !ClrSafeInt<T>::multiply( this->m_value, rhs, this->m_value ) ) + { + this->SetOverflow(); + } + + return *this; + } + + // + // STATIC HELPER METHODS + //these compile down to something as efficient as macros and allow run-time testing + //of type by the developer + // + + template <typename U> static bool IsSigned(U) + { + return std::is_signed<U>::value; + } + + static bool IsSigned() + { + return std::is_signed<T>::value; + } + + static bool IsMixedSign(T lhs, T rhs) + { + return ((lhs ^ rhs) < 0); + } + + static unsigned char BitCount(){return (sizeof(T)*8);} + + static bool Is64Bit(){return sizeof(T) == 8;} + static bool Is32Bit(){return sizeof(T) == 4;} + static bool Is16Bit(){return sizeof(T) == 2;} + static bool Is8Bit(){return sizeof(T) == 1;} + + //both of the following should optimize away + static T MaxInt() + { + if(IsSigned()) + { + return (T)~((T)1 << (BitCount()-1)); + } + //else + return (T)(~(T)0); + } + + static T MinInt() + { + if(IsSigned()) + { + return (T)((T)1 << (BitCount()-1)); + } + else + { + return ((T)0); + } + } + + // Align a value up to the nearest boundary, which must be a power of 2 + inline void AlignUp( T alignment ) + { + _ASSERTE_SAFEMATH( IsPowerOf2( alignment ) ); + *this += (alignment - 1); + if( !this->m_overflow ) + { + m_value &= ~(alignment - 1); + } + } + + // + // Arithmetic implementation functions + // + + //note - this looks complex, but most of the conditionals + //are constant and optimize away + //for example, a signed 64-bit check collapses to: +/* + if(lhs == 0 || rhs == 0) + return 0; + + if(MaxInt()/+lhs < +rhs) + { + //overflow + throw SafeIntException(ERROR_ARITHMETIC_OVERFLOW); + } + //ok + return lhs * rhs; + + Which ought to inline nicely +*/ + // Returns true if safe, false for overflow. + static bool multiply(T lhs, T rhs, T &result) + { + if(Is64Bit()) + { + //fast track this one - and avoid DIV_0 below + if(lhs == 0 || rhs == 0) + { + result = 0; + return true; + } + + //we're 64 bit - slow, but the only way to do it + if(IsSigned()) + { + if(!IsMixedSign(lhs, rhs)) + { + //both positive or both negative + //result will be positive, check for lhs * rhs > MaxInt + if(lhs > 0) + { + //both positive + if(MaxInt()/lhs < rhs) + { + //overflow + return false; + } + } + else + { + //both negative + + //comparison gets tricky unless we force it to positive + //EXCEPT that -MinInt is undefined - can't be done + //And MinInt always has a greater magnitude than MaxInt + if(lhs == MinInt() || rhs == MinInt()) + { + //overflow + return false; + } + +#ifdef _MSC_VER +#pragma warning( disable : 4146 ) // unary minus applied to unsigned is still unsigned +#endif + if(MaxInt()/(-lhs) < (-rhs) ) + { + //overflow + return false; + } +#ifdef _MSC_VER +#pragma warning( default : 4146 ) +#endif + } + } + else + { + //mixed sign - this case is difficult + //test case is lhs * rhs < MinInt => overflow + //if lhs < 0 (implies rhs > 0), + //lhs < MinInt/rhs is the correct test + //else if lhs > 0 + //rhs < MinInt/lhs is the correct test + //avoid dividing MinInt by a negative number, + //because MinInt/-1 is a corner case + + if(lhs < 0) + { + if(lhs < MinInt()/rhs) + { + //overflow + return false; + } + } + else + { + if(rhs < MinInt()/lhs) + { + //overflow + return false; + } + } + } + + //ok + result = lhs * rhs; + return true; + } + else + { + //unsigned, easy case + if(MaxInt()/lhs < rhs) + { + //overflow + return false; + } + //ok + result = lhs * rhs; + return true; + } + } + else if(Is32Bit()) + { + //we're 32-bit + if(IsSigned()) + { + INT64 tmp = (INT64)lhs * (INT64)rhs; + + //upper 33 bits must be the same + //most common case is likely that both are positive - test first + if( (tmp & 0xffffffff80000000LL) == 0 || + (tmp & 0xffffffff80000000LL) == 0xffffffff80000000LL) + { + //this is OK + result = (T)tmp; + return true; + } + + //overflow + return false; + + } + else + { + UINT64 tmp = (UINT64)lhs * (UINT64)rhs; + if (tmp & 0xffffffff00000000ULL) //overflow + { + //overflow + return false; + } + result = (T)tmp; + return true; + } + } + else if(Is16Bit()) + { + //16-bit + if(IsSigned()) + { + INT32 tmp = (INT32)lhs * (INT32)rhs; + //upper 17 bits must be the same + //most common case is likely that both are positive - test first + if( (tmp & 0xffff8000) == 0 || (tmp & 0xffff8000) == 0xffff8000) + { + //this is OK + result = (T)tmp; + return true; + } + + //overflow + return false; + } + else + { + UINT32 tmp = (UINT32)lhs * (UINT32)rhs; + if (tmp & 0xffff0000) //overflow + { + return false; + } + result = (T)tmp; + return true; + } + } + else //8-bit + { + _ASSERTE_SAFEMATH(Is8Bit()); + + if(IsSigned()) + { + INT16 tmp = (INT16)lhs * (INT16)rhs; + //upper 9 bits must be the same + //most common case is likely that both are positive - test first + if( (tmp & 0xff80) == 0 || (tmp & 0xff80) == 0xff80) + { + //this is OK + result = (T)tmp; + return true; + } + + //overflow + return false; + } + else + { + UINT16 tmp = ((UINT16)lhs) * ((UINT16)rhs); + + if (tmp & 0xff00) //overflow + { + return false; + } + result = (T)tmp; + return true; + } + } + } + + // Returns true if safe, false on overflow + static inline bool addition(T lhs, T rhs, T &result) + { + if(IsSigned()) + { + //test for +/- combo + if(!IsMixedSign(lhs, rhs)) + { + //either two negatives, or 2 positives +#ifdef __GNUC__ + // Workaround for GCC warning: "comparison is always + // false due to limited range of data type." + if (!(rhs == 0 || rhs > 0)) +#else + if(rhs < 0) +#endif // __GNUC__ else + { + //two negatives + if(lhs < (T)(MinInt() - rhs)) //remember rhs < 0 + { + return false; + } + //ok + } + else + { + //two positives + if((T)(MaxInt() - lhs) < rhs) + { + return false; + } + //OK + } + } + //else overflow not possible + result = lhs + rhs; + return true; + } + else //unsigned + { + if((T)(MaxInt() - lhs) < rhs) + { + return false; + + } + result = lhs + rhs; + return true; + } + } + + // Returns true if safe, false on overflow + static inline bool subtraction(T lhs, T rhs, T& result) + { + T tmp = lhs - rhs; + + if(IsSigned()) + { + if(IsMixedSign(lhs, rhs)) //test for +/- combo + { + //mixed positive and negative + //two cases - +X - -Y => X + Y - check for overflow against MaxInt() + // -X - +Y - check for overflow against MinInt() + + if(lhs >= 0) //first case + { + //test is X - -Y > MaxInt() + //equivalent to X > MaxInt() - |Y| + //Y == MinInt() creates special case + //Even 0 - MinInt() can't be done + //note that the special case collapses into the general case, due to the fact + //MaxInt() - MinInt() == -1, and lhs is non-negative + //OR tmp should be GTE lhs + + // old test - leave in for clarity + //if(lhs > (T)(MaxInt() + rhs)) //remember that rhs is negative + if(tmp < lhs) + { + return false; + } + //fall through to return value + } + else + { + //second case + //test is -X - Y < MinInt() + //or -X < MinInt() + Y + //we do not have the same issues because abs(MinInt()) > MaxInt() + //tmp should be LTE lhs + + //if(lhs < (T)(MinInt() + rhs)) // old test - leave in for clarity + if(tmp > lhs) + { + return false; + } + //fall through to return value + } + } + // else + //both negative, or both positive + //no possible overflow + result = tmp; + return true; + } + else + { + //easy unsigned case + if(lhs < rhs) + { + return false; + } + result = tmp; + return true; + } + } + +private: + // Private helper functions + // Note that's it occasionally handy to call the arithmetic implementation + // functions above so we leave them public, even though we almost always use + // the operators instead. + + // True if the specified value is a power of two. + static inline bool IsPowerOf2( T x ) + { + // find the smallest power of 2 >= x + T testPow = 1; + while( testPow < x ) + { + testPow = testPow << 1; // advance to next power of 2 + if( testPow <= 0 ) + { + return false; // overflow + } + } + + return( testPow == x ); + } + + // + // Instance data + // + + // The integer value this instance represents, or 0 if overflow. + T m_value; + + // True if overflow has been reached. Once this is set, it cannot be cleared. + bool m_overflow; + + // In debug builds we verify that our caller checked the overflow bit before + // accessing the value. This flag is cleared on initialization, and whenever + // m_value or m_overflow changes, and set only when IsOverflow + // is called. + INDEBUG( mutable bool m_checkedOverflow; ) +}; + +// Allows creation of a ClrSafeInt corresponding to the type of the argument. +template <typename T> +ClrSafeInt<T> AsClrSafeInt(T t) +{ + return ClrSafeInt<T>(t); +} + +template <typename T> +ClrSafeInt<T> AsClrSafeInt(ClrSafeInt<T> t) +{ + return t; +} + +// Convenience safe-integer types. Currently these are the only types +// we are using ClrSafeInt with. We may want to add others. +// These type names are based on our standardized names in clrtypes.h +typedef ClrSafeInt<UINT8> S_UINT8; +typedef ClrSafeInt<UINT16> S_UINT16; +//typedef ClrSafeInt<UINT32> S_UINT32; +#define S_UINT32 ClrSafeInt<UINT32> +typedef ClrSafeInt<UINT64> S_UINT64; +typedef ClrSafeInt<SIZE_T> S_SIZE_T; + +// Note: we can get bogus /Wp64 compiler warnings when S_SIZE_T is used. +// This is due to VSWhidbey 138322 which the C++ folks have said they can't +// currently fix. We can work around the problem by using this macro to force +// a no-op cast on 32-bit MSVC platforms. It's not yet clear why we need to +// use this in some places (specifically, rotor lkgvc builds) and not others. +// We also make the error less likely by using a #define instead of a +// typedef for S_UINT32 above since that means we're less likely to instantiate +// ClrSafeInt<UINT32> AND ClrSafeInt<SIZE_T> in the same compliation unit. +#if defined(_TARGET_X86_) && defined( _MSC_VER ) +#define S_SIZE_T_WP64BUG(v) S_SIZE_T( static_cast<UINT32>( v ) ) +#else +#define S_SIZE_T_WP64BUG(v) S_SIZE_T( v ) +#endif + + #endif // SAFEMATH_H_ |