From 21d4cc15c8a2a3536aab5623258ba3b3dae573ce Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 16 Aug 2026 10:00:41 -0700 Subject: [PATCH] Reject sign-aware zero padding in std::exception and error_code formatters Treat a leading '0' as the zero flag rather than the start of the width so it is rejected (as in the core formatter) instead of being silently consumed. Also trim the exception_align test to the cases this actually introduces. --- include/fmt/std.h | 4 ++-- test/std-test.cc | 38 ++++++++++++++++---------------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index aca0ed62..f505c78b 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -562,7 +562,7 @@ template <> struct formatter { it = detail::parse_align(it, end, specs_); char c = *it; - if (it != end && ((c >= '0' && c <= '9') || c == '{')) + if (it != end && ((c >= '1' && c <= '9') || c == '{')) it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it != end && *it == '?') { @@ -634,7 +634,7 @@ struct formatter< if (it == end) return it; char c = *it; - if ((c >= '0' && c <= '9') || c == '{') + if ((c >= '1' && c <= '9') || c == '{') it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it != end && *it == 't') { ++it; diff --git a/test/std-test.cc b/test/std-test.cc index bba264af..9cb02dcd 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -466,34 +466,25 @@ TEST(std_test, exception_ptr) { } TEST(std_test, exception_align) { - // The exception formatter supports standard width, fill and alignment. auto ex = std::runtime_error("boom"); - EXPECT_EQ(fmt::format("{:>8}", ex), " boom"); - EXPECT_EQ(fmt::format("{:<8}", ex), "boom "); - EXPECT_EQ(fmt::format("{:^8}", ex), " boom "); - EXPECT_EQ(fmt::format("{:*>8}", ex), "****boom"); + + // Static width, fill and alignment. + EXPECT_EQ(fmt::format("{:*^8}", ex), "**boom**"); + + // Dynamic width. EXPECT_EQ(fmt::format("{:{}}", ex, 8), "boom "); - // A width smaller than the message leaves the message untouched. - EXPECT_EQ(fmt::format("{:>2}", ex), "boom"); + + // Sign-aware zero padding is not applicable to exceptions. + EXPECT_THROW((void)fmt::format(fmt::runtime("{:08}"), ex), fmt::format_error); #if FMT_USE_RTTI - // Alignment combines with the 't' (type name) presentation type. - try { - using namespace my_ns1::my_ns2; - throw my_exception("oops"); - } catch (const std::exception& e) { - auto base = fmt::format("{:t}", e); - auto padded = fmt::format("{:*<40t}", e); - EXPECT_EQ(padded.size(), 40u); - EXPECT_EQ(padded.substr(0, base.size()), base); - EXPECT_EQ(padded.substr(base.size()), std::string(40 - base.size(), '*')); - } + // Formatting specs followed by the exception-specific 't'. + EXPECT_EQ(fmt::format("{:*<40t}", ex), + "std::runtime_error: boom****************"); #endif // FMT_USE_RTTI - // exception_ptr honors the same specifiers, for both the empty ("none") - // and the non-empty case. - std::exception_ptr enull = nullptr; - EXPECT_EQ(fmt::format("{:>8}", enull), " none"); + // exception_ptr takes a distinct path: null formats directly as "none", while + // a non-null pointer is rethrown and delegated to the exception formatter. std::exception_ptr ep; try { throw std::runtime_error("bang"); @@ -501,6 +492,9 @@ TEST(std_test, exception_align) { ep = std::current_exception(); } EXPECT_EQ(fmt::format("{:>8}", ep), " bang"); + + std::exception_ptr enull; + EXPECT_EQ(fmt::format("{:>8}", enull), " none"); } #if FMT_USE_RTTI TEST(std_test, type_info) {