Minor improvements to til::parse_(un)signed (#20778)

* Allow an explicit `+` prefix like other parsers
* Reject invalid bases
* Fix UB during `uint64_t` -> `T` cast in `parse_signed`
This commit is contained in:
Leonard Hecker authored and GitHub committed 2026-10-07 17:27:02 -05:00
1 parent 35f29bd316
commit 2d417e4eda
2 files changed
+39 -6

No files matched your search

+12 -6
View File
@@ -382,6 +382,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
namespace details
{
// Just like std::wcstoul, but without annoying locales and null-terminating strings.
// Don't use this, use parse_unsigned and parse_signed instead.
template<typename T, typename Traits>
_TIL_INLINEPREFIX constexpr std::optional<uint64_t> parse_u64(const std::basic_string_view<T, Traits>& str, int base = 0) noexcept
{
@@ -397,7 +398,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
uint64_t accumulator = 0;
uint64_t base_uint64 = base;
if (base <= 0)
if (base == 0)
{
base_uint64 = 10;
@@ -422,7 +423,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
}
if (ptr == end || base_uint64 > 36)
if (ptr == end || base_uint64 < 2 || base_uint64 > 36)
{
return {};
}
@@ -467,8 +468,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
template<std::unsigned_integral R, typename T, typename Traits>
constexpr std::optional<R> parse_unsigned(const std::basic_string_view<T, Traits>& str, int base = 0) noexcept
constexpr std::optional<R> parse_unsigned(std::basic_string_view<T, Traits> str, int base = 0) noexcept
{
if (str.starts_with('+'))
{
str = str.substr(1);
}
if constexpr (std::is_same_v<R, uint64_t>)
{
return details::parse_u64<>(str, base);
@@ -488,7 +494,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
constexpr std::optional<R> parse_signed(std::basic_string_view<T, Traits> str, int base = 0) noexcept
{
const bool hasSign = str.starts_with(L'-');
if (hasSign)
if (hasSign || str.starts_with('+'))
{
str = str.substr(1);
}
@@ -500,8 +506,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return {};
}
const auto r = gsl::narrow_cast<R>(*opt);
return hasSign ? -r : r;
const auto value = hasSign ? uint64_t{ 0 } - *opt : *opt;
return gsl::narrow_cast<R>(value);
}
}
+27
View File
@@ -129,6 +129,11 @@ class StringTests
TEST_METHOD(parse_unsigned)
{
for (const auto text : { "+", "++1", "+-1", "-+1", "-0", "+ 1", " +1", "+1 ", "+0x", "+0b", "+08", "+4294967296" })
{
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>(text));
}
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint64_t>(L"++1"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>(""));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0x"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("Z"));
@@ -139,17 +144,30 @@ class StringTests
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint32_t>("0x100000000"));
VERIFY_ARE_EQUAL(0u, til::parse_unsigned<uint32_t>("0"));
VERIFY_ARE_EQUAL(0u, til::parse_unsigned<uint32_t>("0x0"));
VERIFY_ARE_EQUAL(0u, til::parse_unsigned<uint32_t>("+0"));
VERIFY_ARE_EQUAL(123u, til::parse_unsigned<uint32_t>("0b1111011"));
VERIFY_ARE_EQUAL(0123u, til::parse_unsigned<uint32_t>("0123"));
VERIFY_ARE_EQUAL(123u, til::parse_unsigned<uint32_t>("123"));
VERIFY_ARE_EQUAL(0x123u, til::parse_unsigned<uint32_t>("0x123"));
VERIFY_ARE_EQUAL(123u, til::parse_unsigned<uint32_t>("+0B1111011"));
VERIFY_ARE_EQUAL(0123u, til::parse_unsigned<uint32_t>("+0123"));
VERIFY_ARE_EQUAL(123u, til::parse_unsigned<uint32_t>("+123"));
VERIFY_ARE_EQUAL(0x123u, til::parse_unsigned<uint32_t>("+0X123"));
VERIFY_ARE_EQUAL(0x123abcu, til::parse_unsigned<uint32_t>("0x123abc"));
VERIFY_ARE_EQUAL(0X123ABCu, til::parse_unsigned<uint32_t>("0X123ABC"));
VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned<uint32_t>("0xffffffff"));
VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned<uint32_t>("4294967295"));
VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned<uint32_t>("+4294967295"));
VERIFY_ARE_EQUAL(UINT64_MAX, til::parse_unsigned<uint64_t>(L"+18446744073709551615"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned<uint64_t>(L"+18446744073709551616"));
}
TEST_METHOD(parse_signed)
{
for (const auto text : { "+", "++1", "+-1", "-+1", "--1", "+ 1", " +1", "+1 ", "+0x", "+0b", "+08", "+2147483648" })
{
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>(text));
}
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>(""));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("--"));
@@ -160,13 +178,20 @@ class StringTests
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("0x80000000"));
VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed<int32_t>("-0x80000001"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("0"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("+0"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("-0"));
VERIFY_ARE_EQUAL(0, til::parse_signed<int32_t>("-0x0"));
VERIFY_ARE_EQUAL(123, til::parse_signed<int32_t>("0b1111011"));
VERIFY_ARE_EQUAL(0123, til::parse_signed<int32_t>("0123"));
VERIFY_ARE_EQUAL(123, til::parse_signed<int32_t>("123"));
VERIFY_ARE_EQUAL(0x123, til::parse_signed<int32_t>("0x123"));
VERIFY_ARE_EQUAL(123, til::parse_signed<int32_t>("+0b1111011"));
VERIFY_ARE_EQUAL(-123, til::parse_signed<int32_t>("-0b1111011"));
VERIFY_ARE_EQUAL(0123, til::parse_signed<int32_t>("+0123"));
VERIFY_ARE_EQUAL(-0123, til::parse_signed<int32_t>("-0123"));
VERIFY_ARE_EQUAL(123, til::parse_signed<int32_t>("+123"));
VERIFY_ARE_EQUAL(-123, til::parse_signed<int32_t>("-123"));
VERIFY_ARE_EQUAL(0x123, til::parse_signed<int32_t>("+0X123"));
VERIFY_ARE_EQUAL(-0x123, til::parse_signed<int32_t>("-0x123"));
VERIFY_ARE_EQUAL(-0x123abc, til::parse_signed<int32_t>("-0x123abc"));
VERIFY_ARE_EQUAL(-0X123ABC, til::parse_signed<int32_t>("-0X123ABC"));
@@ -174,6 +199,8 @@ class StringTests
VERIFY_ARE_EQUAL(INT32_MIN, til::parse_signed<int32_t>("-2147483648"));
VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed<int32_t>("0x7fffffff"));
VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed<int32_t>("2147483647"));
VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed<int32_t>("+2147483647"));
VERIFY_ARE_EQUAL(INT64_MAX, til::parse_signed<int64_t>(L"+9223372036854775807"));
}
TEST_METHOD(tolower_ascii)