https://github.com/a-tarasyuk created https://github.com/llvm/llvm-project/pull/201098
This patch adds `-Wformat` support for the C23 `H`, `D`, and `DD` length modifiers in `printf`/`scanf` format strings. #116962 >From 4f186083fe8aedb3e3c1dd694afbfe56320f2c9b Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk <[email protected]> Date: Tue, 2 Jun 2026 14:05:25 +0300 Subject: [PATCH] [Clang] add support for C23 'H', 'D', and 'DD' length modifiers --- clang/docs/ReleaseNotes.rst | 1 + clang/include/clang/AST/FormatString.h | 4 ++ clang/lib/AST/FormatString.cpp | 45 +++++++++++++++++++ clang/lib/AST/PrintfFormatString.cpp | 22 ++++++++- clang/lib/AST/ScanfFormatString.cpp | 15 +++++++ clang/test/Sema/format-strings-decimal.c | 57 ++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 clang/test/Sema/format-strings-decimal.c diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index cef93e25f1e7d..ecaffff1e03f2 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -227,6 +227,7 @@ C2y Feature Support C23 Feature Support ^^^^^^^^^^^^^^^^^^^ - Clang now allows C23 ``constexpr`` struct member access through the dot operator in constant expressions. (#GH178349) +- Clang now supports the C23 ``H``, ``D``, and ``DD`` length modifiers. (#GH116962) Objective-C Language Changes ----------------------------- diff --git a/clang/include/clang/AST/FormatString.h b/clang/include/clang/AST/FormatString.h index a3382e1a1d007..f7f99fcbc08cc 100644 --- a/clang/include/clang/AST/FormatString.h +++ b/clang/include/clang/AST/FormatString.h @@ -80,6 +80,9 @@ class LengthModifier { AsInt3264, // 'I' (MSVCRT, like __int3264 from MIDL) AsInt64, // 'I64' (MSVCRT, like __int64) AsLongDouble, // 'L' + AsDecimal32, // 'H' (C23, _Decimal32) + AsDecimal64, // 'D' (C23, _Decimal64) + AsDecimal128, // 'DD' (C23, _Decimal128) AsAllocate, // for '%as', GNU extension to C90 scanf AsMAllocate, // for '%ms', GNU extension to scanf AsWide, // 'w' (MSVCRT, like l but only for c, C, s, S, or Z @@ -97,6 +100,7 @@ class LengthModifier { return 1; case AsLongLong: case AsChar: + case AsDecimal128: return 2; case AsInt32: case AsInt64: diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp index 7e1ac0de6dcaf..7c327ab20f4b6 100644 --- a/clang/lib/AST/FormatString.cpp +++ b/clang/lib/AST/FormatString.cpp @@ -286,6 +286,25 @@ bool clang::analyze_format_string::ParseLengthModifier(FormatSpecifier &FS, ++I; lmKind = LengthModifier::AsInt3264; break; + case 'H': + if (LO.C23) { + lmKind = LengthModifier::AsDecimal32; + ++I; + break; + } + return false; + case 'D': + if (LO.C23) { + ++I; + if (I != E && *I == 'D') { + ++I; + lmKind = LengthModifier::AsDecimal128; + } else { + lmKind = LengthModifier::AsDecimal64; + } + break; + } + return false; case 'w': lmKind = LengthModifier::AsWide; ++I; @@ -875,6 +894,12 @@ const char *analyze_format_string::LengthModifier::toString() const { return "I64"; case AsLongDouble: return "L"; + case AsDecimal32: + return "H"; + case AsDecimal64: + return "D"; + case AsDecimal128: + return "DD"; case AsAllocate: return "a"; case AsMAllocate: @@ -1202,6 +1227,23 @@ bool FormatSpecifier::hasValidLengthModifier(const TargetInfo &Target, default: return false; } + + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: + switch (CS.getKind()) { + case ConversionSpecifier::aArg: + case ConversionSpecifier::AArg: + case ConversionSpecifier::eArg: + case ConversionSpecifier::EArg: + case ConversionSpecifier::fArg: + case ConversionSpecifier::FArg: + case ConversionSpecifier::gArg: + case ConversionSpecifier::GArg: + return LO.C23; + default: + return false; + } } llvm_unreachable("Invalid LengthModifier Kind!"); } @@ -1217,6 +1259,9 @@ bool FormatSpecifier::hasStandardLengthModifier() const { case LengthModifier::AsSizeT: case LengthModifier::AsPtrDiff: case LengthModifier::AsLongDouble: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return true; case LengthModifier::AsAllocate: case LengthModifier::AsMAllocate: diff --git a/clang/lib/AST/PrintfFormatString.cpp b/clang/lib/AST/PrintfFormatString.cpp index 6610a2de9e083..e39c27cdf1b63 100644 --- a/clang/lib/AST/PrintfFormatString.cpp +++ b/clang/lib/AST/PrintfFormatString.cpp @@ -604,6 +604,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx, case LengthModifier::AsAllocate: case LengthModifier::AsMAllocate: case LengthModifier::AsWide: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return ArgType::Invalid(); } @@ -642,6 +645,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx, case LengthModifier::AsAllocate: case LengthModifier::AsMAllocate: case LengthModifier::AsWide: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return ArgType::Invalid(); } @@ -658,9 +664,18 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx, } } - if (LM.getKind() == LengthModifier::AsLongDouble) + switch (LM.getKind()) { + case LengthModifier::AsLongDouble: return Ctx.LongDoubleTy; - return Ctx.DoubleTy; + case LengthModifier::AsDecimal32: + return ArgType(Ctx.DoubleTy, "_Decimal32"); + case LengthModifier::AsDecimal64: + return ArgType(Ctx.DoubleTy, "_Decimal64"); + case LengthModifier::AsDecimal128: + return ArgType(Ctx.LongDoubleTy, "_Decimal128"); + default: + return Ctx.DoubleTy; + } } if (CS.getKind() == ConversionSpecifier::nArg) { @@ -692,6 +707,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx, case LengthModifier::AsInt3264: case LengthModifier::AsInt64: case LengthModifier::AsWide: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return ArgType::Invalid(); case LengthModifier::AsShortLong: llvm_unreachable("only used for OpenCL which doesn not handle nArg"); diff --git a/clang/lib/AST/ScanfFormatString.cpp b/clang/lib/AST/ScanfFormatString.cpp index 90cbbd60bbcf5..8e4300820db74 100644 --- a/clang/lib/AST/ScanfFormatString.cpp +++ b/clang/lib/AST/ScanfFormatString.cpp @@ -309,6 +309,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const { case LengthModifier::AsInt3264: case LengthModifier::AsWide: case LengthModifier::AsShortLong: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return ArgType::Invalid(); } llvm_unreachable("Unsupported LengthModifier Type"); @@ -353,6 +356,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const { case LengthModifier::AsInt3264: case LengthModifier::AsWide: case LengthModifier::AsShortLong: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return ArgType::Invalid(); } llvm_unreachable("Unsupported LengthModifier Type"); @@ -373,6 +379,12 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const { return ArgType::PtrTo(Ctx.DoubleTy); case LengthModifier::AsLongDouble: return ArgType::PtrTo(Ctx.LongDoubleTy); + case LengthModifier::AsDecimal32: + return ArgType::PtrTo(ArgType(Ctx.FloatTy, "_Decimal32")); + case LengthModifier::AsDecimal64: + return ArgType::PtrTo(ArgType(Ctx.DoubleTy, "_Decimal64")); + case LengthModifier::AsDecimal128: + return ArgType::PtrTo(ArgType(Ctx.LongDoubleTy, "_Decimal128")); default: return ArgType::Invalid(); } @@ -451,6 +463,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const { case LengthModifier::AsInt3264: case LengthModifier::AsWide: case LengthModifier::AsShortLong: + case LengthModifier::AsDecimal32: + case LengthModifier::AsDecimal64: + case LengthModifier::AsDecimal128: return ArgType::Invalid(); } diff --git a/clang/test/Sema/format-strings-decimal.c b/clang/test/Sema/format-strings-decimal.c new file mode 100644 index 0000000000000..abb02255c7339 --- /dev/null +++ b/clang/test/Sema/format-strings-decimal.c @@ -0,0 +1,57 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c23 -fsyntax-only -verify -Wformat %s + +int printf(const char *restrict, ...); +int scanf(const char *restrict, ...); + +void t1(float f, double d, long double ld) { + printf("%Hf", f); + printf("%He", f); + printf("%Hg", f); + printf("%Ha", f); + printf("%HF", f); + printf("%HE", f); + printf("%HG", f); + printf("%HA", f); + + printf("%Df", d); + printf("%De", d); + printf("%Dg", d); + printf("%Da", d); + printf("%DF", d); + printf("%DE", d); + printf("%DG", d); + printf("%DA", d); + + printf("%DDf", ld); + printf("%DDe", ld); + printf("%DDg", ld); + printf("%DDa", ld); + printf("%DDF", ld); + printf("%DDE", ld); + printf("%DDG", ld); + printf("%DDA", ld); +} + +void t2(int i, float f, double d) { + printf("%Df", i); // expected-warning{{format specifies type '_Decimal64'}} + printf("%Hf", i); // expected-warning{{format specifies type '_Decimal32'}} + printf("%DDf", i); // expected-warning{{format specifies type '_Decimal128'}} +} + +void t3(float f) { + printf("%Hd", f); // expected-warning{{length modifier 'H' results in undefined behavior or no effect with 'd' conversion specifier}} + printf("%Dd", f); // expected-warning{{length modifier 'D' results in undefined behavior or no effect with 'd' conversion specifier}} + printf("%DDd", f); // expected-warning{{length modifier 'DD' results in undefined behavior or no effect with 'd' conversion specifier}} + printf("%Hs", "str"); // expected-warning{{length modifier 'H' results in undefined behavior or no effect with 's' conversion specifier}} +} + +void t4(double *d_ptr, float *f_ptr, long double *ld_ptr) { + scanf("%Hf", f_ptr); + scanf("%Df", d_ptr); + scanf("%DDf", ld_ptr); +} + +void t5(int *i_ptr) { + scanf("%Df", i_ptr); // expected-warning{{format specifies type '_Decimal64 *'}} + scanf("%Hf", i_ptr); // expected-warning{{format specifies type '_Decimal32 *'}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
