diff --git a/CMakeFiles/cmake.check_cache b/CMakeFiles/cmake.check_cache deleted file mode 100644 index 3dccd73..0000000 --- a/CMakeFiles/cmake.check_cache +++ /dev/null @@ -1 +0,0 @@ -# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/cmake/config.h.in b/cmake/config.h.in new file mode 100644 index 0000000..6d502c8 --- /dev/null +++ b/cmake/config.h.in @@ -0,0 +1,4 @@ +#pragma once + +#cmakedefine CPPCORE_CONVERT_CAST_ABORT +#cmakedefine CPPCORE_CONVERT_CAST_THROW diff --git a/cmake/cppcore-options.cmake b/cmake/cppcore-options.cmake index 5e3cdd0..f698146 100644 --- a/cmake/cppcore-options.cmake +++ b/cmake/cppcore-options.cmake @@ -9,3 +9,9 @@ Option ( CPPCORE_INSTALL_PACKAGE Option ( CPPCORE_USE_GIT_VERSION "Read the git tags to get the version of cppcore" ON ) +If ( NOT CPPCORE_CONVERT_CAST ) + Set ( CPPCORE_CONVERT_CAST throw + CACHE STRING "Specify how the convert_cast should handle invalid values." ) + Set_Property ( CACHE CPPCORE_CONVERT_CAST + PROPERTY STRINGS throw abort ) +EndIf ( ) diff --git a/include/cppcore/conversion/convert_cast.h b/include/cppcore/conversion/convert_cast.h new file mode 100644 index 0000000..53deb52 --- /dev/null +++ b/include/cppcore/conversion/convert_cast.h @@ -0,0 +1,25 @@ +#pragma once + +namespace cppcore +{ + + /** + * @brief Convert one type to another type. + * + * This method will try to convert the given value to the given type. + * If a convertion between the two types is unknown, a compiler error is raised. + * If the value to convert does not fullfill the conversion requirements (like range checks) + * the program will panic (call to std::abort) or throw an convert_exception (if configured). + * + * @tparam T_to Type to convert to. + * @tparam T_from Type to convert from. + * + * @param[in] p_value Value to convert. + * @return Converted value. + */ + template + constexpr T_to convert_cast(T_from p_value); + +} + +#include "convert_cast.inl" diff --git a/include/cppcore/conversion/convert_cast.inl b/include/cppcore/conversion/convert_cast.inl new file mode 100644 index 0000000..62dc694 --- /dev/null +++ b/include/cppcore/conversion/convert_cast.inl @@ -0,0 +1,178 @@ +#pragma once + +#ifdef CPPCORE_CONVERT_CAST_ABORT +# include +#endif + +#include +#include + +#include +#include +#include + +#include "convert_cast.h" + +namespace cppcore +{ + + namespace __impl + { + template + void convert_cast_raise(T_from p_value) + { + #if defined(CPPCORE_CONVERT_CAST_THROW) + using namespace std; + throw convert_exception("Unable to convert "s + + type_helper::name() + + "(" + std::to_string(p_value) + ") to " + + type_helper::name()); + #elif defined(CPPCORE_CONVERT_CAST_ABORT) + std::cerr + << "Unable to convert " + << type_helper::name() + << "(" << p_value << ") to " + << type_helper::name() + << std::endl; + std::abort(); + #endif + } + + template + struct convert_cast_impl + { + constexpr T_to operator()(T_from p_value) const + { static_assert(sizeof(T_from) < 0, "convert_cast is not available for the passed types!"); } + }; + + /** + * types are equal => simple static cast + */ + template + struct convert_cast_impl + { + constexpr T operator()(T p_value) const + { return p_value; } + }; + + /** + * T_from does fit into T_to => simple static cast + */ + template + struct convert_cast_impl< + T_from, + T_to, + std::enable_if_t< + (sizeof(T_from) < sizeof(T_to)) + && std::disjunction_v< + std::conjunction< + std::is_unsigned>, + std::is_integral> + >, + std::conjunction< + std::is_signed>, + std::is_signed> + > + > + > + > + { + constexpr T_to operator()(T_from p_value) const + { return static_cast(p_value); } + }; + + /** + * T_from does fit into T_to => check range and cast + */ + template + struct convert_cast_impl< + T_from, + T_to, + std::enable_if_t< + (sizeof(T_from) > sizeof(T_to)) + && std::is_signed_v> + && std::is_signed_v> + > + > + { + constexpr T_to operator()(T_from p_value) const + { + if (p_value < std::numeric_limits::min()) + convert_cast_raise(p_value); + if (p_value > std::numeric_limits::max()) + convert_cast_raise(p_value); + return static_cast(p_value); + } + }; + + /** + * T_from does fit into T_to => check range and cast + */ + template + struct convert_cast_impl< + T_from, + T_to, + std::enable_if_t< + ( (sizeof(T_from) >= sizeof(T_to)) + && std::is_unsigned_v> + && std::is_signed_v>) + || ( (sizeof(T_from) > sizeof(T_to)) + && std::is_unsigned_v> + && std::is_unsigned_v>) + > + > + { + constexpr T_to operator()(T_from p_value) const + { + if (p_value > std::numeric_limits::max()) + convert_cast_raise(p_value); + return static_cast(p_value); + } + }; + + /** + * signed to unsigned cast => check lower range and cast + */ + template + struct convert_cast_impl< + T_from, + T_to, + std::enable_if_t< + std::is_signed_v > + && std::is_unsigned_v>>> + { + constexpr T_to operator()(T_from p_value) const + { + using unsigned_type = std::make_unsigned_t; + if (p_value < 0) + convert_cast_raise(p_value); + return convert_cast(static_cast(p_value)); + } + }; + + /** + * enum to integral cast => static cast + */ + template + struct convert_cast_impl< + T_from, + T_to, + std::enable_if_t< + std::is_enum_v > + && std::is_integral_v> + >> + { + constexpr T_to operator()(T_from p_value) const + { + using underlying_type = std::underlying_type_t; + return convert_cast(static_cast(p_value)); + } + }; + + } + + template + constexpr T_to convert_cast(T_from p_value) + { return __impl::convert_cast_impl()(p_value); } + +} diff --git a/include/cppcore/conversion/string.inl b/include/cppcore/conversion/string.inl index 24a2302..d3b3dbd 100644 --- a/include/cppcore/conversion/string.inl +++ b/include/cppcore/conversion/string.inl @@ -1,10 +1,13 @@ #pragma once +#include + #include #include #include "enum.h" #include "string.h" +#include "convert_cast.h" #ifdef _GLIBCXX_VECTOR #define CPPCORE_HAS_VECTOR @@ -51,7 +54,7 @@ namespace cppcore && ( *i == seperator || *i == '\0')) { - std::string tmp(s, static_cast(i - s)); + std::string tmp(s, convert_cast(i - s)); if (!predicate(tmp)) return false; s = i + 1; @@ -362,13 +365,32 @@ namespace cppcore }; template - struct op_from_string>> + struct op_from_string && std::is_integral_v>> { - inline bool operator()(const std::string& s, T& v) const + inline bool operator()(const std::string& s, T& value) const + { + char *e = nullptr; + const char *c = s.c_str(); + auto tmp = std::strtoull(c, &e, 0); + if (tmp > std::numeric_limits::max()) + return false; + value = static_cast(tmp); + return (c != e); + } + }; + + template + struct op_from_string && std::is_integral_v>> + { + inline bool operator()(const std::string& s, T& value) const { char *e = nullptr; const char *c = s.c_str(); - v = static_cast(std::strtoull(c, &e, 0)); + auto tmp = std::strtoll(c, &e, 0); + if ( tmp > std::numeric_limits::max() + || tmp < std::numeric_limits::min()) + return false; + value = static_cast(tmp); return (c != e); } }; @@ -380,7 +402,11 @@ namespace cppcore { char *e = nullptr; const char *c = s.c_str(); - value = static_cast(std::strtold(c, &e)); + auto tmp = std::strtold(c, &e); + if ( tmp > static_cast(std::numeric_limits::max()) + || tmp < static_cast(std::numeric_limits::min())) + return false; + value = static_cast(tmp); return (c != e); } }; diff --git a/include/cppcore/misc/stream.inl b/include/cppcore/misc/stream.inl index 233ccee..cf2a0d7 100644 --- a/include/cppcore/misc/stream.inl +++ b/include/cppcore/misc/stream.inl @@ -1,5 +1,7 @@ #pragma once +#include + #include "stream.h" namespace cppcore @@ -123,7 +125,7 @@ namespace cppcore { auto pos = is.tellg(); t.deserialize(is); - return static_cast(is.tellg() - pos); + return convert_cast(is.tellg() - pos); } }; @@ -150,10 +152,10 @@ namespace cppcore { if (t.size() > std::numeric_limits::max()) throw exception("unable to write data to stream: string is to large"); - op_stream_write()(os, static_cast(t.size())); + op_stream_write()(os, convert_cast(t.size())); if (!os) throw exception("unable to write data to stream: invalid stream"); - os.write(t.data(), static_cast(t.size())); + os.write(t.data(), convert_cast(t.size())); if (!os) throw exception("unable to write data to stream: stream error"); return sizeof(uint32_t) + t.size(); @@ -167,7 +169,7 @@ namespace cppcore { auto pos = os.tellp(); t.serialize(os); - return static_cast(os.tellp() - pos); + return convert_cast(os.tellp() - pos); } }; diff --git a/include/cppcore/misc/vector_streambuf.inl b/include/cppcore/misc/vector_streambuf.inl index 356a2ce..ee0811d 100644 --- a/include/cppcore/misc/vector_streambuf.inl +++ b/include/cppcore/misc/vector_streambuf.inl @@ -1,5 +1,7 @@ #pragma once +#include + #include "vector_streambuf.h" namespace cppcore @@ -18,7 +20,7 @@ namespace cppcore ::get() const { auto vec = _buffer; - vec.resize(static_cast(this->pptr() - this->pbase())); + vec.resize(convert_cast(this->pptr() - this->pbase())); return vec; } @@ -40,7 +42,7 @@ namespace cppcore basic_vector_streambuf ::extract() { - _buffer.resize(static_cast(this->pptr() - this->pbase())); + _buffer.resize(convert_cast(this->pptr() - this->pbase())); auto ret = std::move(_buffer); this->setp(nullptr, nullptr); this->setg(nullptr, nullptr, nullptr); @@ -52,11 +54,11 @@ namespace cppcore basic_vector_streambuf ::underflow() { - auto gpos = static_cast(this->gptr() - this->eback()); + auto gpos = convert_cast(this->gptr() - this->eback()); this->setg(this->eback(), this->eback() + gpos, this->pptr()); if (this->gptr() == this->egptr()) return traits_type::eof(); - auto ret = static_cast(*this->gptr()); + auto ret = convert_cast(*this->gptr()); return ret; } @@ -80,15 +82,15 @@ namespace cppcore auto beg = reinterpret_cast(&_buffer.front()); auto end = beg + _buffer.size(); - auto ppos = static_cast(this->pptr() - this->pbase()); - auto gpos = static_cast(this->gptr() - this->eback()); + auto ppos = convert_cast(this->pptr() - this->pbase()); + auto gpos = convert_cast(this->gptr() - this->eback()); this->setp(beg, end); this->setg(beg, beg + gpos, beg + ppos); this->pbump(ppos); auto p = this->pptr(); assert(this->pbase() <= p && p < this->epptr()); - *p = static_cast(ch); + *p = convert_cast(ch); this->pbump(1); } return ch; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d2d6d61..3dfcfe8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,11 +6,21 @@ Include ( strip_symbols OPTIONAL RESULT_VARIABLE HAS_STRIP_S # Interface Library ############################################################################### +If ( "${CPPCORE_CONVERT_CAST}" STREQUAL "abort" ) + Set ( CPPCORE_CONVERT_CAST_ABORT 1 ) +ElseIf ( "${CPPCORE_CONVERT_CAST}" STREQUAL "throw" ) + Set ( CPPCORE_CONVERT_CAST_THROW 1 ) +EndIf ( ) + +Set ( CPPCORE_GENERATED_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated ) +Configure_File ( ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/config.h.in + ${CPPCORE_GENERATED_INCLUDE_DIR}/cppcore/config.h ) Set ( CPPCORE_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include ) Add_Library ( cppcore INTERFACE ) Target_Include_Directories ( cppcore INTERFACE $ + $ $ ) # Install ######################################################################################### @@ -24,6 +34,8 @@ If ( CPPCORE_INSTALL_HEADER ) DESTINATION ${CPPCORE_INSTALL_DIR_INCLUDE} ) Install ( DIRECTORY ${CPPCORE_INCLUDE_DIR}/cppcore DESTINATION ${CPPCORE_INSTALL_DIR_INCLUDE} ) + Install ( DIRECTORY ${CPPCORE_GENERATED_INCLUDE_DIR}/cppcore + DESTINATION ${CPPCORE_INSTALL_DIR_INCLUDE} ) Install ( TARGETS cppcore EXPORT cppcore DESTINATION ${CPPCORE_INSTALL_DIR_INCLUDE} ) diff --git a/test/cppcore/conversion/convert_cast_tests.cpp b/test/cppcore/conversion/convert_cast_tests.cpp new file mode 100644 index 0000000..67ef94a --- /dev/null +++ b/test/cppcore/conversion/convert_cast_tests.cpp @@ -0,0 +1,182 @@ +#include + +#define CPPCORE_CONVERT_CAST_THROW +#include + +using namespace ::cppcore; + +enum class test_enum +{ + test0 = 0, + test1, + test2, + test256 = 256, +}; + +TEST(convert_cast_test, i8) +{ + EXPECT_THROW(convert_cast(static_cast< int16_t>(128)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int32_t>(128)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int64_t>(128)), convert_exception); + + EXPECT_THROW(convert_cast(static_cast< uint8_t>(128)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(128)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(128)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(128)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, u8) +{ + EXPECT_THROW(convert_cast(static_cast< int8_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int16_t>(256)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int32_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int64_t>(256)), convert_exception); + + EXPECT_THROW(convert_cast(static_cast(256)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(256)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(256)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, i16) +{ + EXPECT_THROW(convert_cast(static_cast< int32_t>(32'768)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int64_t>(32'768)), convert_exception); + + EXPECT_THROW(convert_cast(static_cast(32'768)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(32'768)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(32'768)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, u16) +{ + EXPECT_THROW(convert_cast(static_cast< int8_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int16_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int32_t>(65'536)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int64_t>(65'536)), convert_exception); + + EXPECT_THROW(convert_cast(static_cast(65'536)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(65'536)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, i32) +{ + EXPECT_THROW(convert_cast(static_cast< int64_t>(2'147'483'648)), convert_exception); + + EXPECT_THROW(convert_cast(static_cast(2'147'483'648)), convert_exception); + EXPECT_THROW(convert_cast(static_cast(2'147'483'648)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, u32) +{ + EXPECT_THROW(convert_cast(static_cast< int8_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int16_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int32_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int64_t>(4'294'967'296)), convert_exception); + + EXPECT_THROW(convert_cast(static_cast(4'294'967'296)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, i64) +{ + EXPECT_THROW(convert_cast(static_cast(std::numeric_limits::max()) + 1), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, u64) +{ + EXPECT_THROW(convert_cast(static_cast< int8_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int16_t>( -1)), convert_exception); + EXPECT_THROW(convert_cast(static_cast< int32_t>( -1)), convert_exception); + + EXPECT_EQ (convert_cast(static_cast< int8_t>(1)), static_cast(1)); + EXPECT_EQ (convert_cast(static_cast< int16_t>(2)), static_cast(2)); + EXPECT_EQ (convert_cast(static_cast< int32_t>(3)), static_cast(3)); + EXPECT_EQ (convert_cast(static_cast< int64_t>(4)), static_cast(4)); + + EXPECT_EQ (convert_cast(static_cast< uint8_t>(4)), static_cast(4)); + EXPECT_EQ (convert_cast(static_cast(5)), static_cast(5)); + EXPECT_EQ (convert_cast(static_cast(6)), static_cast(6)); + EXPECT_EQ (convert_cast(static_cast(7)), static_cast(7)); +} + +TEST(convert_cast_test, enum) +{ + EXPECT_EQ(convert_cast< int8_t>( test_enum::test0), static_cast< int8_t>( 0)); + EXPECT_EQ(convert_cast( test_enum::test1), static_cast( 1)); + EXPECT_EQ(convert_cast( test_enum::test2), static_cast( 2)); + EXPECT_EQ(convert_cast(test_enum::test256), static_cast(256)); + + EXPECT_EQ(convert_cast< uint8_t>( test_enum::test0), static_cast< uint8_t>( 0)); + EXPECT_EQ(convert_cast( test_enum::test1), static_cast( 1)); + EXPECT_EQ(convert_cast( test_enum::test2), static_cast( 2)); + EXPECT_EQ(convert_cast(test_enum::test256), static_cast(256)); +}