Fix printf conversion of a zero value with a precision of zero (#4942)

This commit is contained in:
Sachit Madaan 2026-09-19 07:49:15 -07:00 committed by GitHub
parent fd0a9b6620
commit cfb57bfa3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 67 additions and 3 deletions

View File

@ -88,7 +88,7 @@ struct printf_precision_handler {
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big");
return max_of(static_cast<int>(value), 0);
return detail::is_negative(value) ? -1 : static_cast<int>(value);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -246,6 +246,19 @@ class printf_arg_formatter : public arg_formatter<Char> {
detail::write<Char>(this->out, value, this->specs, this->locale);
}
// C requires no characters when converting a zero value with a precision of
// zero, so only the sign and the padding remain.
void write_zero_with_zero_precision() {
auto s = this->specs;
const char* sign_str = s.sign() == sign::plus ? "+"
: s.sign() == sign::space ? " "
: "";
s.set_type(presentation_type::none);
if (s.align() == align::none || s.align() == align::numeric)
s.set_align(align::right);
write_bytes<Char>(this->out, sign_str, s);
}
public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx)
@ -258,6 +271,13 @@ class printf_arg_formatter : public arg_formatter<Char> {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
auto t = this->specs.type();
if (value == 0 && this->specs.precision == 0 &&
(t == presentation_type::dec || t == presentation_type::oct ||
t == presentation_type::hex)) {
write_zero_with_zero_precision();
return;
}
write(value);
return;
}
@ -490,7 +510,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = sv;
}
if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
if (specs.fill_unit<Char>() == '0') {
if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
specs.set_align(align::numeric);
@ -550,6 +569,13 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
if (specs.type() == presentation_type::none)
report_error("invalid format specifier");
if (upper) specs.set_upper();
// For '#o', C requires a single '0' when the value and the precision are
// both zero.
bool alt_zero = specs.alt() && arg.visit(is_zero_int());
if (alt_zero) specs.clear_alt();
if (alt_zero && specs.type() == presentation_type::oct &&
specs.precision == 0)
specs.precision = 1;
start = it;

View File

@ -278,6 +278,40 @@ TEST(printf_test, int_precision) {
EXPECT_PRINTF("00042 ", "%-#10.5o", 042);
}
// C99 7.21.6.1: the result of converting a zero value with a precision of zero
// is no characters.
TEST(printf_test, zero_int_with_zero_precision) {
EXPECT_PRINTF("", "%.0d", 0);
EXPECT_PRINTF("", "%.d", 0);
EXPECT_PRINTF("", "%.0i", 0);
EXPECT_PRINTF("", "%.0o", 0);
EXPECT_PRINTF("", "%.0u", 0);
EXPECT_PRINTF("", "%.0x", 0);
EXPECT_PRINTF("", "%.0X", 0);
// '#' forces a single '0' for octal, but has no effect on other conversions.
EXPECT_PRINTF("0", "%#.0o", 0);
EXPECT_PRINTF("0", "%#.0hho", 256);
EXPECT_PRINTF("", "%#.0x", 0);
EXPECT_PRINTF("", "%#.0X", 0);
// The sign, space and width still apply.
EXPECT_PRINTF("+", "%+.0d", 0);
EXPECT_PRINTF(" ", "% .0d", 0);
EXPECT_PRINTF(" ", "%5.0d", 0);
EXPECT_PRINTF(" ", "%-5.0d", 0);
EXPECT_PRINTF(" ", "%05.0d", 0);
EXPECT_PRINTF(" 0", "%#5.0o", 0);
EXPECT_PRINTF("0 ", "%#-5.0o", 0);
// A nonzero value or a nonzero precision is unaffected.
EXPECT_PRINTF("42", "%.0d", 42);
EXPECT_PRINTF("-42", "%.0d", -42);
EXPECT_PRINTF("ff", "%.0x", 255);
EXPECT_PRINTF("0", "%.1d", 0);
EXPECT_PRINTF("00", "%.2d", 0);
}
TEST(printf_test, float_precision) {
char buffer[256];
safe_sprintf(buffer, "%.3e", 1234.5678);
@ -300,6 +334,8 @@ TEST(printf_test, ignore_precision_for_non_numeric_arg) {
TEST(printf_test, dynamic_precision) {
EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42));
EXPECT_EQ("42", test_sprintf("%.*d", -5, 42));
EXPECT_EQ("0", test_sprintf("%.*d", -1, 0));
EXPECT_EQ("+0", test_sprintf("%+.*d", -1, 0));
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
"precision is not integer");
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found");
@ -326,6 +362,8 @@ TEST(printf_test, positional_width) {
TEST(printf_test, positional_precision) {
EXPECT_EQ("00042", test_sprintf("%2$.*1$d", 5, 42));
EXPECT_EQ("42", test_sprintf("%2$.*1$d", -5, 42));
EXPECT_EQ("0", test_sprintf("%2$.*1$d", -1, 0));
EXPECT_EQ("+0", test_sprintf("%2$+.*1$d", -1, 0));
EXPECT_EQ("Hell", test_sprintf("%2$.*1$s", 4, "Hello"));
EXPECT_THROW_MSG(test_sprintf("%2$.*1$d", 5.0, 42), format_error,
"precision is not integer");
@ -561,7 +599,7 @@ TEST(printf_test, check_format_string_regression) {
}
TEST(printf_test, fixed_large_exponent) {
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
EXPECT_EQ("1000000000000000000000.000000", fmt::sprintf("%.*f", -13, 1e21));
}
TEST(printf_test, make_printf_args) {