From 2d417e4eda7f79bc56fd362cf61813db68a9ddbd Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 8 Oct 2026 00:27:02 +0200 Subject: [PATCH] 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` --- src/inc/til/string.h | 18 ++++++++++++------ src/til/ut_til/string.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/inc/til/string.h b/src/inc/til/string.h index 616f33bc0..064759211 100644 --- a/src/inc/til/string.h +++ b/src/inc/til/string.h @@ -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 _TIL_INLINEPREFIX constexpr std::optional parse_u64(const std::basic_string_view& 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 - constexpr std::optional parse_unsigned(const std::basic_string_view& str, int base = 0) noexcept + constexpr std::optional parse_unsigned(std::basic_string_view str, int base = 0) noexcept { + if (str.starts_with('+')) + { + str = str.substr(1); + } + if constexpr (std::is_same_v) { return details::parse_u64<>(str, base); @@ -488,7 +494,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" constexpr std::optional parse_signed(std::basic_string_view 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(*opt); - return hasSign ? -r : r; + const auto value = hasSign ? uint64_t{ 0 } - *opt : *opt; + return gsl::narrow_cast(value); } } diff --git a/src/til/ut_til/string.cpp b/src/til/ut_til/string.cpp index 0c9d7a7dc..aa1ae82c7 100644 --- a/src/til/ut_til/string.cpp +++ b/src/til/ut_til/string.cpp @@ -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(text)); + } + VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned(L"++1")); VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned("")); VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned("0x")); VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned("Z")); @@ -139,17 +144,30 @@ class StringTests VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned("0x100000000")); VERIFY_ARE_EQUAL(0u, til::parse_unsigned("0")); VERIFY_ARE_EQUAL(0u, til::parse_unsigned("0x0")); + VERIFY_ARE_EQUAL(0u, til::parse_unsigned("+0")); + VERIFY_ARE_EQUAL(123u, til::parse_unsigned("0b1111011")); VERIFY_ARE_EQUAL(0123u, til::parse_unsigned("0123")); VERIFY_ARE_EQUAL(123u, til::parse_unsigned("123")); VERIFY_ARE_EQUAL(0x123u, til::parse_unsigned("0x123")); + VERIFY_ARE_EQUAL(123u, til::parse_unsigned("+0B1111011")); + VERIFY_ARE_EQUAL(0123u, til::parse_unsigned("+0123")); + VERIFY_ARE_EQUAL(123u, til::parse_unsigned("+123")); + VERIFY_ARE_EQUAL(0x123u, til::parse_unsigned("+0X123")); VERIFY_ARE_EQUAL(0x123abcu, til::parse_unsigned("0x123abc")); VERIFY_ARE_EQUAL(0X123ABCu, til::parse_unsigned("0X123ABC")); VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned("0xffffffff")); VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned("4294967295")); + VERIFY_ARE_EQUAL(UINT32_MAX, til::parse_unsigned("+4294967295")); + VERIFY_ARE_EQUAL(UINT64_MAX, til::parse_unsigned(L"+18446744073709551615")); + VERIFY_ARE_EQUAL(std::nullopt, til::parse_unsigned(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(text)); + } VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed("")); VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed("-")); VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed("--")); @@ -160,13 +178,20 @@ class StringTests VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed("0x80000000")); VERIFY_ARE_EQUAL(std::nullopt, til::parse_signed("-0x80000001")); VERIFY_ARE_EQUAL(0, til::parse_signed("0")); + VERIFY_ARE_EQUAL(0, til::parse_signed("+0")); VERIFY_ARE_EQUAL(0, til::parse_signed("-0")); VERIFY_ARE_EQUAL(0, til::parse_signed("-0x0")); + VERIFY_ARE_EQUAL(123, til::parse_signed("0b1111011")); VERIFY_ARE_EQUAL(0123, til::parse_signed("0123")); VERIFY_ARE_EQUAL(123, til::parse_signed("123")); VERIFY_ARE_EQUAL(0x123, til::parse_signed("0x123")); + VERIFY_ARE_EQUAL(123, til::parse_signed("+0b1111011")); + VERIFY_ARE_EQUAL(-123, til::parse_signed("-0b1111011")); + VERIFY_ARE_EQUAL(0123, til::parse_signed("+0123")); VERIFY_ARE_EQUAL(-0123, til::parse_signed("-0123")); + VERIFY_ARE_EQUAL(123, til::parse_signed("+123")); VERIFY_ARE_EQUAL(-123, til::parse_signed("-123")); + VERIFY_ARE_EQUAL(0x123, til::parse_signed("+0X123")); VERIFY_ARE_EQUAL(-0x123, til::parse_signed("-0x123")); VERIFY_ARE_EQUAL(-0x123abc, til::parse_signed("-0x123abc")); VERIFY_ARE_EQUAL(-0X123ABC, til::parse_signed("-0X123ABC")); @@ -174,6 +199,8 @@ class StringTests VERIFY_ARE_EQUAL(INT32_MIN, til::parse_signed("-2147483648")); VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed("0x7fffffff")); VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed("2147483647")); + VERIFY_ARE_EQUAL(INT32_MAX, til::parse_signed("+2147483647")); + VERIFY_ARE_EQUAL(INT64_MAX, til::parse_signed(L"+9223372036854775807")); } TEST_METHOD(tolower_ascii)