//********************************************************* // // Copyright (c) Microsoft. All rights reserved. // This code is licensed under the MIT License. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. // //********************************************************* #ifndef __WIL_SAFECAST_INCLUDED #define __WIL_SAFECAST_INCLUDED #include "result_macros.h" #include #include "wistd_config.h" #include "wistd_type_traits.h" namespace wil { namespace details { // Default error case for undefined conversions in intsafe.h template constexpr wistd::nullptr_t intsafe_conversion = nullptr; // is_known_safe_static_cast_v determines if a conversion is known to be safe or not. Known // safe conversions can be handled by static_cast, this includes conversions between the same // type, when the new type is larger than the old type but is not a signed to unsigned // conversion, and when the two types are the same size and signed/unsigned. All other // conversions will be assumed to be potentially unsafe, and the conversion must be handled // by intsafe and checked. template constexpr bool is_known_safe_static_cast_v = (sizeof(NewT) > sizeof(OldT) && !(wistd::is_signed_v && wistd::is_unsigned_v)) || (sizeof(NewT) == sizeof(OldT) && ((wistd::is_signed_v && wistd::is_signed_v) || (wistd::is_unsigned_v && wistd::is_unsigned_v))); // Helper template to determine that NewT and OldT are both integral types. The safe_cast // operation only supports conversions between integral types. template constexpr bool both_integral_v = wistd::is_integral::value && wistd::is_integral::value; // Note on native wchar_t (__wchar_t): // Intsafe.h does not currently handle native wchar_t. When compiling with /Zc:wchar_t-, this is fine as wchar_t is // typedef'd to unsigned short. However, when compiling with /Zc:wchar_t or wchar_t as a native type, the lack of // support for native wchar_t in intsafe.h becomes an issue. To work around this, we treat native wchar_t as an // unsigned short when passing it to intsafe.h, because the two on the Windows platform are the same size and // share the same range according to MSDN. If the cast is to a native wchar_t, the result from intsafe.h is cast // to a native wchar_t. // Intsafe does not have a defined conversion for native wchar_t template constexpr bool neither_native_wchar_v = !wistd::is_same::value && !wistd::is_same::value; // Check to see if the cast is a conversion to native wchar_t template constexpr bool is_cast_to_wchar_v = wistd::is_same::value && !wistd::is_same::value; // Check to see if the cast is a conversion from native wchar_t template constexpr bool is_cast_from_wchar_v = !wistd::is_same::value && wistd::is_same::value; // Validate the conversion to be performed has a defined mapping to an intsafe conversion template constexpr bool is_supported_intsafe_cast_v = intsafe_conversion != nullptr; // True when the conversion is between integral types and can be handled by static_cast template constexpr bool is_supported_safe_static_cast_v = both_integral_v && is_known_safe_static_cast_v; // True when the conversion is between integral types, does not involve native wchar, has // a mapped intsafe conversion, and is unsafe. template constexpr bool is_supported_unsafe_cast_no_wchar_v = both_integral_v && !is_known_safe_static_cast_v && neither_native_wchar_v && is_supported_intsafe_cast_v; // True when the conversion is between integral types, is a cast to native wchar_t, has // a mapped intsafe conversion, and is unsafe. template constexpr bool is_supported_unsafe_cast_to_wchar_v = both_integral_v && !is_known_safe_static_cast_v && is_cast_to_wchar_v && is_supported_intsafe_cast_v; // True when the conversion is between integral types, is a cast from native wchar_t, has // a mapped intsafe conversion, and is unsafe. template constexpr bool is_supported_unsafe_cast_from_wchar_v = both_integral_v && !is_known_safe_static_cast_v && is_cast_from_wchar_v && is_supported_intsafe_cast_v; // True when the conversion is supported and unsafe, and may or may not involve // native wchar_t. template constexpr bool is_supported_unsafe_cast_v = is_supported_unsafe_cast_no_wchar_v || is_supported_unsafe_cast_to_wchar_v || is_supported_unsafe_cast_from_wchar_v; // True when T is any one of the primitive types that the variably sized types are defined as. template constexpr bool is_potentially_variably_sized_type_v = wistd::is_same::value || wistd::is_same::value || wistd::is_same::value || wistd::is_same::value || wistd::is_same::value || wistd::is_same::value; // True when either type is potentialy variably sized (e.g. size_t, ptrdiff_t) template constexpr bool is_potentially_variably_sized_cast_v = is_potentially_variably_sized_type_v || is_potentially_variably_sized_type_v; // Mappings of all conversions defined in intsafe.h to intsafe_conversion // Note: Uppercase types (UINT, DWORD, SIZE_T, etc) and architecture dependent types resolve // to the base types. The base types are used since they do not vary based on architecture. template<> constexpr auto intsafe_conversion<__int64, char> = LongLongToChar; template<> constexpr auto intsafe_conversion<__int64, int> = LongLongToInt; template<> constexpr auto intsafe_conversion<__int64, long> = LongLongToLong; template<> constexpr auto intsafe_conversion<__int64, short> = LongLongToShort; template<> constexpr auto intsafe_conversion<__int64, signed char> = LongLongToInt8; template<> constexpr auto intsafe_conversion<__int64, unsigned __int64> = LongLongToULongLong; template<> constexpr auto intsafe_conversion<__int64, unsigned char> = LongLongToUChar; template<> constexpr auto intsafe_conversion<__int64, unsigned int> = LongLongToUInt; template<> constexpr auto intsafe_conversion<__int64, unsigned long> = LongLongToULong; template<> constexpr auto intsafe_conversion<__int64, unsigned short> = LongLongToUShort; template<> constexpr auto intsafe_conversion = IntToChar; template<> constexpr auto intsafe_conversion = IntToShort; template<> constexpr auto intsafe_conversion = IntToInt8; template<> constexpr auto intsafe_conversion = IntToULongLong; template<> constexpr auto intsafe_conversion = IntToUChar; template<> constexpr auto intsafe_conversion = IntToUInt; template<> constexpr auto intsafe_conversion = IntToULong; template<> constexpr auto intsafe_conversion = IntToUShort; template<> constexpr auto intsafe_conversion = LongToChar; template<> constexpr auto intsafe_conversion = LongToInt; template<> constexpr auto intsafe_conversion = LongToShort; template<> constexpr auto intsafe_conversion = LongToInt8; template<> constexpr auto intsafe_conversion = LongToULongLong; template<> constexpr auto intsafe_conversion = LongToUChar; template<> constexpr auto intsafe_conversion = LongToUInt; template<> constexpr auto intsafe_conversion = LongToULong; template<> constexpr auto intsafe_conversion = LongToUShort; template<> constexpr auto intsafe_conversion = ShortToChar; template<> constexpr auto intsafe_conversion = ShortToInt8; template<> constexpr auto intsafe_conversion = ShortToULongLong; template<> constexpr auto intsafe_conversion = ShortToUChar; template<> constexpr auto intsafe_conversion = ShortToUInt; template<> constexpr auto intsafe_conversion = ShortToULong; template<> constexpr auto intsafe_conversion = ShortToUShort; template<> constexpr auto intsafe_conversion = Int8ToULongLong; template<> constexpr auto intsafe_conversion = Int8ToUChar; template<> constexpr auto intsafe_conversion = Int8ToUInt; template<> constexpr auto intsafe_conversion = Int8ToULong; template<> constexpr auto intsafe_conversion = Int8ToUShort; template<> constexpr auto intsafe_conversion = ULongLongToLongLong; template<> constexpr auto intsafe_conversion = ULongLongToChar; template<> constexpr auto intsafe_conversion = ULongLongToInt; template<> constexpr auto intsafe_conversion = ULongLongToLong; template<> constexpr auto intsafe_conversion = ULongLongToShort; template<> constexpr auto intsafe_conversion = ULongLongToInt8; template<> constexpr auto intsafe_conversion = ULongLongToUChar; template<> constexpr auto intsafe_conversion = ULongLongToUInt; template<> constexpr auto intsafe_conversion = ULongLongToULong; template<> constexpr auto intsafe_conversion = ULongLongToUShort; template<> constexpr auto intsafe_conversion = UInt8ToChar; template<> constexpr auto intsafe_conversion = UIntToInt8; template<> constexpr auto intsafe_conversion = UIntToChar; template<> constexpr auto intsafe_conversion = UIntToInt; template<> constexpr auto intsafe_conversion = UIntToLong; template<> constexpr auto intsafe_conversion = UIntToShort; template<> constexpr auto intsafe_conversion = UIntToInt8; template<> constexpr auto intsafe_conversion = UIntToUChar; template<> constexpr auto intsafe_conversion = UIntToUShort; template<> constexpr auto intsafe_conversion = ULongToChar; template<> constexpr auto intsafe_conversion = ULongToInt; template<> constexpr auto intsafe_conversion = ULongToLong; template<> constexpr auto intsafe_conversion = ULongToShort; template<> constexpr auto intsafe_conversion = ULongToInt8; template<> constexpr auto intsafe_conversion = ULongToUChar; template<> constexpr auto intsafe_conversion = ULongToUInt; template<> constexpr auto intsafe_conversion = ULongToUShort; template<> constexpr auto intsafe_conversion = UShortToChar; template<> constexpr auto intsafe_conversion = UShortToShort; template<> constexpr auto intsafe_conversion = UShortToInt8; template<> constexpr auto intsafe_conversion = UShortToUChar; } // Unsafe conversion where failure results in fail fast. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast_failfast(const OldT var) { NewT newVar; FAIL_FAST_IF_FAILED((details::intsafe_conversion(var, &newVar))); return newVar; } // Unsafe conversion where failure results in fail fast. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast_failfast(const OldT var) { NewT newVar; FAIL_FAST_IF_FAILED((details::intsafe_conversion(static_cast(var), &newVar))); return newVar; } // Unsafe conversion where failure results in fail fast. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast_failfast(const OldT var) { unsigned short newVar; FAIL_FAST_IF_FAILED((details::intsafe_conversion(var, &newVar))); return static_cast<__wchar_t>(newVar); } // This conversion is always safe, therefore a static_cast is fine. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast_failfast(const OldT var) { return static_cast(var); } #ifdef WIL_ENABLE_EXCEPTIONS // Unsafe conversion where failure results in a thrown exception. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast(const OldT var) { NewT newVar; THROW_IF_FAILED((details::intsafe_conversion(var, &newVar))); return newVar; } // Unsafe conversion where failure results in a thrown exception. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast(const OldT var) { NewT newVar; THROW_IF_FAILED((details::intsafe_conversion(static_cast(var), &newVar))); return newVar; } // Unsafe conversion where failure results in a thrown exception. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast(const OldT var) { unsigned short newVar; THROW_IF_FAILED((details::intsafe_conversion(var, &newVar))); return static_cast<__wchar_t>(newVar); } // This conversion is always safe, therefore a static_cast is fine. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast(const OldT var) { return static_cast(var); } #endif // This conversion is unsafe, therefore the two parameter version of safe_cast_nothrow must be used template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast_nothrow(const OldT /*var*/) { static_assert(!wistd::is_same_v, "This cast has the potential to fail, use the two parameter safe_cast_nothrow instead"); } // This conversion is always safe, therefore a static_cast is fine. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > NewT safe_cast_nothrow(const OldT var) { return static_cast(var); } // Unsafe conversion where an HRESULT is returned. It is up to the callee to check and handle the HRESULT template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > HRESULT safe_cast_nothrow(const OldT var, NewT* newTResult) { return details::intsafe_conversion(var, newTResult); } // Unsafe conversion where an HRESULT is returned. It is up to the callee to check and handle the HRESULT template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > HRESULT safe_cast_nothrow(const OldT var, NewT* newTResult) { return details::intsafe_conversion(static_cast(var), newTResult); } // Unsafe conversion where an HRESULT is returned. It is up to the callee to check and handle the HRESULT template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > HRESULT safe_cast_nothrow(const OldT var, NewT* newTResult) { return details::intsafe_conversion(var, reinterpret_cast(newTResult)); } // This conversion is always safe, therefore a static_cast is fine. If it can be determined the conversion // does not involve a variably sized type, then the compilation will fail and say the single parameter version // of safe_cast_nothrow should be used instead. template < typename NewT, typename OldT, wistd::enable_if_t, int> = 0 > HRESULT safe_cast_nothrow(const OldT var, NewT* newTResult) { static_assert(details::is_potentially_variably_sized_cast_v, "This cast is always safe; use safe_cast_nothrow(value) to avoid unnecessary error handling."); *newTResult = static_cast(var); return S_OK; } } #endif // __WIL_SAFECAST_INCLUDED