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.
This commit is contained in:
Victor Zverovich 2026-08-16 10:00:41 -07:00
parent cde18a38bb
commit 21d4cc15c8
2 changed files with 18 additions and 24 deletions

View File

@ -562,7 +562,7 @@ template <> struct formatter<std::error_code> {
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;

View File

@ -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) {