Fix out-of-range integers with the 'c' presentation type (#4839)

Formatting an integer with ':c' used the magnitude (abs_value) and never
range-checked, so negatives were mangled and out-of-range values silently
truncated. Copy the value as a character and report an error when it is out
of range, treating all character types as unsigned for portability.
This commit is contained in:
Victor Zverovich 2026-07-17 11:36:52 -07:00
parent d45179c1e7
commit 7852fc384c
2 changed files with 21 additions and 2 deletions

View File

@ -1961,6 +1961,15 @@ FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
prefix += (1u + (value > 0xff ? 1 : 0)) << 24;
}
// Writes an integer as a character, treating chars as unsigned.
template <typename Char, typename OutputIt, typename UInt>
FMT_CONSTEXPR auto write_int_chr(OutputIt out, UInt abs_value, bool negative,
const format_specs& specs) -> OutputIt {
if (negative || abs_value > max_value<make_unsigned_t<Char>>())
report_error("character value out of range");
return write_char<Char>(out, static_cast<Char>(abs_value), specs);
}
// Writes a decimal integer with digit grouping.
template <typename OutputIt, typename UInt, typename Char>
auto write_int(OutputIt out, UInt value, unsigned prefix,
@ -1997,7 +2006,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix,
format_base2e<char>(1, appender(buffer), value, num_digits);
break;
case presentation_type::chr:
return write_char<Char>(out, static_cast<Char>(value), specs);
return write_int_chr<Char>(out, value, (prefix & 0xff) == '-', specs);
}
unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) +
@ -2124,7 +2133,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0');
break;
case presentation_type::chr:
return write_char<Char>(out, static_cast<Char>(abs_value), specs);
return write_int_chr<Char>(out, abs_value, (prefix & 0xff) == '-', specs);
}
// Write an integer in the format

View File

@ -1334,6 +1334,16 @@ TEST(format_test, format_int) {
"invalid format specifier");
check_unknown_types(42, "bBdoxXnLc", "integer");
EXPECT_EQ(fmt::format("{:c}", static_cast<int>('x')), "x");
// The 'c' type treats all character types as unsigned for portability, so the
// representable range for char is [0, 255] and out-of-range values are
// reported as an error.
EXPECT_EQ(fmt::format("{:c}", 200), std::string(1, static_cast<char>(200)));
EXPECT_EQ(fmt::format("{:c}", 255), std::string(1, static_cast<char>(255)));
const char* msg = "character value out of range";
EXPECT_THROW_MSG((void)fmt::format("{:c}", -1), format_error, msg);
EXPECT_THROW_MSG((void)fmt::format("{:c}", -104), format_error, msg);
EXPECT_THROW_MSG((void)fmt::format("{:c}", 256), format_error, msg);
EXPECT_THROW_MSG((void)fmt::format("{:c}", 400u), format_error, msg);
}
TEST(format_test, format_bin) {