From 7852fc384ce4232284fc685f66f70ed99b0c2cd4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Jul 2026 11:36:52 -0700 Subject: [PATCH] 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. --- include/fmt/format.h | 13 +++++++++++-- test/format-test.cc | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index f2ee623d..99743f1c 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -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 +FMT_CONSTEXPR auto write_int_chr(OutputIt out, UInt abs_value, bool negative, + const format_specs& specs) -> OutputIt { + if (negative || abs_value > max_value>()) + report_error("character value out of range"); + return write_char(out, static_cast(abs_value), specs); +} + // Writes a decimal integer with digit grouping. template auto write_int(OutputIt out, UInt value, unsigned prefix, @@ -1997,7 +2006,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, format_base2e(1, appender(buffer), value, num_digits); break; case presentation_type::chr: - return write_char(out, static_cast(value), specs); + return write_int_chr(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 arg, prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); break; case presentation_type::chr: - return write_char(out, static_cast(abs_value), specs); + return write_int_chr(out, abs_value, (prefix & 0xff) == '-', specs); } // Write an integer in the format diff --git a/test/format-test.cc b/test/format-test.cc index 8c2670a1..a1fccfc3 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -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('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(200))); + EXPECT_EQ(fmt::format("{:c}", 255), std::string(1, static_cast(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) {