diff --git a/util/stdlib/string_number_conversion.cc b/util/stdlib/string_number_conversion.cc index 859a8333..64211844 100644 --- a/util/stdlib/string_number_conversion.cc +++ b/util/stdlib/string_number_conversion.cc @@ -97,15 +97,34 @@ struct StringToUnsignedIntTraits } }; -struct StringToInt64Traits - : public StringToSignedIntegerTraits { +struct StringToLongTraits + : public StringToSignedIntegerTraits { static LongType Convert(const char* str, char** end, int base) { return strtoll(str, end, base); } }; -struct StringToUnsignedInt64Traits - : public StringToUnsignedIntegerTraits { +struct StringToUnsignedLongTraits + : public StringToUnsignedIntegerTraits { + static LongType Convert(const char* str, char** end, int base) { + if (str[0] == '-') { + *end = const_cast(str); + return 0; + } + return strtoull(str, end, base); + } +}; + +struct StringToLongLongTraits + : public StringToSignedIntegerTraits { + static LongType Convert(const char* str, char** end, int base) { + return strtoll(str, end, base); + } +}; + +struct StringToUnsignedLongLongTraits + : public StringToUnsignedIntegerTraits { static LongType Convert(const char* str, char** end, int base) { if (str[0] == '-') { *end = const_cast(str); @@ -136,7 +155,7 @@ bool StringToIntegerInternal(const std::string& string, end != string.data() + string.length()) { return false; } - *number = result; + *number = static_cast(result); return true; } @@ -152,12 +171,21 @@ bool StringToNumber(const std::string& string, unsigned int* number) { return StringToIntegerInternal(string, number); } -bool StringToNumber(const std::string& string, int64_t* number) { - return StringToIntegerInternal(string, number); +bool StringToNumber(const std::string& string, long* number) { + return StringToIntegerInternal(string, number); } -bool StringToNumber(const std::string& string, uint64_t* number) { - return StringToIntegerInternal(string, number); +bool StringToNumber(const std::string& string, unsigned long* number) { + return StringToIntegerInternal(string, number); +} + +bool StringToNumber(const std::string& string, long long* number) { + return StringToIntegerInternal(string, number); +} + +bool StringToNumber(const std::string& string, unsigned long long* number) { + return StringToIntegerInternal(string, + number); } } // namespace crashpad diff --git a/util/stdlib/string_number_conversion.h b/util/stdlib/string_number_conversion.h index b5f1d44a..69e7fdd6 100644 --- a/util/stdlib/string_number_conversion.h +++ b/util/stdlib/string_number_conversion.h @@ -56,8 +56,10 @@ namespace crashpad { //! where such prefix recognition is desirable. bool StringToNumber(const std::string& string, int* number); bool StringToNumber(const std::string& string, unsigned int* number); -bool StringToNumber(const std::string& string, int64_t* number); -bool StringToNumber(const std::string& string, uint64_t* number); +bool StringToNumber(const std::string& string, long* number); +bool StringToNumber(const std::string& string, unsigned long* number); +bool StringToNumber(const std::string& string, long long* number); +bool StringToNumber(const std::string& string, unsigned long long* number); //! \} } // namespace crashpad diff --git a/util/stdlib/string_number_conversion_test.cc b/util/stdlib/string_number_conversion_test.cc index 689b4ad5..760dc4a8 100644 --- a/util/stdlib/string_number_conversion_test.cc +++ b/util/stdlib/string_number_conversion_test.cc @@ -16,30 +16,39 @@ #include +#include #include +#include #include "base/stl_util.h" #include "gtest/gtest.h" -namespace crashpad { -namespace test { -namespace { +#define STRINGIFY(a) STR(a) +#define STR(a) #a -TEST(StringNumberConversion, StringToInt) { - static constexpr struct { - const char* string; - bool valid; - int value; - } kTestData[] = { +template +struct TestSpecification { + const char* string; + bool valid; + TValueType value; +}; + +// Signed 32-bit test data +template ::value && + std::is_signed::value && + (sizeof(TIntType) == 4)>::type* = nullptr> +static constexpr std::array, 61> kTestDataFunc() { + return {{ {"", false, 0}, {"0", true, 0}, {"1", true, 1}, - {"2147483647", true, std::numeric_limits::max()}, + {"2147483647", true, std::numeric_limits::max()}, {"2147483648", false, 0}, {"4294967295", false, 0}, {"4294967296", false, 0}, {"-1", true, -1}, - {"-2147483648", true, std::numeric_limits::min()}, + {"-2147483648", true, std::numeric_limits::min()}, {"-2147483649", false, 0}, {"00", true, 0}, {"01", true, 1}, @@ -50,12 +59,12 @@ TEST(StringNumberConversion, StringToInt) { {"+0x20", true, 32}, {"0xf", true, 15}, {"0xg", false, 0}, - {"0x7fffffff", true, std::numeric_limits::max()}, - {"0x7FfFfFfF", true, std::numeric_limits::max()}, + {"0x7fffffff", true, std::numeric_limits::max()}, + {"0x7FfFfFfF", true, std::numeric_limits::max()}, {"0x80000000", false, 0}, {"0xFFFFFFFF", false, 0}, {"-0x7fffffff", true, -2147483647}, - {"-0x80000000", true, std::numeric_limits::min()}, + {"-0x80000000", true, std::numeric_limits::min()}, {"-0x80000001", false, 0}, {"-0xffffffff", false, 0}, {"0x100000000", false, 0}, @@ -92,45 +101,22 @@ TEST(StringNumberConversion, StringToInt) { {"9223372036854775809", false, 0}, {"18446744073709551615", false, 0}, {"18446744073709551616", false, 0}, - }; - - for (size_t index = 0; index < base::size(kTestData); ++index) { - int value; - bool valid = StringToNumber(kTestData[index].string, &value); - if (kTestData[index].valid) { - EXPECT_TRUE(valid) << "index " << index << ", string " - << kTestData[index].string; - if (valid) { - EXPECT_EQ(value, kTestData[index].value) - << "index " << index << ", string " << kTestData[index].string; - } - } else { - EXPECT_FALSE(valid) << "index " << index << ", string " - << kTestData[index].string << ", value " << value; - } - } - - // Ensure that embedded NUL characters are treated as bad input. The string - // is split to avoid MSVC warning: - // "decimal digit terminates octal escape sequence". - static constexpr char input[] = "6\000" "6"; - std::string input_string(input, base::size(input) - 1); - int output; - EXPECT_FALSE(StringToNumber(input_string, &output)); + }}; } -TEST(StringNumberConversion, StringToUnsignedInt) { - static constexpr struct { - const char* string; - bool valid; - unsigned int value; - } kTestData[] = { +// Unsigned 32-bit test data +template ::value && + !std::is_signed::value && + (sizeof(TIntType) == 4)>::type* = nullptr> +static constexpr std::array, 61> kTestDataFunc() { + return {{ {"", false, 0}, {"0", true, 0}, {"1", true, 1}, {"2147483647", true, 2147483647}, {"2147483648", true, 2147483648}, - {"4294967295", true, std::numeric_limits::max()}, + {"4294967295", true, std::numeric_limits::max()}, {"4294967296", false, 0}, {"-1", false, 0}, {"-2147483648", false, 0}, @@ -186,9 +172,121 @@ TEST(StringNumberConversion, StringToUnsignedInt) { {"9223372036854775809", false, 0}, {"18446744073709551615", false, 0}, {"18446744073709551616", false, 0}, - }; + }}; +} - for (size_t index = 0; index < base::size(kTestData); ++index) { +// Signed 64-bit test data +template ::value && + std::is_signed::value && + (sizeof(TIntType) == 8)>::type* = nullptr> +static constexpr std::array, 24> kTestDataFunc() { + return {{ + {"", false, 0}, + {"0", true, 0}, + {"1", true, 1}, + {"2147483647", true, 2147483647}, + {"2147483648", true, 2147483648}, + {"4294967295", true, 4294967295}, + {"4294967296", true, 4294967296}, + {"9223372036854775807", true, std::numeric_limits::max()}, + {"9223372036854775808", false, 0}, + {"18446744073709551615", false, 0}, + {"18446744073709551616", false, 0}, + {"-1", true, -1}, + {"-2147483648", true, INT64_C(-2147483648)}, + {"-2147483649", true, INT64_C(-2147483649)}, + {"-9223372036854775808", true, std::numeric_limits::min()}, + {"-9223372036854775809", false, 0}, + {"0x7fffffffffffffff", true, std::numeric_limits::max()}, + {"0x8000000000000000", false, 0}, + {"0xffffffffffffffff", false, 0}, + {"0x10000000000000000", false, 0}, + {"-0x7fffffffffffffff", true, -9223372036854775807}, + {"-0x8000000000000000", true, std::numeric_limits::min()}, + {"-0x8000000000000001", false, 0}, + {"0x7Fffffffffffffff", true, std::numeric_limits::max()}, + }}; +} + +// Unsigned 64-bit test data +template ::value && + !std::is_signed::value && + (sizeof(TIntType) == 8)>::type* = nullptr> +static constexpr std::array, 25> kTestDataFunc() { + return {{ + {"", false, 0}, + {"0", true, 0}, + {"1", true, 1}, + {"2147483647", true, 2147483647}, + {"2147483648", true, 2147483648}, + {"4294967295", true, 4294967295}, + {"4294967296", true, 4294967296}, + {"9223372036854775807", true, 9223372036854775807}, + {"9223372036854775808", true, 9223372036854775808u}, + {"18446744073709551615", true, std::numeric_limits::max()}, + {"18446744073709551616", false, 0}, + {"-1", false, 0}, + {"-2147483648", false, 0}, + {"-2147483649", false, 0}, + {"-2147483648", false, 0}, + {"-9223372036854775808", false, 0}, + {"-9223372036854775809", false, 0}, + {"0x7fffffffffffffff", true, 9223372036854775807}, + {"0x8000000000000000", true, 9223372036854775808u}, + {"0xffffffffffffffff", true, std::numeric_limits::max()}, + {"0x10000000000000000", false, 0}, + {"-0x7fffffffffffffff", false, 0}, + {"-0x8000000000000000", false, 0}, + {"-0x8000000000000001", false, 0}, + {"0xFfffffffffffffff", true, std::numeric_limits::max()}, + }}; +} + +// This string is split to avoid MSVC warning: +// "decimal digit terminates octal escape sequence". +static constexpr char kEmbeddedNullInputRaw[] = "6\000" "6"; + +namespace crashpad { +namespace test { +namespace { + +TEST(StringNumberConversion, StringToInt) { + static_assert(sizeof(int) == 4, "Test only configured for 32-bit int."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { + int value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(value, kTestData[index].value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } + + // Ensure that embedded NUL characters are treated as bad input. The string + // is split to avoid MSVC warning: + // "decimal digit terminates octal escape sequence". + int output; + std::string kEmbeddedNullInput(kEmbeddedNullInputRaw, + base::size(kEmbeddedNullInputRaw) - 1); + EXPECT_FALSE(StringToNumber(kEmbeddedNullInput, &output)); +} + +TEST(StringNumberConversion, StringToUnsignedInt) { + static_assert(sizeof(unsigned int) == 4, + "Test only configured for 32-bit unsigned int."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { unsigned int value; bool valid = StringToNumber(kTestData[index].string, &value); if (kTestData[index].valid) { @@ -207,46 +305,20 @@ TEST(StringNumberConversion, StringToUnsignedInt) { // Ensure that embedded NUL characters are treated as bad input. The string // is split to avoid MSVC warning: // "decimal digit terminates octal escape sequence". - static constexpr char input[] = "6\000" "6"; - std::string input_string(input, base::size(input) - 1); unsigned int output; - EXPECT_FALSE(StringToNumber(input_string, &output)); + std::string kEmbeddedNullInput(kEmbeddedNullInputRaw, + base::size(kEmbeddedNullInputRaw) - 1); + EXPECT_FALSE(StringToNumber(kEmbeddedNullInput, &output)); } -TEST(StringNumberConversion, StringToInt64) { - static constexpr struct { - const char* string; - bool valid; - int64_t value; - } kTestData[] = { - {"", false, 0}, - {"0", true, 0}, - {"1", true, 1}, - {"2147483647", true, 2147483647}, - {"2147483648", true, 2147483648}, - {"4294967295", true, 4294967295}, - {"4294967296", true, 4294967296}, - {"9223372036854775807", true, std::numeric_limits::max()}, - {"9223372036854775808", false, 0}, - {"18446744073709551615", false, 0}, - {"18446744073709551616", false, 0}, - {"-1", true, -1}, - {"-2147483648", true, INT64_C(-2147483648)}, - {"-2147483649", true, INT64_C(-2147483649)}, - {"-9223372036854775808", true, std::numeric_limits::min()}, - {"-9223372036854775809", false, 0}, - {"0x7fffffffffffffff", true, std::numeric_limits::max()}, - {"0x8000000000000000", false, 0}, - {"0xffffffffffffffff", false, 0}, - {"0x10000000000000000", false, 0}, - {"-0x7fffffffffffffff", true, -9223372036854775807}, - {"-0x8000000000000000", true, std::numeric_limits::min()}, - {"-0x8000000000000001", false, 0}, - {"0x7Fffffffffffffff", true, std::numeric_limits::max()}, - }; +TEST(StringNumberConversion, StringToLong) { + static_assert( + sizeof(long) == 4 || sizeof(long) == 8, + "Test not configured for " STRINGIFY(__SIZEOF_LONG__) "-byte long"); + static constexpr auto kTestData = kTestDataFunc(); - for (size_t index = 0; index < base::size(kTestData); ++index) { - int64_t value; + for (size_t index = 0; index < kTestData.size(); ++index) { + long value; bool valid = StringToNumber(kTestData[index].string, &value); if (kTestData[index].valid) { EXPECT_TRUE(valid) << "index " << index << ", string " @@ -262,41 +334,58 @@ TEST(StringNumberConversion, StringToInt64) { } } -TEST(StringNumberConversion, StringToUnsignedInt64) { - static constexpr struct { - const char* string; - bool valid; - uint64_t value; - } kTestData[] = { - {"", false, 0}, - {"0", true, 0}, - {"1", true, 1}, - {"2147483647", true, 2147483647}, - {"2147483648", true, 2147483648}, - {"4294967295", true, 4294967295}, - {"4294967296", true, 4294967296}, - {"9223372036854775807", true, 9223372036854775807}, - {"9223372036854775808", true, 9223372036854775808u}, - {"18446744073709551615", true, std::numeric_limits::max()}, - {"18446744073709551616", false, 0}, - {"-1", false, 0}, - {"-2147483648", false, 0}, - {"-2147483649", false, 0}, - {"-2147483648", false, 0}, - {"-9223372036854775808", false, 0}, - {"-9223372036854775809", false, 0}, - {"0x7fffffffffffffff", true, 9223372036854775807}, - {"0x8000000000000000", true, 9223372036854775808u}, - {"0xffffffffffffffff", true, std::numeric_limits::max()}, - {"0x10000000000000000", false, 0}, - {"-0x7fffffffffffffff", false, 0}, - {"-0x8000000000000000", false, 0}, - {"-0x8000000000000001", false, 0}, - {"0xFfffffffffffffff", true, std::numeric_limits::max()}, - }; +TEST(StringNumberConversion, StringToUnsignedLong) { + static_assert( + sizeof(long) == 4 || sizeof(long) == 8, + "Test not configured for " STRINGIFY(__SIZEOF_LONG__) "-byte long"); + static constexpr auto kTestData = kTestDataFunc(); - for (size_t index = 0; index < base::size(kTestData); ++index) { - uint64_t value; + for (size_t index = 0; index < kTestData.size(); ++index) { + unsigned long value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(value, kTestData[index].value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } +} + +TEST(StringNumberConversion, StringToLongLong) { + static_assert(sizeof(long long) == 8, + "Test only configured for 64-bit long long."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { + long long value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(value, kTestData[index].value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } +} + +TEST(StringNumberConversion, StringToUnsignedLongLong) { + static_assert(sizeof(unsigned long long) == 8, + "Test only configured for 64-bit unsigned long long."); + static constexpr auto kTestData = kTestDataFunc(); + + for (size_t index = 0; index < kTestData.size(); ++index) { + unsigned long long value; bool valid = StringToNumber(kTestData[index].string, &value); if (kTestData[index].valid) { EXPECT_TRUE(valid) << "index " << index << ", string "