From 8f8145105c773e1c88016ef70c2c917ed247f96d Mon Sep 17 00:00:00 2001 From: Gabriel Ravier Date: Tue, 3 Sep 2024 09:33:55 +0200 Subject: [PATCH] Add POSIX's C conversion specifier to printf funcs (#1276) POSIX specifies the C conversion specifier as being "equivalent to %lc", i.e. printf("%C", arg) is equivalent in behaviour to printf("%lc", arg). This patch implements this conversion specifier, and adds a test for it, alongside another test, which ensures that va_arg uses the correct size, even though we set signbit to 63 in the code (which one might think will result in the wrong size of argument being va_arg-ed, but having signbit set to 63 is in fact what __fmt_stoa expects and is a requirement for it properly formatting the wchar_t argument - this does not result in wrong usage of va_arg because the implementation of the c conversion specifier (which the implementation of the C conversion specifier fallsthrough to) always calls va_arg with an argument type of int, to avoid the very same bug occuring with %lc, as the l length modifier also sets signbit to 63) --- libc/stdio/fmt.c | 3 +++ test/libc/stdio/snprintf_test.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/libc/stdio/fmt.c b/libc/stdio/fmt.c index a866b196f..6f42e9b39 100644 --- a/libc/stdio/fmt.c +++ b/libc/stdio/fmt.c @@ -1073,6 +1073,9 @@ int __fmt(void *fn, void *arg, const char *format, va_list va, int *wrote) { } break; } + case 'C': + signbit = 63; + // fallthrough case 'c': if ((charbuf[0] = va_arg(va, int))) { p = charbuf; diff --git a/test/libc/stdio/snprintf_test.c b/test/libc/stdio/snprintf_test.c index 21f4f5e06..6f7d895eb 100644 --- a/test/libc/stdio/snprintf_test.c +++ b/test/libc/stdio/snprintf_test.c @@ -71,3 +71,33 @@ TEST(snprintf, testInf) { for (i = 4; i < 10; ++i) ASSERT_EQ(buf[i], '\0'); } + +TEST(snprintf, testUppercaseCConversionSpecifier) { + char buf[10] = {}; + int i = snprintf(buf, sizeof(buf), "%C", L'a'); + + ASSERT_EQ(i, 1); + ASSERT_STREQ(buf, "a"); + + i = snprintf(buf, sizeof(buf), "%C", L'☺'); + ASSERT_EQ(i, 3); + ASSERT_STREQ(buf, "☺"); +} + +// Make sure we don't va_arg the wrong argument size on wide character +// conversion specifiers +TEST(snprintf, + testWideCConversionSpecifierWithLotsOfArgumentsBeforeAndOneAfter) { + char buf[20] = {}; + int i = snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%lc%d", 0, 0, 0, 0, 0, 0, + 0, 0, L'x', 1); + + ASSERT_EQ(i, 10); + ASSERT_STREQ(buf, "00000000x1"); + + memset(buf, 0, sizeof(buf)); + i = snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%C%d", 0, 0, 0, 0, 0, 0, 0, 0, + L'x', 1); + ASSERT_EQ(i, 10); + ASSERT_STREQ(buf, "00000000x1"); +}