From 42306e2ff8716ce9a8f57da791122cc88308890c Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Thu, 20 Jun 2019 19:48:53 -0400 Subject: [PATCH] QUtil: add unsigned int/string functions --- ChangeLog | 4 ++ include/qpdf/QUtil.hh | 8 +++ libqpdf/QUtil.cc | 100 ++++++++++++++++++++++++--------- libtests/qtest/qutil/qutil.out | 13 +++-- libtests/qutil.cc | 29 ++++++++-- 5 files changed, 119 insertions(+), 35 deletions(-) diff --git a/ChangeLog b/ChangeLog index d88fad35..d51a92c5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2019-06-20 Jay Berkenbilt + * Add functions to QUtil to convert unsigned integers to strings, + avoiding implicit conversion between unsigned and signed integer + types. + * Add QIC.hh, containing integer type converters that do range checking. diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh index 02dec5ad..afdd2033 100644 --- a/include/qpdf/QUtil.hh +++ b/include/qpdf/QUtil.hh @@ -40,8 +40,12 @@ namespace QUtil QPDF_DLL std::string int_to_string(long long, int length = 0); QPDF_DLL + std::string uint_to_string(unsigned long long, int length = 0); + QPDF_DLL std::string int_to_string_base(long long, int base, int length = 0); QPDF_DLL + std::string uint_to_string_base(unsigned long long, int base, int length = 0); + QPDF_DLL std::string double_to_string(double, int decimal_places = 0); // These string to number methods throw std::runtime_error on @@ -50,6 +54,10 @@ namespace QUtil long long string_to_ll(char const* str); QPDF_DLL int string_to_int(char const* str); + QPDF_DLL + unsigned long long string_to_ull(char const* str); + QPDF_DLL + unsigned int string_to_uint(char const* str); // Pipeline's write method wants unsigned char*, but we often have // some other type of string. These methods do combinations of diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc index 17e958ca..dee3a636 100644 --- a/libqpdf/QUtil.cc +++ b/libqpdf/QUtil.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -233,14 +234,10 @@ static unsigned short mac_roman_to_unicode[] = { 0x02c7, // 0xff }; +template +static std::string -QUtil::int_to_string(long long num, int length) -{ - return int_to_string_base(num, 10, length); -} - -std::string -QUtil::int_to_string_base(long long num, int base, int length) +int_to_string_base_internal(T num, int base, int length) { // Backward compatibility -- int_to_string, which calls this // function, used to use sprintf with %0*d, so we interpret length @@ -255,18 +252,42 @@ QUtil::int_to_string_base(long long num, int base, int length) buf << std::setbase(base) << std::nouppercase << num; std::string result; if ((length > 0) && - (buf.str().length() < static_cast(length))) + (buf.str().length() < QIntC::to_size(length))) { result.append(length - buf.str().length(), '0'); } result += buf.str(); - if ((length < 0) && (buf.str().length() < static_cast(-length))) + if ((length < 0) && (buf.str().length() < QIntC::to_size(-length))) { result.append(-length - buf.str().length(), ' '); } return result; } +std::string +QUtil::int_to_string(long long num, int length) +{ + return int_to_string_base(num, 10, length); +} + +std::string +QUtil::uint_to_string(unsigned long long num, int length) +{ + return int_to_string_base(num, 10, length); +} + +std::string +QUtil::int_to_string_base(long long num, int base, int length) +{ + return int_to_string_base_internal(num, base, length); +} + +std::string +QUtil::uint_to_string_base(unsigned long long num, int base, int length) +{ + return int_to_string_base_internal(num, base, length); +} + std::string QUtil::double_to_string(double num, int decimal_places) { @@ -294,7 +315,7 @@ QUtil::string_to_ll(char const* str) #endif if (errno == ERANGE) { - throw std::runtime_error( + throw std::range_error( std::string("overflow/underflow converting ") + str + " to 64-bit integer"); } @@ -304,24 +325,47 @@ QUtil::string_to_ll(char const* str) int QUtil::string_to_int(char const* str) { + // QIntC::to_int does range checking + return QIntC::to_int(string_to_ll(str)); +} + +unsigned long long +QUtil::string_to_ull(char const* str) +{ + char const* p = str; + while (*p && is_space(*p)) + { + ++p; + } + if (*p == '-') + { + throw std::runtime_error( + std::string("underflow converting ") + str + + " to 64-bit unsigned integer"); + } + errno = 0; - long long_val = strtol(str, 0, 10); +#ifdef _MSC_VER + unsigned long long result = _strtoui64(str, 0, 10); +#else + unsigned long long result = strtoull(str, 0, 10); +#endif if (errno == ERANGE) { throw std::runtime_error( - std::string("overflow/underflow converting ") + str - + " to long integer"); - } - int result = static_cast(long_val); - if (static_cast(result) != long_val) - { - throw std::runtime_error( - std::string("overflow/underflow converting ") + str - + " to integer"); + std::string("overflow converting ") + str + + " to 64-bit unsigned integer"); } return result; } +unsigned int +QUtil::string_to_uint(char const* str) +{ + // QIntC::to_uint does range checking + return QIntC::to_uint(string_to_ull(str)); +} + unsigned char* QUtil::unsigned_char_pointer(std::string const& str) { @@ -412,14 +456,18 @@ int QUtil::seek(FILE* stream, qpdf_offset_t offset, int whence) { #if HAVE_FSEEKO - return fseeko(stream, static_cast(offset), whence); + return fseeko(stream, + QIntC::IntConverter::convert(offset), + whence); #elif HAVE_FSEEKO64 return fseeko64(stream, offset, whence); #else # if defined _MSC_VER || defined __BORLANDC__ return _fseeki64(stream, offset, whence); # else - return fseek(stream, static_cast(offset), whence); + return fseek(stream, + QIntC::IntConverter(offset), + whence); # endif #endif } @@ -428,14 +476,14 @@ qpdf_offset_t QUtil::tell(FILE* stream) { #if HAVE_FSEEKO - return static_cast(ftello(stream)); + return QIntC::to_offset(ftello(stream)); #elif HAVE_FSEEKO64 - return static_cast(ftello64(stream)); + return QIntC::to_offset(ftello64(stream)); #else # if defined _MSC_VER || defined __BORLANDC__ return _ftelli64(stream); # else - return static_cast(ftell(stream)); + return QIntC::to_offset(ftell(stream)); # endif #endif } @@ -508,7 +556,7 @@ QUtil::hex_encode(std::string const& input) for (unsigned int i = 0; i < input.length(); ++i) { result += QUtil::int_to_string_base( - static_cast(static_cast(input.at(i))), 16, 2); + QIntC::to_int(static_cast(input.at(i))), 16, 2); } return result; } diff --git a/libtests/qtest/qutil/qutil.out b/libtests/qtest/qutil/qutil.out index c35f22e3..4d19617d 100644 --- a/libtests/qtest/qutil/qutil.out +++ b/libtests/qtest/qutil/qutil.out @@ -17,12 +17,17 @@ one compare okay -2147483648 to int: PASSED 2147483647 to int: PASSED -2147483648 to int threw: PASSED --2147483649 to int threw: PASSED -9999999999999999999999999 to int threw: PASSED +2147483648 to int threw (integer out of range converting 2147483648 from a 8-byte signed type to a 4-byte signed type): PASSED +-2147483649 to int threw (integer out of range converting -2147483649 from a 8-byte signed type to a 4-byte signed type): PASSED +9999999999999999999999999 to int threw (overflow/underflow converting 9999999999999999999999999 to 64-bit integer): PASSED 2147483648 to int: PASSED -2147483649 to int: PASSED -99999999999999999999999999999999999999999999999999 to int threw: PASSED +99999999999999999999999999999999999999999999999999 to int threw (overflow/underflow converting 99999999999999999999999999999999999999999999999999 to 64-bit integer): PASSED +16059 to int: PASSED +-16059 to int threw (underflow converting -16059 to 64-bit unsigned integer): PASSED +9999999999 to int threw (integer out of range converting 9999999999 from a 8-byte unsigned type to a 4-byte unsigned type): PASSED +16059 to int: PASSED +-16059 to int threw (underflow converting -16059 to 64-bit unsigned integer): PASSED ---- os wrapper before remove exception: remove file: No such file or directory diff --git a/libtests/qutil.cc b/libtests/qutil.cc index 27881c6e..0e0a063b 100644 --- a/libtests/qutil.cc +++ b/libtests/qutil.cc @@ -23,20 +23,22 @@ void test_to_number(char const* str, int_T wanted, bool error, bool threw = false; bool worked = false; int_T result = 0; + std::string msg; try { result = fn(str); worked = (wanted == result); } - catch (std::runtime_error const&) + catch (std::runtime_error const& e) { threw = true; + msg = e.what(); } if (threw) { if (error) { - std::cout << str << " to int threw: PASSED" << std::endl; + std::cout << str << " to int threw (" << msg << "): PASSED" << std::endl; } else { @@ -67,6 +69,16 @@ void test_to_ll(char const* str, long long wanted, bool error) test_to_number(str, wanted, error, QUtil::string_to_ll); } +void test_to_uint(char const* str, unsigned int wanted, bool error) +{ + test_to_number(str, wanted, error, QUtil::string_to_uint); +} + +void test_to_ull(char const* str, unsigned long long wanted, bool error) +{ + test_to_number(str, wanted, error, QUtil::string_to_ull); +} + void string_conversion_test() { std::cout << QUtil::int_to_string(16059) << std::endl @@ -105,6 +117,8 @@ void string_conversion_test() long long int_min_minus_1 = static_cast(INT_MIN) - 1; std::string int_max_plus_1_str = QUtil::int_to_string(int_max_plus_1); std::string int_min_minus_1_str = QUtil::int_to_string(int_min_minus_1); + std::string small_positive = QUtil::uint_to_string(16059U); + std::string small_negative = QUtil::int_to_string(-16059); test_to_int(int_min_str.c_str(), INT_MIN, false); test_to_int(int_max_str.c_str(), INT_MAX, false); test_to_int(int_max_plus_1_str.c_str(), 0, true); @@ -113,6 +127,11 @@ void string_conversion_test() test_to_ll(int_max_plus_1_str.c_str(), int_max_plus_1, false); test_to_ll(int_min_minus_1_str.c_str(), int_min_minus_1, false); test_to_ll("99999999999999999999999999999999999999999999999999", 0, true); + test_to_uint(small_positive.c_str(), 16059U, false); + test_to_uint(small_negative.c_str(), 0, true); + test_to_uint("9999999999", 0, true); + test_to_ull(small_positive.c_str(), 16059U, false); + test_to_ull(small_negative.c_str(), 0, true); } void os_wrapper_test() @@ -159,7 +178,7 @@ void getenv_test() static void print_utf8(unsigned long val) { std::string result = QUtil::toUTF8(val); - std::cout << "0x" << QUtil::int_to_string_base(val, 16) << " ->"; + std::cout << "0x" << QUtil::uint_to_string_base(val, 16) << " ->"; if (val < 0xfffe) { std::cout << " " << result; @@ -199,7 +218,7 @@ void to_utf8_test() static void print_utf16(unsigned long val) { std::string result = QUtil::toUTF16(val); - std::cout << "0x" << QUtil::int_to_string_base(val, 16) << " ->"; + std::cout << "0x" << QUtil::uint_to_string_base(val, 16) << " ->"; for (std::string::iterator iter = result.begin(); iter != result.end(); ++iter) { @@ -249,7 +268,7 @@ void transcoding_test(std::string (*to_utf8)(std::string const&), std::string back; for (int i = 128; i <= last; ++i) { - in.at(0) = static_cast(i); + in.at(0) = static_cast(static_cast(i)); out = (*to_utf8)(in); std::string wanted = (out == "\xef\xbf\xbd") ? unknown : in; back = (*from_utf8)(out, '?');