diff --git a/Utilities/File.cpp b/Utilities/File.cpp index a1af1265e5..66a40d3546 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -15,29 +15,35 @@ static std::unique_ptr to_wchar(const std::string& source) { - const auto buf_size = source.size() + 1; // size + null terminator + // String size + null terminator + const std::size_t buf_size = source.size() + 1; - const int size = source.size() < INT_MAX ? static_cast(buf_size) : (fmt::throw_exception("to_wchar(): invalid source length (0x%llx)", source.size()), 0); + // Safe size + const int size = narrow(buf_size, "to_wchar" HERE); - std::unique_ptr buffer(new wchar_t[buf_size]); // allocate buffer assuming that length is the max possible size + // Buffer for max possible output length + std::unique_ptr buffer(new wchar_t[buf_size]); verify("to_wchar" HERE), MultiByteToWideChar(CP_UTF8, 0, source.c_str(), size, buffer.get(), size); return buffer; } -static void to_utf8(std::string& result, const wchar_t* source) +static void to_utf8(std::string& out, const wchar_t* source) { - const auto length = std::wcslen(source); + // String size + const std::size_t length = std::wcslen(source); - const int buf_size = length <= INT_MAX / 3 ? static_cast(length) * 3 + 1 : (fmt::throw_exception("to_utf8(): invalid source length (0x%llx)", length), 0); + // Safe buffer size for max possible output length (including null terminator) + const int buf_size = narrow(length * 3 + 1, "to_utf8" HERE); - result.resize(buf_size); // set max possible length for utf-8 + null terminator + // Resize buffer + out.resize(buf_size - 1); - const int nwritten = verify(WideCharToMultiByte(CP_UTF8, 0, source, static_cast(length) + 1, &result.front(), buf_size, NULL, NULL), "to_utf8" HERE); + const int result = WideCharToMultiByte(CP_UTF8, 0, source, static_cast(length) + 1, &out.front(), buf_size, NULL, NULL); - // fix the size, remove null terminator - result.resize(nwritten - 1); + // Fix the size + out.resize(verify(result, "to_utf8" HERE) - 1); } static time_t to_time(const ULARGE_INTEGER& ft) @@ -722,8 +728,7 @@ bool fs::file::open(const std::string& path, bs_t mode) u64 read(void* buffer, u64 count) override { // TODO (call ReadFile multiple times if count is too big) - const int size = ::narrow(count, "Too big count" HERE); - EXPECTS(size >= 0); + const int size = narrow(count, "file::read" HERE); DWORD nread; verify("file::read" HERE), ReadFile(m_handle, buffer, size, &nread, NULL); @@ -734,8 +739,7 @@ bool fs::file::open(const std::string& path, bs_t mode) u64 write(const void* buffer, u64 count) override { // TODO (call WriteFile multiple times if count is too big) - const int size = ::narrow(count, "Too big count" HERE); - EXPECTS(size >= 0); + const int size = narrow(count, "file::write" HERE); DWORD nwritten; verify("file::write" HERE), WriteFile(m_handle, buffer, size, &nwritten, NULL); diff --git a/Utilities/types.h b/Utilities/types.h index 0b5c2b1b45..ea83a3f90f 100644 --- a/Utilities/types.h +++ b/Utilities/types.h @@ -574,19 +574,110 @@ inline std::remove_reference_t&& verify_move(T&& value, const char* cause, F& return std::move(value); } -// Narrow cast (throws on failure) +// narrow() function details +template +struct narrow_impl +{ + // Temporarily (diagnostic) + static_assert(std::is_void::value, "narrow_impl<> specialization not found"); + + // Returns true if value cannot be represented in type To + static constexpr bool test(const From& value) + { + // Unspecialized cases (including cast to void) always considered narrowing + return true; + } +}; + +// Unsigned to unsigned narrowing +template +struct narrow_impl::value && std::is_unsigned::value>> +{ + static constexpr bool test(const From& value) + { + return sizeof(To) < sizeof(From) && static_cast(value) != value; + } +}; + +// Signed to signed narrowing +template +struct narrow_impl::value && std::is_signed::value>> +{ + static constexpr bool test(const From& value) + { + return sizeof(To) < sizeof(From) && static_cast(value) != value; + } +}; + +// Unsigned to signed narrowing +template +struct narrow_impl::value && std::is_signed::value>> +{ + static constexpr bool test(const From& value) + { + return sizeof(To) <= sizeof(From) && value > (static_cast>(-1) >> 1); + } +}; + +// Signed to unsigned narrowing (I) +template +struct narrow_impl::value && std::is_unsigned::value && sizeof(To) >= sizeof(From)>> +{ + static constexpr bool test(const From& value) + { + return value < static_cast(0); + } +}; + +// Signed to unsigned narrowing (II) +template +struct narrow_impl::value && std::is_unsigned::value && sizeof(To) < sizeof(From)>> +{ + static constexpr bool test(const From& value) + { + return static_cast>(value) > static_cast(-1); + } +}; + +// Enum to integer (TODO?) +template +struct narrow_impl::value && std::is_integral::value>> + : narrow_impl, To> +{ +}; + +// Integer to enum (TODO?) +template +struct narrow_impl::value && std::is_enum::value>> + : narrow_impl> +{ +}; + +// Enum to enum (TODO?) +template +struct narrow_impl::value && std::is_enum::value>> + : narrow_impl, std::underlying_type_t> +{ +}; + +// Simple type enabled (TODO?) +template +struct narrow_impl> + : narrow_impl, To> +{ +}; + template (std::declval()))> inline To narrow(const From& value, const char* msg = nullptr) { - // Allow "narrowing to void" and ensure it always fails in this case - auto&& result = static_cast::value, From, To>>(value); - if (std::is_void::value || static_cast(result) != value) + // Narrow check + if (narrow_impl::test(value)) { // Pack value as formatting argument fmt::raw_narrow_error(msg, fmt::get_type_info::type>(), fmt_unveil::get(value)); } - return static_cast::value, void, decltype(result)>>(result); + return static_cast(value); } // Returns u32 size() for container