Add support for 8-bit and 16-bit integers to absl::SimpleAtoi Closes https://github.com/abseil/abseil-cpp/pull/1762 PiperOrigin-RevId: 730514635 Change-Id: I33aa7140a457a81bf7b0f878854fbc127258c439
diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index 83ea80b..f2e55fe 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc
@@ -1111,6 +1111,16 @@ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; +bool safe_strto8_base(absl::string_view text, absl::Nonnull<int8_t*> value, + int base) { + return safe_int_internal<int8_t>(text, value, base); +} + +bool safe_strto16_base(absl::string_view text, absl::Nonnull<int16_t*> value, + int base) { + return safe_int_internal<int16_t>(text, value, base); +} + bool safe_strto32_base(absl::string_view text, absl::Nonnull<int32_t*> value, int base) { return safe_int_internal<int32_t>(text, value, base); @@ -1126,6 +1136,16 @@ return safe_int_internal<absl::int128>(text, value, base); } +bool safe_strtou8_base(absl::string_view text, absl::Nonnull<uint8_t*> value, + int base) { + return safe_uint_internal<uint8_t>(text, value, base); +} + +bool safe_strtou16_base(absl::string_view text, absl::Nonnull<uint16_t*> value, + int base) { + return safe_uint_internal<uint16_t>(text, value, base); +} + bool safe_strtou32_base(absl::string_view text, absl::Nonnull<uint32_t*> value, int base) { return safe_uint_internal<uint32_t>(text, value, base);
diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h index 739dbb2..a3e0e6d 100644 --- a/absl/strings/numbers.h +++ b/absl/strings/numbers.h
@@ -32,6 +32,7 @@ #endif #include <cstddef> +#include <cstdint> #include <cstdlib> #include <cstring> #include <ctime> @@ -39,6 +40,7 @@ #include <string> #include <type_traits> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/macros.h" @@ -127,6 +129,18 @@ ABSL_NAMESPACE_BEGIN namespace numbers_internal { +template <typename int_type> +constexpr bool is_signed() { + if constexpr (std::is_arithmetic<int_type>::value) { + // Use std::numeric_limits<T>::is_signed where it's defined to work. + return std::numeric_limits<int_type>::is_signed; + } + // TODO(jorg): This signed-ness check is used because it works correctly + // with enums, and it also serves to check that int_type is not a pointer. + // If one day something like std::is_signed<enum E> works, switch to it. + return static_cast<int_type>(1) - 2 < 0; +} + // Digit conversion. ABSL_DLL extern const char kHexChar[17]; // 0123456789abcdef ABSL_DLL extern const char @@ -142,12 +156,20 @@ // safe_strto?() functions for implementing SimpleAtoi() +bool safe_strto8_base(absl::string_view text, absl::Nonnull<int8_t*> value, + int base); +bool safe_strto16_base(absl::string_view text, absl::Nonnull<int16_t*> value, + int base); bool safe_strto32_base(absl::string_view text, absl::Nonnull<int32_t*> value, int base); bool safe_strto64_base(absl::string_view text, absl::Nonnull<int64_t*> value, int base); bool safe_strto128_base(absl::string_view text, absl::Nonnull<absl::int128*> value, int base); +bool safe_strtou8_base(absl::string_view text, absl::Nonnull<uint8_t*> value, + int base); +bool safe_strtou16_base(absl::string_view text, absl::Nonnull<uint16_t*> value, + int base); bool safe_strtou32_base(absl::string_view text, absl::Nonnull<uint32_t*> value, int base); bool safe_strtou64_base(absl::string_view text, absl::Nonnull<uint64_t*> value, @@ -186,11 +208,8 @@ ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize) { static_assert(sizeof(i) <= 64 / 8, "FastIntToBuffer works only with 64-bit-or-less integers."); - // TODO(jorg): This signed-ness check is used because it works correctly - // with enums, and it also serves to check that int_type is not a pointer. - // If one day something like std::is_signed<enum E> works, switch to it. // These conditions are constexpr bools to suppress MSVC warning C4127. - constexpr bool kIsSigned = static_cast<int_type>(1) - 2 < 0; + constexpr bool kIsSigned = is_signed<int_type>(); constexpr bool kUse64Bit = sizeof(i) > 32 / 8; if (kIsSigned) { if (kUse64Bit) { @@ -213,36 +232,50 @@ ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s, absl::Nonnull<int_type*> out, int base) { - static_assert(sizeof(*out) == 4 || sizeof(*out) == 8, - "SimpleAtoi works only with 32-bit or 64-bit integers."); + static_assert(sizeof(*out) == 1 || sizeof(*out) == 2 || sizeof(*out) == 4 || + sizeof(*out) == 8, + "SimpleAtoi works only with 8, 16, 32, or 64-bit integers."); static_assert(!std::is_floating_point<int_type>::value, "Use SimpleAtof or SimpleAtod instead."); bool parsed; - // TODO(jorg): This signed-ness check is used because it works correctly - // with enums, and it also serves to check that int_type is not a pointer. - // If one day something like std::is_signed<enum E> works, switch to it. // These conditions are constexpr bools to suppress MSVC warning C4127. - constexpr bool kIsSigned = static_cast<int_type>(1) - 2 < 0; - constexpr bool kUse64Bit = sizeof(*out) == 64 / 8; + constexpr bool kIsSigned = is_signed<int_type>(); + constexpr int kIntTypeSize = sizeof(*out) * 8; if (kIsSigned) { - if (kUse64Bit) { + if (kIntTypeSize == 64) { int64_t val; parsed = numbers_internal::safe_strto64_base(s, &val, base); *out = static_cast<int_type>(val); - } else { + } else if (kIntTypeSize == 32) { int32_t val; parsed = numbers_internal::safe_strto32_base(s, &val, base); *out = static_cast<int_type>(val); + } else if (kIntTypeSize == 16) { + int16_t val; + parsed = numbers_internal::safe_strto16_base(s, &val, base); + *out = static_cast<int_type>(val); + } else if (kIntTypeSize == 8) { + int8_t val; + parsed = numbers_internal::safe_strto8_base(s, &val, base); + *out = static_cast<int_type>(val); } } else { - if (kUse64Bit) { + if (kIntTypeSize == 64) { uint64_t val; parsed = numbers_internal::safe_strtou64_base(s, &val, base); *out = static_cast<int_type>(val); - } else { + } else if (kIntTypeSize == 32) { uint32_t val; parsed = numbers_internal::safe_strtou32_base(s, &val, base); *out = static_cast<int_type>(val); + } else if (kIntTypeSize == 16) { + uint16_t val; + parsed = numbers_internal::safe_strtou16_base(s, &val, base); + *out = static_cast<int_type>(val); + } else if (kIntTypeSize == 8) { + uint8_t val; + parsed = numbers_internal::safe_strtou8_base(s, &val, base); + *out = static_cast<int_type>(val); } } return parsed;
diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index 75c2dcf..3d9a173 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc
@@ -34,6 +34,7 @@ #include <random> #include <set> #include <string> +#include <type_traits> #include <vector> #include "gmock/gmock.h" @@ -53,10 +54,14 @@ using absl::SimpleAtoi; using absl::SimpleHexAtoi; using absl::numbers_internal::kSixDigitsToBufferSize; +using absl::numbers_internal::safe_strto16_base; using absl::numbers_internal::safe_strto32_base; using absl::numbers_internal::safe_strto64_base; +using absl::numbers_internal::safe_strto8_base; +using absl::numbers_internal::safe_strtou16_base; using absl::numbers_internal::safe_strtou32_base; using absl::numbers_internal::safe_strtou64_base; +using absl::numbers_internal::safe_strtou8_base; using absl::numbers_internal::SixDigitsToBuffer; using absl::strings_internal::Itoa; using absl::strings_internal::strtouint32_test_cases; @@ -256,9 +261,7 @@ template <typename int_type, typename in_val_type> void VerifySimpleAtoiGood(in_val_type in_value, int_type exp_value) { - std::string s; - // (u)int128 can be streamed but not StrCat'd. - absl::strings_internal::OStringStream(&s) << in_value; + std::string s = absl::StrCat(in_value); int_type x = static_cast<int_type>(~exp_value); EXPECT_TRUE(SimpleAtoi(s, &x)) << "in_value=" << in_value << " s=" << s << " x=" << x; @@ -270,15 +273,71 @@ template <typename int_type, typename in_val_type> void VerifySimpleAtoiBad(in_val_type in_value) { - std::string s; - // (u)int128 can be streamed but not StrCat'd. - absl::strings_internal::OStringStream(&s) << in_value; + std::string s = absl::StrCat(in_value); int_type x; EXPECT_FALSE(SimpleAtoi(s, &x)); EXPECT_FALSE(SimpleAtoi(s.c_str(), &x)); } TEST(NumbersTest, Atoi) { + // SimpleAtoi(absl::string_view, int8_t) + VerifySimpleAtoiGood<int8_t>(0, 0); + VerifySimpleAtoiGood<int8_t>(42, 42); + VerifySimpleAtoiGood<int8_t>(-42, -42); + + VerifySimpleAtoiGood<int8_t>(std::numeric_limits<int8_t>::min(), + std::numeric_limits<int8_t>::min()); + VerifySimpleAtoiGood<int8_t>(std::numeric_limits<int8_t>::max(), + std::numeric_limits<int8_t>::max()); + + VerifySimpleAtoiBad<int8_t>(std::numeric_limits<uint8_t>::max()); + VerifySimpleAtoiBad<int8_t>(std::numeric_limits<int16_t>::min()); + VerifySimpleAtoiBad<int8_t>(std::numeric_limits<int16_t>::max()); + + // SimpleAtoi(absl::string_view, uint8_t) + VerifySimpleAtoiGood<uint8_t>(0, 0); + VerifySimpleAtoiGood<uint8_t>(42, 42); + VerifySimpleAtoiBad<uint8_t>(-42); + + VerifySimpleAtoiBad<uint8_t>(std::numeric_limits<int8_t>::min()); + VerifySimpleAtoiGood<uint8_t>(std::numeric_limits<int8_t>::max(), + std::numeric_limits<int8_t>::max()); + VerifySimpleAtoiGood<uint8_t>(std::numeric_limits<uint8_t>::max(), + std::numeric_limits<uint8_t>::max()); + + VerifySimpleAtoiBad<uint8_t>(std::numeric_limits<int16_t>::min()); + VerifySimpleAtoiBad<uint8_t>(std::numeric_limits<int16_t>::max()); + VerifySimpleAtoiBad<uint8_t>(std::numeric_limits<uint16_t>::max()); + + // SimpleAtoi(absl::string_view, uint16_t) + VerifySimpleAtoiGood<int16_t>(0, 0); + VerifySimpleAtoiGood<int16_t>(42, 42); + VerifySimpleAtoiGood<int16_t>(-42, -42); + + VerifySimpleAtoiGood<int16_t>(std::numeric_limits<int16_t>::min(), + std::numeric_limits<int16_t>::min()); + VerifySimpleAtoiGood<int16_t>(std::numeric_limits<int16_t>::max(), + std::numeric_limits<int16_t>::max()); + + VerifySimpleAtoiBad<int16_t>(std::numeric_limits<uint16_t>::max()); + VerifySimpleAtoiBad<int16_t>(std::numeric_limits<int32_t>::min()); + VerifySimpleAtoiBad<int16_t>(std::numeric_limits<int32_t>::max()); + + // SimpleAtoi(absl::string_view, uint16_t) + VerifySimpleAtoiGood<uint16_t>(0, 0); + VerifySimpleAtoiGood<uint16_t>(42, 42); + VerifySimpleAtoiBad<uint16_t>(-42); + + VerifySimpleAtoiBad<uint16_t>(std::numeric_limits<int16_t>::min()); + VerifySimpleAtoiGood<uint16_t>(std::numeric_limits<int16_t>::max(), + std::numeric_limits<int16_t>::max()); + VerifySimpleAtoiGood<uint16_t>(std::numeric_limits<uint16_t>::max(), + std::numeric_limits<uint16_t>::max()); + + VerifySimpleAtoiBad<uint16_t>(std::numeric_limits<int16_t>::min()); + VerifySimpleAtoiBad<uint16_t>(std::numeric_limits<int32_t>::max()); + VerifySimpleAtoiBad<uint16_t>(std::numeric_limits<uint32_t>::max()); + // SimpleAtoi(absl::string_view, int32_t) VerifySimpleAtoiGood<int32_t>(0, 0); VerifySimpleAtoiGood<int32_t>(42, 42); @@ -381,6 +440,7 @@ VerifySimpleAtoiBad<absl::int128>(std::numeric_limits<absl::uint128>::max()); // Some other types + VerifySimpleAtoiGood<short>(-42, -42); // NOLINT: runtime-int VerifySimpleAtoiGood<int>(-42, -42); VerifySimpleAtoiGood<int32_t>(-42, -42); VerifySimpleAtoiGood<uint32_t>(42, 42); @@ -677,10 +737,14 @@ template <typename int_type, typename in_val_type> void VerifySimpleHexAtoiGood(in_val_type in_value, int_type exp_value) { std::string s; - // uint128 can be streamed but not StrCat'd absl::strings_internal::OStringStream strm(&s); if (in_value >= 0) { - strm << std::hex << in_value; + if constexpr (std::is_arithmetic<in_val_type>::value) { + absl::StrAppend(&s, absl::Hex(in_value)); + } else { + // absl::Hex doesn't work with absl::(u)int128. + strm << std::hex << in_value; + } } else { // Inefficient for small integers, but works with all integral types. strm << "-" << std::hex << -absl::uint128(in_value); @@ -698,10 +762,14 @@ template <typename int_type, typename in_val_type> void VerifySimpleHexAtoiBad(in_val_type in_value) { std::string s; - // uint128 can be streamed but not StrCat'd absl::strings_internal::OStringStream strm(&s); if (in_value >= 0) { - strm << std::hex << in_value; + if constexpr (std::is_arithmetic<in_val_type>::value) { + absl::StrAppend(&s, absl::Hex(in_value)); + } else { + // absl::Hex doesn't work with absl::(u)int128. + strm << std::hex << in_value; + } } else { // Inefficient for small integers, but works with all integral types. strm << "-" << std::hex << -absl::uint128(in_value); @@ -713,6 +781,54 @@ } TEST(NumbersTest, HexAtoi) { + // SimpleHexAtoi(absl::string_view, int8_t) + VerifySimpleHexAtoiGood<int8_t>(0, 0); + VerifySimpleHexAtoiGood<int8_t>(0x42, 0x42); + VerifySimpleHexAtoiGood<int8_t>(-0x42, -0x42); + + VerifySimpleHexAtoiGood<int8_t>(std::numeric_limits<int8_t>::min(), + std::numeric_limits<int8_t>::min()); + VerifySimpleHexAtoiGood<int8_t>(std::numeric_limits<int8_t>::max(), + std::numeric_limits<int8_t>::max()); + + // SimpleHexAtoi(absl::string_view, uint8_t) + VerifySimpleHexAtoiGood<uint8_t>(0, 0); + VerifySimpleHexAtoiGood<uint8_t>(0x42, 0x42); + VerifySimpleHexAtoiBad<uint8_t>(-0x42); + + VerifySimpleHexAtoiBad<uint8_t>(std::numeric_limits<int8_t>::min()); + VerifySimpleHexAtoiGood<uint8_t>(std::numeric_limits<int8_t>::max(), + std::numeric_limits<int8_t>::max()); + VerifySimpleHexAtoiGood<uint8_t>(std::numeric_limits<uint8_t>::max(), + std::numeric_limits<uint8_t>::max()); + VerifySimpleHexAtoiBad<uint8_t>(std::numeric_limits<int16_t>::min()); + VerifySimpleHexAtoiBad<uint8_t>(std::numeric_limits<int16_t>::max()); + VerifySimpleHexAtoiBad<uint8_t>(std::numeric_limits<uint16_t>::max()); + + // SimpleHexAtoi(absl::string_view, int16_t) + VerifySimpleHexAtoiGood<int16_t>(0, 0); + VerifySimpleHexAtoiGood<int16_t>(0x42, 0x42); + VerifySimpleHexAtoiGood<int16_t>(-0x42, -0x42); + + VerifySimpleHexAtoiGood<int16_t>(std::numeric_limits<int16_t>::min(), + std::numeric_limits<int16_t>::min()); + VerifySimpleHexAtoiGood<int16_t>(std::numeric_limits<int16_t>::max(), + std::numeric_limits<int16_t>::max()); + + // SimpleHexAtoi(absl::string_view, uint16_t) + VerifySimpleHexAtoiGood<uint16_t>(0, 0); + VerifySimpleHexAtoiGood<uint16_t>(0x42, 0x42); + VerifySimpleHexAtoiBad<uint16_t>(-0x42); + + VerifySimpleHexAtoiBad<uint16_t>(std::numeric_limits<int16_t>::min()); + VerifySimpleHexAtoiGood<uint16_t>(std::numeric_limits<int16_t>::max(), + std::numeric_limits<int16_t>::max()); + VerifySimpleHexAtoiGood<uint16_t>(std::numeric_limits<uint16_t>::max(), + std::numeric_limits<uint16_t>::max()); + VerifySimpleHexAtoiBad<uint16_t>(std::numeric_limits<int32_t>::min()); + VerifySimpleHexAtoiBad<uint16_t>(std::numeric_limits<int32_t>::max()); + VerifySimpleHexAtoiBad<uint16_t>(std::numeric_limits<uint32_t>::max()); + // SimpleHexAtoi(absl::string_view, int32_t) VerifySimpleHexAtoiGood<int32_t>(0, 0); VerifySimpleHexAtoiGood<int32_t>(0x42, 0x42); @@ -790,6 +906,7 @@ std::numeric_limits<absl::uint128>::max()); // Some other types + VerifySimpleHexAtoiGood<short>(-0x42, -0x42); // NOLINT: runtime-int VerifySimpleHexAtoiGood<int>(-0x42, -0x42); VerifySimpleHexAtoiGood<int32_t>(-0x42, -0x42); VerifySimpleHexAtoiGood<uint32_t>(0x42, 0x42); @@ -816,6 +933,154 @@ EXPECT_EQ(0x34234324, value); } +TEST(stringtest, safe_strto8_base) { + int8_t value; + EXPECT_TRUE(safe_strto8_base("0x34", &value, 16)); + EXPECT_EQ(0x34, value); + + EXPECT_TRUE(safe_strto8_base("0X34", &value, 16)); + EXPECT_EQ(0x34, value); + + EXPECT_TRUE(safe_strto8_base("34", &value, 16)); + EXPECT_EQ(0x34, value); + + EXPECT_TRUE(safe_strto8_base("0", &value, 16)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto8_base(" \t\n -0x34", &value, 16)); + EXPECT_EQ(-0x34, value); + + EXPECT_TRUE(safe_strto8_base(" \t\n -34", &value, 16)); + EXPECT_EQ(-0x34, value); + + EXPECT_TRUE(safe_strto8_base("76", &value, 8)); + EXPECT_EQ(076, value); + + EXPECT_TRUE(safe_strto8_base("-0123", &value, 8)); + EXPECT_EQ(-0123, value); + + EXPECT_FALSE(safe_strto8_base("183", &value, 8)); + + // Autodetect base. + EXPECT_TRUE(safe_strto8_base("0", &value, 0)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto8_base("077", &value, 0)); + EXPECT_EQ(077, value); // Octal interpretation + + // Leading zero indicates octal, but then followed by invalid digit. + EXPECT_FALSE(safe_strto8_base("088", &value, 0)); + + // Leading 0x indicated hex, but then followed by invalid digit. + EXPECT_FALSE(safe_strto8_base("0xG", &value, 0)); + + // Base-10 version. + EXPECT_TRUE(safe_strto8_base("124", &value, 10)); + EXPECT_EQ(124, value); + + EXPECT_TRUE(safe_strto8_base("0", &value, 10)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto8_base(" \t\n -124", &value, 10)); + EXPECT_EQ(-124, value); + + EXPECT_TRUE(safe_strto8_base("124 \n\t ", &value, 10)); + EXPECT_EQ(124, value); + + // Invalid ints. + EXPECT_FALSE(safe_strto8_base("", &value, 10)); + EXPECT_FALSE(safe_strto8_base(" ", &value, 10)); + EXPECT_FALSE(safe_strto8_base("abc", &value, 10)); + EXPECT_FALSE(safe_strto8_base("34a", &value, 10)); + EXPECT_FALSE(safe_strto8_base("34.3", &value, 10)); + + // Out of bounds. + EXPECT_FALSE(safe_strto8_base("128", &value, 10)); + EXPECT_FALSE(safe_strto8_base("-129", &value, 10)); + + // String version. + EXPECT_TRUE(safe_strto8_base(std::string("0x12"), &value, 16)); + EXPECT_EQ(0x12, value); + + // Base-10 string version. + EXPECT_TRUE(safe_strto8_base("123", &value, 10)); + EXPECT_EQ(123, value); +} + +TEST(stringtest, safe_strto16_base) { + int16_t value; + EXPECT_TRUE(safe_strto16_base("0x3423", &value, 16)); + EXPECT_EQ(0x3423, value); + + EXPECT_TRUE(safe_strto16_base("0X3423", &value, 16)); + EXPECT_EQ(0x3423, value); + + EXPECT_TRUE(safe_strto16_base("3423", &value, 16)); + EXPECT_EQ(0x3423, value); + + EXPECT_TRUE(safe_strto16_base("0", &value, 16)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto16_base(" \t\n -0x3423", &value, 16)); + EXPECT_EQ(-0x3423, value); + + EXPECT_TRUE(safe_strto16_base(" \t\n -3423", &value, 16)); + EXPECT_EQ(-0x3423, value); + + EXPECT_TRUE(safe_strto16_base("34567", &value, 8)); + EXPECT_EQ(034567, value); + + EXPECT_TRUE(safe_strto16_base("-01234", &value, 8)); + EXPECT_EQ(-01234, value); + + EXPECT_FALSE(safe_strto16_base("1834", &value, 8)); + + // Autodetect base. + EXPECT_TRUE(safe_strto16_base("0", &value, 0)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto16_base("077", &value, 0)); + EXPECT_EQ(077, value); // Octal interpretation + + // Leading zero indicates octal, but then followed by invalid digit. + EXPECT_FALSE(safe_strto16_base("088", &value, 0)); + + // Leading 0x indicated hex, but then followed by invalid digit. + EXPECT_FALSE(safe_strto16_base("0xG", &value, 0)); + + // Base-10 version. + EXPECT_TRUE(safe_strto16_base("3423", &value, 10)); + EXPECT_EQ(3423, value); + + EXPECT_TRUE(safe_strto16_base("0", &value, 10)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto16_base(" \t\n -3423", &value, 10)); + EXPECT_EQ(-3423, value); + + EXPECT_TRUE(safe_strto16_base("3423 \n\t ", &value, 10)); + EXPECT_EQ(3423, value); + + // Invalid ints. + EXPECT_FALSE(safe_strto16_base("", &value, 10)); + EXPECT_FALSE(safe_strto16_base(" ", &value, 10)); + EXPECT_FALSE(safe_strto16_base("abc", &value, 10)); + EXPECT_FALSE(safe_strto16_base("324a", &value, 10)); + EXPECT_FALSE(safe_strto16_base("4234.3", &value, 10)); + + // Out of bounds. + EXPECT_FALSE(safe_strto16_base("32768", &value, 10)); + EXPECT_FALSE(safe_strto16_base("-32769", &value, 10)); + + // String version. + EXPECT_TRUE(safe_strto16_base(std::string("0x1234"), &value, 16)); + EXPECT_EQ(0x1234, value); + + // Base-10 string version. + EXPECT_TRUE(safe_strto16_base("1234", &value, 10)); + EXPECT_EQ(1234, value); +} + TEST(stringtest, safe_strto32_base) { int32_t value; EXPECT_TRUE(safe_strto32_base("0x34234324", &value, 16)); @@ -890,76 +1155,6 @@ EXPECT_EQ(1234, value); } -TEST(stringtest, safe_strto32_range) { - // These tests verify underflow/overflow behaviour. - int32_t value; - EXPECT_FALSE(safe_strto32_base("2147483648", &value, 10)); - EXPECT_EQ(std::numeric_limits<int32_t>::max(), value); - - EXPECT_TRUE(safe_strto32_base("-2147483648", &value, 10)); - EXPECT_EQ(std::numeric_limits<int32_t>::min(), value); - - EXPECT_FALSE(safe_strto32_base("-2147483649", &value, 10)); - EXPECT_EQ(std::numeric_limits<int32_t>::min(), value); -} - -TEST(stringtest, safe_strto64_range) { - // These tests verify underflow/overflow behaviour. - int64_t value; - EXPECT_FALSE(safe_strto64_base("9223372036854775808", &value, 10)); - EXPECT_EQ(std::numeric_limits<int64_t>::max(), value); - - EXPECT_TRUE(safe_strto64_base("-9223372036854775808", &value, 10)); - EXPECT_EQ(std::numeric_limits<int64_t>::min(), value); - - EXPECT_FALSE(safe_strto64_base("-9223372036854775809", &value, 10)); - EXPECT_EQ(std::numeric_limits<int64_t>::min(), value); -} - -TEST(stringtest, safe_strto32_leading_substring) { - // These tests verify this comment in numbers.h: - // On error, returns false, and sets *value to: [...] - // conversion of leading substring if available ("123@@@" -> 123) - // 0 if no leading substring available - int32_t value; - EXPECT_FALSE(safe_strto32_base("04069@@@", &value, 10)); - EXPECT_EQ(4069, value); - - EXPECT_FALSE(safe_strto32_base("04069@@@", &value, 8)); - EXPECT_EQ(0406, value); - - EXPECT_FALSE(safe_strto32_base("04069balloons", &value, 10)); - EXPECT_EQ(4069, value); - - EXPECT_FALSE(safe_strto32_base("04069balloons", &value, 16)); - EXPECT_EQ(0x4069ba, value); - - EXPECT_FALSE(safe_strto32_base("@@@", &value, 10)); - EXPECT_EQ(0, value); // there was no leading substring -} - -TEST(stringtest, safe_strto64_leading_substring) { - // These tests verify this comment in numbers.h: - // On error, returns false, and sets *value to: [...] - // conversion of leading substring if available ("123@@@" -> 123) - // 0 if no leading substring available - int64_t value; - EXPECT_FALSE(safe_strto64_base("04069@@@", &value, 10)); - EXPECT_EQ(4069, value); - - EXPECT_FALSE(safe_strto64_base("04069@@@", &value, 8)); - EXPECT_EQ(0406, value); - - EXPECT_FALSE(safe_strto64_base("04069balloons", &value, 10)); - EXPECT_EQ(4069, value); - - EXPECT_FALSE(safe_strto64_base("04069balloons", &value, 16)); - EXPECT_EQ(0x4069ba, value); - - EXPECT_FALSE(safe_strto64_base("@@@", &value, 10)); - EXPECT_EQ(0, value); // there was no leading substring -} - TEST(stringtest, safe_strto64_base) { int64_t value; EXPECT_TRUE(safe_strto64_base("0x3423432448783446", &value, 16)); @@ -1031,6 +1226,146 @@ EXPECT_EQ(1234, value); } +TEST(stringtest, safe_strto8_range) { + // These tests verify underflow/overflow behaviour. + int8_t value; + EXPECT_FALSE(safe_strto8_base("128", &value, 10)); + EXPECT_EQ(std::numeric_limits<int8_t>::max(), value); + + EXPECT_TRUE(safe_strto8_base("-128", &value, 10)); + EXPECT_EQ(std::numeric_limits<int8_t>::min(), value); + + EXPECT_FALSE(safe_strto8_base("-129", &value, 10)); + EXPECT_EQ(std::numeric_limits<int8_t>::min(), value); +} + +TEST(stringtest, safe_strto16_range) { + // These tests verify underflow/overflow behaviour. + int16_t value; + EXPECT_FALSE(safe_strto16_base("32768", &value, 10)); + EXPECT_EQ(std::numeric_limits<int16_t>::max(), value); + + EXPECT_TRUE(safe_strto16_base("-32768", &value, 10)); + EXPECT_EQ(std::numeric_limits<int16_t>::min(), value); + + EXPECT_FALSE(safe_strto16_base("-32769", &value, 10)); + EXPECT_EQ(std::numeric_limits<int16_t>::min(), value); +} + +TEST(stringtest, safe_strto32_range) { + // These tests verify underflow/overflow behaviour. + int32_t value; + EXPECT_FALSE(safe_strto32_base("2147483648", &value, 10)); + EXPECT_EQ(std::numeric_limits<int32_t>::max(), value); + + EXPECT_TRUE(safe_strto32_base("-2147483648", &value, 10)); + EXPECT_EQ(std::numeric_limits<int32_t>::min(), value); + + EXPECT_FALSE(safe_strto32_base("-2147483649", &value, 10)); + EXPECT_EQ(std::numeric_limits<int32_t>::min(), value); +} + +TEST(stringtest, safe_strto64_range) { + // These tests verify underflow/overflow behaviour. + int64_t value; + EXPECT_FALSE(safe_strto64_base("9223372036854775808", &value, 10)); + EXPECT_EQ(std::numeric_limits<int64_t>::max(), value); + + EXPECT_TRUE(safe_strto64_base("-9223372036854775808", &value, 10)); + EXPECT_EQ(std::numeric_limits<int64_t>::min(), value); + + EXPECT_FALSE(safe_strto64_base("-9223372036854775809", &value, 10)); + EXPECT_EQ(std::numeric_limits<int64_t>::min(), value); +} + +TEST(stringtest, safe_strto8_leading_substring) { + // These tests verify this comment in numbers.h: + // On error, returns false, and sets *value to: [...] + // conversion of leading substring if available ("123@@@" -> 123) + // 0 if no leading substring available + int8_t value; + EXPECT_FALSE(safe_strto8_base("069@@@", &value, 10)); + EXPECT_EQ(69, value); + + EXPECT_FALSE(safe_strto8_base("01769@@@", &value, 8)); + EXPECT_EQ(0176, value); + + EXPECT_FALSE(safe_strto8_base("069balloons", &value, 10)); + EXPECT_EQ(69, value); + + EXPECT_FALSE(safe_strto8_base("07bland", &value, 16)); + EXPECT_EQ(0x7b, value); + + EXPECT_FALSE(safe_strto8_base("@@@", &value, 10)); + EXPECT_EQ(0, value); // there was no leading substring +} + +TEST(stringtest, safe_strto16_leading_substring) { + // These tests verify this comment in numbers.h: + // On error, returns false, and sets *value to: [...] + // conversion of leading substring if available ("123@@@" -> 123) + // 0 if no leading substring available + int16_t value; + EXPECT_FALSE(safe_strto16_base("04069@@@", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto16_base("04069@@@", &value, 8)); + EXPECT_EQ(0406, value); + + EXPECT_FALSE(safe_strto16_base("04069balloons", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto16_base("069balloons", &value, 16)); + EXPECT_EQ(0x69ba, value); + + EXPECT_FALSE(safe_strto16_base("@@@", &value, 10)); + EXPECT_EQ(0, value); // there was no leading substring +} + +TEST(stringtest, safe_strto32_leading_substring) { + // These tests verify this comment in numbers.h: + // On error, returns false, and sets *value to: [...] + // conversion of leading substring if available ("123@@@" -> 123) + // 0 if no leading substring available + int32_t value; + EXPECT_FALSE(safe_strto32_base("04069@@@", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto32_base("04069@@@", &value, 8)); + EXPECT_EQ(0406, value); + + EXPECT_FALSE(safe_strto32_base("04069balloons", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto32_base("04069balloons", &value, 16)); + EXPECT_EQ(0x4069ba, value); + + EXPECT_FALSE(safe_strto32_base("@@@", &value, 10)); + EXPECT_EQ(0, value); // there was no leading substring +} + +TEST(stringtest, safe_strto64_leading_substring) { + // These tests verify this comment in numbers.h: + // On error, returns false, and sets *value to: [...] + // conversion of leading substring if available ("123@@@" -> 123) + // 0 if no leading substring available + int64_t value; + EXPECT_FALSE(safe_strto64_base("04069@@@", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto64_base("04069@@@", &value, 8)); + EXPECT_EQ(0406, value); + + EXPECT_FALSE(safe_strto64_base("04069balloons", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto64_base("04069balloons", &value, 16)); + EXPECT_EQ(0x4069ba, value); + + EXPECT_FALSE(safe_strto64_base("@@@", &value, 10)); + EXPECT_EQ(0, value); // there was no leading substring +} + const size_t kNumRandomTests = 10000; template <typename IntType> @@ -1042,7 +1377,7 @@ RandomEngine rng(rd()); std::uniform_int_distribution<IntType> random_int( std::numeric_limits<IntType>::min()); - std::uniform_int_distribution<int> random_base(2, 35); + std::uniform_int_distribution<int> random_base(2, 36); for (size_t i = 0; i < kNumRandomTests; i++) { IntType value = random_int(rng); int base = random_base(rng); @@ -1070,12 +1405,18 @@ } } +TEST(stringtest, safe_strto16_random) { + test_random_integer_parse_base<int16_t>(&safe_strto16_base); +} TEST(stringtest, safe_strto32_random) { test_random_integer_parse_base<int32_t>(&safe_strto32_base); } TEST(stringtest, safe_strto64_random) { test_random_integer_parse_base<int64_t>(&safe_strto64_base); } +TEST(stringtest, safe_strtou16_random) { + test_random_integer_parse_base<uint16_t>(&safe_strtou16_base); +} TEST(stringtest, safe_strtou32_random) { test_random_integer_parse_base<uint32_t>(&safe_strtou32_base); } @@ -1083,8 +1424,7 @@ test_random_integer_parse_base<uint64_t>(&safe_strtou64_base); } TEST(stringtest, safe_strtou128_random) { - // random number generators don't work for uint128, and - // uint128 can be streamed but not StrCat'd, so this code must be custom + // random number generators don't work for uint128 so this code must be custom // implemented for uint128, but is generally the same as what's above. // test_random_integer_parse_base<absl::uint128>( // &absl::numbers_internal::safe_strtou128_base); @@ -1096,7 +1436,7 @@ RandomEngine rng(rd()); std::uniform_int_distribution<uint64_t> random_uint64( std::numeric_limits<uint64_t>::min()); - std::uniform_int_distribution<int> random_base(2, 35); + std::uniform_int_distribution<int> random_base(2, 36); for (size_t i = 0; i < kNumRandomTests; i++) { IntType value = random_uint64(rng); @@ -1111,20 +1451,16 @@ EXPECT_EQ(parsed_value, value); // Test overflow - std::string s; - absl::strings_internal::OStringStream(&s) - << std::numeric_limits<IntType>::max() << value; - EXPECT_FALSE(parse_func(s, &parsed_value, base)); + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits<IntType>::max(), value), + &parsed_value, base)); // Test underflow - s.clear(); - absl::strings_internal::OStringStream(&s) << "-" << value; - EXPECT_FALSE(parse_func(s, &parsed_value, base)); + EXPECT_FALSE(parse_func(absl::StrCat("-", value), &parsed_value, base)); } } TEST(stringtest, safe_strto128_random) { - // random number generators don't work for int128, and - // int128 can be streamed but not StrCat'd, so this code must be custom + // random number generators don't work for int128 so this code must be custom // implemented for int128, but is generally the same as what's above. // test_random_integer_parse_base<absl::int128>( // &absl::numbers_internal::safe_strto128_base); @@ -1138,7 +1474,7 @@ std::numeric_limits<int64_t>::min()); std::uniform_int_distribution<uint64_t> random_uint64( std::numeric_limits<uint64_t>::min()); - std::uniform_int_distribution<int> random_base(2, 35); + std::uniform_int_distribution<int> random_base(2, 36); for (size_t i = 0; i < kNumRandomTests; ++i) { int64_t high = random_int64(rng); @@ -1155,16 +1491,70 @@ EXPECT_EQ(parsed_value, value); // Test overflow - std::string s; - absl::strings_internal::OStringStream(&s) - << std::numeric_limits<IntType>::max() << value; - EXPECT_FALSE(parse_func(s, &parsed_value, base)); + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits<IntType>::max(), value), + &parsed_value, base)); // Test underflow - s.clear(); - absl::strings_internal::OStringStream(&s) - << std::numeric_limits<IntType>::min() << value; - EXPECT_FALSE(parse_func(s, &parsed_value, base)); + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits<IntType>::min(), value), + &parsed_value, base)); + } +} + +TEST(stringtest, safe_strtou8_exhaustive) { + // Testing the entire space for uint8_t since it is small. + using IntType = uint8_t; + constexpr auto parse_func = &absl::numbers_internal::safe_strtou8_base; + + for (int i = std::numeric_limits<IntType>::min(); + i <= std::numeric_limits<IntType>::max(); i++) { + IntType value = static_cast<IntType>(i); + for (int base = 2; base <= 36; base++) { + std::string str_value; + EXPECT_TRUE(Itoa<IntType>(value, base, &str_value)); + IntType parsed_value; + + // Test successful parse + EXPECT_TRUE(parse_func(str_value, &parsed_value, base)); + EXPECT_EQ(parsed_value, value); + + // Test overflow + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits<IntType>::max(), value), + &parsed_value, base)); + // Test underflow + EXPECT_FALSE(parse_func(absl::StrCat("-", value), &parsed_value, base)); + } + } +} + +TEST(stringtest, safe_strto8_exhaustive) { + // Testing the entire space for int8_t since it is small. + using IntType = int8_t; + constexpr auto parse_func = &absl::numbers_internal::safe_strto8_base; + + for (int i = std::numeric_limits<IntType>::min(); + i <= std::numeric_limits<IntType>::max(); i++) { + IntType value = static_cast<IntType>(i); + for (int base = 2; base <= 36; base++) { + std::string str_value; + EXPECT_TRUE(Itoa<IntType>(value, base, &str_value)); + IntType parsed_value; + + // Test successful parse + EXPECT_TRUE(parse_func(str_value, &parsed_value, base)); + EXPECT_EQ(parsed_value, value); + + // Test overflow + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits<IntType>::max(), value), + &parsed_value, base)); + // Test underflow + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits<IntType>::min(), value), + &parsed_value, base)); + } } } @@ -1434,6 +1824,134 @@ } } +TEST(StrToInt8, Partial) { + struct Int8TestLine { + std::string input; + bool status; + int8_t value; + }; + const int8_t int8_min = std::numeric_limits<int8_t>::min(); + const int8_t int8_max = std::numeric_limits<int8_t>::max(); + Int8TestLine int8_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(int8_min, int8_max), false, int8_min}, + {absl::StrCat(int8_max, int8_max), false, int8_max}, + }; + + for (const Int8TestLine& test_line : int8_test_line) { + int8_t value = -2; + bool status = safe_strto8_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto8_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto8_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToUint8, Partial) { + struct Uint8TestLine { + std::string input; + bool status; + uint8_t value; + }; + const uint8_t uint8_max = std::numeric_limits<uint8_t>::max(); + Uint8TestLine uint8_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(uint8_max, uint8_max), false, uint8_max}, + }; + + for (const Uint8TestLine& test_line : uint8_test_line) { + uint8_t value = 2; + bool status = safe_strtou8_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou8_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou8_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToInt16, Partial) { + struct Int16TestLine { + std::string input; + bool status; + int16_t value; + }; + const int16_t int16_min = std::numeric_limits<int16_t>::min(); + const int16_t int16_max = std::numeric_limits<int16_t>::max(); + Int16TestLine int16_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(int16_min, int16_max), false, int16_min}, + {absl::StrCat(int16_max, int16_max), false, int16_max}, + }; + + for (const Int16TestLine& test_line : int16_test_line) { + int16_t value = -2; + bool status = safe_strto16_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto16_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto16_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToUint16, Partial) { + struct Uint16TestLine { + std::string input; + bool status; + uint16_t value; + }; + const uint16_t uint16_max = std::numeric_limits<uint16_t>::max(); + Uint16TestLine uint16_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(uint16_max, uint16_max), false, uint16_max}, + }; + + for (const Uint16TestLine& test_line : uint16_test_line) { + uint16_t value = 2; + bool status = safe_strtou16_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou16_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou16_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + TEST(StrToInt32, Partial) { struct Int32TestLine { std::string input; @@ -1731,6 +2249,10 @@ } TEST(FastIntToBuffer, WritesNull) { + ExpectWritesNull<int8_t>(); + ExpectWritesNull<uint8_t>(); + ExpectWritesNull<int16_t>(); + ExpectWritesNull<uint16_t>(); ExpectWritesNull<int32_t>(); ExpectWritesNull<uint32_t>(); ExpectWritesNull<int64_t>();