[clang] [Sema] -Wformat: warn about C23 %b %B when not in C23 language mode (PR #126694)
Daniel RodrÃguez Troitiño via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 2 11:01:07 PST 2026
https://github.com/drodriguez updated https://github.com/llvm/llvm-project/pull/126694
>From 6dd960c6cf0550763b986f1909373a5b7e3a069d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Rodri=CC=81guez=20Troitin=CC=83o?=
<danielrodriguez at meta.com>
Date: Mon, 10 Feb 2025 23:23:58 -0800
Subject: [PATCH 1/2] [Sema] -Wformat: warn about C23 %b %B when not in C23
language mode
The validation of %b and %B was done without taking into account the
current language mode, which means that %b and %B could have been used
in format strings before they were supported.
Change the code to check for the language mode being C23 or after,
modify some tests that use %b and %B to specify C23 language mode and
add a new test that checks the different behaviour depending on the
current language mode.
---
.../clang/Basic/DiagnosticSemaKinds.td | 3 ++
clang/lib/AST/PrintfFormatString.cpp | 4 ++-
clang/lib/Sema/SemaChecking.cpp | 12 +++++++
clang/test/Sema/format-strings-c23-binary.c | 32 +++++++++++++++++++
clang/test/Sema/format-strings-fixit.c | 6 ++--
clang/test/Sema/format-strings.c | 8 ++---
6 files changed, 57 insertions(+), 8 deletions(-)
create mode 100644 clang/test/Sema/format-strings-c23-binary.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ef0e29af0f224..d3e42c4e5596b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10631,6 +10631,9 @@ def warn_printf_data_arg_not_used : Warning<
"data argument not used by format string">, InGroup<FormatExtraArgs>;
def warn_format_invalid_conversion : Warning<
"invalid conversion specifier '%0'">, InGroup<FormatInvalidSpecifier>;
+def warn_format_conversion_specifier_requires_c23 : Warning<
+ "conversion specifier '%0' requires a C standard library compatible with "
+ "C23; data argument may not be used by format">, InGroup<Format>;
def warn_printf_incomplete_specifier : Warning<
"incomplete format specifier">, InGroup<Format>;
def warn_missing_format_string : Warning<
diff --git a/clang/lib/AST/PrintfFormatString.cpp b/clang/lib/AST/PrintfFormatString.cpp
index 855550475721a..2e0adddb70369 100644
--- a/clang/lib/AST/PrintfFormatString.cpp
+++ b/clang/lib/AST/PrintfFormatString.cpp
@@ -333,7 +333,9 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H,
else
k = ConversionSpecifier::bArg;
break;
- case 'B': k = ConversionSpecifier::BArg; break;
+ case 'B':
+ k = ConversionSpecifier::BArg;
+ break;
// POSIX specific.
case 'C': k = ConversionSpecifier::CArg; break;
case 'S': k = ConversionSpecifier::SArg; break;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index a49e3883a35a5..a09bf79cfb8ed 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -8863,6 +8863,18 @@ bool CheckPrintfHandler::HandlePrintfSpecifier(
if (!FS.hasStandardConversionSpecifier(S.getLangOpts()))
HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
+ // Warn about C23 %b/%B format specifiers when not in C23 mode.
+ if ((CS.getKind() == ConversionSpecifier::bArg ||
+ CS.getKind() == ConversionSpecifier::BArg) &&
+ !LangStandard::getLangStandardForKind(S.getLangOpts().LangStd).isC23()) {
+ EmitFormatDiagnostic(
+ S.PDiag(diag::warn_format_conversion_specifier_requires_c23)
+ << CS.toString(),
+ getLocationOfByte(CS.getStart()), /*IsStringLocation*/ true,
+ getSpecifierRange(startSpecifier, specifierLen));
+ return true;
+ }
+
// The remaining checks depend on the data arguments.
if (!HasFormatArguments())
return true;
diff --git a/clang/test/Sema/format-strings-c23-binary.c b/clang/test/Sema/format-strings-c23-binary.c
new file mode 100644
index 0000000000000..a023df82c9a05
--- /dev/null
+++ b/clang/test/Sema/format-strings-c23-binary.c
@@ -0,0 +1,32 @@
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=c17 -isystem %S/Inputs %s
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify=c23 -isystem %S/Inputs %s
+
+#include <stdarg.h>
+#include <stddef.h>
+
+int printf(const char *restrict, ...);
+
+void test(unsigned char x) {
+ printf("%lb %lB", (long) 10, (long) 10); // c17-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+
+ printf("%llb %llB", (long long) 10, (long long) 10); // c17-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+
+ printf("%0b%0B", -1u, -1u); // c17-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+
+ printf("%#b %#15.8B\n", 10, 10u); // c17-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+
+ printf("%'b\n", 123456789); // c17-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{results in undefined behavior with 'b' conversion specifier}}
+ // c23-warning at -2{{results in undefined behavior with 'b' conversion specifier}}
+
+ printf("%'B\n", 123456789); // c17-warning{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{results in undefined behavior with 'B' conversion specifier}}
+ // c23-warning at -2{{results in undefined behavior with 'B' conversion specifier}}
+
+ printf("%hhb %hhB", x, x); // c17-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // c17-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+}
diff --git a/clang/test/Sema/format-strings-fixit.c b/clang/test/Sema/format-strings-fixit.c
index 5e37ec76fed21..6fb191e57bcf2 100644
--- a/clang/test/Sema/format-strings-fixit.c
+++ b/clang/test/Sema/format-strings-fixit.c
@@ -1,7 +1,7 @@
// RUN: cp %s %t
-// RUN: %clang_cc1 -pedantic -Wall -fixit %t
-// RUN: %clang_cc1 -fsyntax-only -pedantic -Wall -Werror %t
-// RUN: %clang_cc1 -E -o - %t | FileCheck %s
+// RUN: %clang_cc1 -std=c23 -pedantic -Wall -fixit %t
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -pedantic -Wall -Werror %t
+// RUN: %clang_cc1 -std=c23 -E -o - %t | FileCheck %s
/* This is a test of the various code modification hints that are
provided as part of warning or extension diagnostics. All of the
diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c
index bdb4466dc6ae8..956a4dd27255b 100644
--- a/clang/test/Sema/format-strings.c
+++ b/clang/test/Sema/format-strings.c
@@ -1,7 +1,7 @@
-// RUN: %clang_cc1 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs %s
-// RUN: %clang_cc1 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -fno-signed-char %s
-// RUN: %clang_cc1 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-unknown-fuchsia %s
-// RUN: %clang_cc1 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-linux-android %s
+// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs %s
+// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -fno-signed-char %s
+// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-unknown-fuchsia %s
+// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-linux-android %s
#include <stdarg.h>
#include <stddef.h>
>From 840e05b5c661039766a4307d0d139866afa2ee24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Rodr=C3=ADguez?= <danielrodriguez at meta.com>
Date: Mon, 2 Mar 2026 10:54:09 -0800
Subject: [PATCH 2/2] Adapt format-strings.c to handle both pre-C23 and
post-C23 warnings
---
clang/test/Sema/format-strings.c | 74 +++++++++++++++++++++-----------
1 file changed, 49 insertions(+), 25 deletions(-)
diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c
index 956a4dd27255b..e38b3e0ba7b44 100644
--- a/clang/test/Sema/format-strings.c
+++ b/clang/test/Sema/format-strings.c
@@ -1,7 +1,8 @@
-// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs %s
-// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -fno-signed-char %s
-// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-unknown-fuchsia %s
-// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-linux-android %s
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,prec23 -Wformat-nonliteral -isystem %S/Inputs %s
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,prec23 -Wformat-nonliteral -isystem %S/Inputs -fno-signed-char %s
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,prec23 -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-unknown-fuchsia %s
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,prec23 -Wformat-nonliteral -isystem %S/Inputs -triple=x86_64-linux-android %s
+// RUN: %clang_cc1 -std=c23 -fblocks -fsyntax-only -verify=expected,c23 -Wformat-nonliteral -isystem %S/Inputs %s
#include <stdarg.h>
#include <stddef.h>
@@ -382,8 +383,10 @@ void test10(int x, float f, int i, long long lli) {
printf("%qp", (void *)0); // expected-warning{{length modifier 'q' results in undefined behavior or no effect with 'p' conversion specifier}}
printf("hhX %hhX", (unsigned char)10); // no-warning
printf("llX %llX", (long long) 10); // no-warning
- printf("%lb %lB", (long) 10, (long) 10); // no-warning
- printf("%llb %llB", (long long) 10, (long long) 10); // no-warning
+ printf("%lb %lB", (long) 10, (long) 10); // prec23-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // prec23-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
+ printf("%llb %llB", (long long) 10, (long long) 10); // prec23-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // prec23-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
// This is fine, because there is an implicit conversion to an int.
printf("%d", (unsigned char) 10); // no-warning
printf("%d", (long long) 10); // expected-warning{{format specifies type 'int' but the argument has type 'long long'}}
@@ -507,7 +510,8 @@ void bug7377_bad_length_mod_usage(void) {
// Bad flag usage
printf("%#p", (void *) 0); // expected-warning{{flag '#' results in undefined behavior with 'p' conversion specifier}}
printf("%0d", -1); // no-warning
- printf("%0b%0B", -1u, -1u); // no-warning
+ printf("%0b%0B", -1u, -1u); // prec23-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // prec23-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
printf("%-p", (void *) 0); // no-warning
#if !defined(__ANDROID__) && !defined(__Fuchsia__)
printf("%#n", (int *) 0); // expected-warning{{flag '#' results in undefined behavior with 'n' conversion specifier}}
@@ -596,7 +600,8 @@ void vprintf_scanf_bad(const char *p, va_list ap, const char *s, ...) {
void pr8641(void) {
printf("%#x\n", 10);
printf("%#X\n", 10);
- printf("%#b %#15.8B\n", 10, 10u);
+ printf("%#b %#15.8B\n", 10, 10u); // prec23-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // prec23-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
}
void posix_extensions(void) {
@@ -606,7 +611,9 @@ void posix_extensions(void) {
printf("%'f\n", (float) 1.0); // no-warning
printf("%'p\n", (void*) 0); // expected-warning{{results in undefined behavior with 'p' conversion specifier}}
printf("%'b\n", 123456789); // expected-warning{{results in undefined behavior with 'b' conversion specifier}}
+ // prec23-warning at -1{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
printf("%'B\n", 123456789); // expected-warning{{results in undefined behavior with 'B' conversion specifier}}
+ // prec23-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
}
// PR8486
@@ -639,7 +646,8 @@ void check_char(unsigned char x, signed char y) {
printf("%hhi", x); // no-warning
printf("%c", x); // no-warning
printf("%hhu", y); // no-warning
- printf("%hhb %hhB", x, x); // no-warning
+ printf("%hhb %hhB", x, x); // prec23-warning{{conversion specifier 'b' requires a C standard library compatible with C23; data argument may not be used by format}}
+ // prec23-warning at -1{{conversion specifier 'B' requires a C standard library compatible with C23; data argument may not be used by format}}
}
// Test suppression of individual warnings.
@@ -987,33 +995,49 @@ void test_promotion(void) {
void test_bool(_Bool b, _Bool* bp)
{
#if __SIZEOF_INT__ != __SIZEOF_SIZE_T__
- printf("%zu", b); // expected-warning-re{{format specifies type 'size_t' (aka '{{.+}}') but the argument has type '_Bool'}}
- printf("%td", b); // expected-warning-re{{format specifies type 'ptrdiff_t' (aka '{{.+}}') but the argument has type '_Bool'}}
+ printf("%zu", b); // prec23-warning-re{{format specifies type 'size_t' (aka '{{.+}}') but the argument has type '_Bool'}}
+ // c23-warning-re at -1{{format specifies type 'size_t' (aka '{{.+}}') but the argument has type 'bool'}}
+ printf("%td", b); // prec23-warning-re{{format specifies type 'ptrdiff_t' (aka '{{.+}}') but the argument has type '_Bool'}}
+ // c23-warning-re at -1{{format specifies type 'ptrdiff_t' (aka '{{.+}}') but the argument has type 'bool'}}
#endif
- printf("%jd", b); // expected-warning-re{{format specifies type 'intmax_t' (aka '{{.+}}') but the argument has type '_Bool'}}
- printf("%lld", b); // expected-warning{{format specifies type 'long long' but the argument has type '_Bool'}}
- printf("%ld", b); // expected-warning{{format specifies type 'long' but the argument has type '_Bool'}}
+ printf("%jd", b); // prec23-warning-re{{format specifies type 'intmax_t' (aka '{{.+}}') but the argument has type '_Bool'}}
+ // c23-warning-re at -1{{format specifies type 'intmax_t' (aka '{{.+}}') but the argument has type 'bool'}}
+ printf("%lld", b); // prec23-warning{{format specifies type 'long long' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'long long' but the argument has type 'bool'}}
+ printf("%ld", b); // prec23-warning{{format specifies type 'long' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'long' but the argument has type 'bool'}}
printf("%d", b); // promoted from _Bool to int
printf("%hhd", b); // promoted from _Bool to int
printf("%hd", b); // promoted from _Bool to int
#if !defined(__Fuchsia__) && !defined(__ANDROID__) //'%n' specifier not supported on this platform
// The n conversion specifier only supports signed types
- printf("%zn", bp); // expected-warning-re{{format specifies type 'signed size_t *' (aka '{{.+}}') but the argument has type '_Bool *'}}
- printf("%jn", bp); // expected-warning-re{{format specifies type 'intmax_t *' (aka '{{.+}}') but the argument has type '_Bool *'}}
- printf("%lln", bp); // expected-warning{{format specifies type 'long long *' but the argument has type '_Bool *'}}
- printf("%ln", bp); // expected-warning{{format specifies type 'long *' but the argument has type '_Bool *'}}
- printf("%n", bp); // expected-warning{{format specifies type 'int *' but the argument has type '_Bool *'}}
- printf("%hhn", bp); // expected-warning{{format specifies type 'signed char *' but the argument has type '_Bool *'}}
+ printf("%zn", bp); // prec23-warning-re{{format specifies type 'signed size_t *' (aka '{{.+}}') but the argument has type '_Bool *'}}
+ // c23-warning-re at -1{{format specifies type 'signed size_t *' (aka '{{.+}}') but the argument has type 'bool *'}}
+ printf("%jn", bp); // prec23-warning-re{{format specifies type 'intmax_t *' (aka '{{.+}}') but the argument has type '_Bool *'}}
+ // c23-warning-re at -1{{format specifies type 'intmax_t *' (aka '{{.+}}') but the argument has type 'bool *'}}
+ printf("%lln", bp); // prec23-warning{{format specifies type 'long long *' but the argument has type '_Bool *'}}
+ // c23-warning at -1{{format specifies type 'long long *' but the argument has type 'bool *'}}
+ printf("%ln", bp); // prec23-warning{{format specifies type 'long *' but the argument has type '_Bool *'}}
+ // c23-warning at -1{{format specifies type 'long *' but the argument has type 'bool *'}}
+ printf("%n", bp); // prec23-warning{{format specifies type 'int *' but the argument has type '_Bool *'}}
+ // c23-warning at -1{{format specifies type 'int *' but the argument has type 'bool *'}}
+ printf("%hhn", bp); // prec23-warning{{format specifies type 'signed char *' but the argument has type '_Bool *'}}
+ // c23-warning at -1{{format specifies type 'signed char *' but the argument has type 'bool *'}}
printf("%hn", bp); // belong to -Wformat-type-confusion
#endif
printf("%c", b); // expected-warning{{using '%c' format specifier, but argument has boolean value}}
- printf("%s", b); // expected-warning{{format specifies type 'char *' but the argument has type '_Bool'}}
+ printf("%s", b); // prec23-warning{{format specifies type 'char *' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'char *' but the argument has type 'bool'}}
printf("%d", b); // promoted from _Bool to int
printf("%o", b); // promoted from _Bool to int
printf("%x", b); // promoted from _Bool to int
printf("%u", b); // promoted from _Bool to int
- printf("%f", b); // expected-warning{{format specifies type 'double' but the argument has type '_Bool'}}
- printf("%e", b); // expected-warning{{format specifies type 'double' but the argument has type '_Bool'}}
- printf("%a", b); // expected-warning{{format specifies type 'double' but the argument has type '_Bool'}}
- printf("%g", b); // expected-warning{{format specifies type 'double' but the argument has type '_Bool'}}
+ printf("%f", b); // prec23-warning{{format specifies type 'double' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'double' but the argument has type 'bool'}}
+ printf("%e", b); // prec23-warning{{format specifies type 'double' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'double' but the argument has type 'bool'}}
+ printf("%a", b); // prec23-warning{{format specifies type 'double' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'double' but the argument has type 'bool'}}
+ printf("%g", b); // prec23-warning{{format specifies type 'double' but the argument has type '_Bool'}}
+ // c23-warning at -1{{format specifies type 'double' but the argument has type 'bool'}}
}
More information about the cfe-commits
mailing list