Merge pull request #422 from sahvx655-wq/is-integer-signed-overflow

avoid signed overflow in is_integer on negative wchar_t units
This commit is contained in:
Daniel Lemire 2026-10-09 15:54:46 -04:00 committed by GitHub
commit 91aef729bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 2 deletions

View File

@ -32,7 +32,13 @@ template <typename UC> fastfloat_really_inline constexpr bool has_simd_opt() {
// able to optimize it well.
template <typename UC>
fastfloat_really_inline constexpr bool is_integer(UC c) noexcept {
return static_cast<unsigned>(c - UC('0')) <= 9u;
// UC can be signed (wchar_t is a signed int on Linux and macOS), and the
// subtraction is then promoted to int and overflows for code units near the
// bottom of the range. Subtracting in the unsigned type wraps instead, which
// selects the same code units, as ch_to_digit already does.
using UnsignedUC = typename std::make_unsigned<UC>::type;
return static_cast<UnsignedUC>(static_cast<UnsignedUC>(c) -
static_cast<UnsignedUC>(UC('0'))) <= 9u;
}
fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) {

View File

@ -70,9 +70,34 @@ bool test_non_space_with_space_low_byte() {
return true;
}
// is_integer subtracts UC('0') from the code unit. When wchar_t is signed the
// subtraction is promoted to int and overflows for code units near the bottom
// of the range, so the sanitized build aborts on input the library is supposed
// to reject. Such a unit is not a digit, so both entry points must fail.
bool test_code_unit_below_zero_digit() {
if (!std::is_signed<wchar_t>::value) {
return true; // only reproducible where wchar_t is signed
}
wchar_t const input[] = {(std::numeric_limits<wchar_t>::min)(), L'1'};
double result;
auto answer = fast_float::from_chars(input, input + 2, result);
if (answer.ec != std::errc::invalid_argument) {
std::cerr << "a code unit below L'0' must not start a number\n";
return false;
}
unsigned long long integer;
auto int_answer = fast_float::from_chars(input, input + 2, integer, 10);
if (int_answer.ec != std::errc::invalid_argument) {
std::cerr << "a code unit below L'0' must not start an integer\n";
return false;
}
return true;
}
int main() {
if (test_minus() && test_plus() && test_space() && test_nan() &&
test_non_space_with_space_low_byte()) {
test_non_space_with_space_low_byte() &&
test_code_unit_below_zero_digit()) {
std::cout << "all ok" << std::endl;
return EXIT_SUCCESS;
}