[clang] [Clang] support C23 printf width length modifiers (PR #199991)
Oleksandr Tarasiuk via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 5 06:21:36 PDT 2026
https://github.com/a-tarasyuk updated https://github.com/llvm/llvm-project/pull/199991
>From 9d51580ef67e65562ef973dbb7b6792c51431c2b Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 27 May 2026 16:28:30 +0300
Subject: [PATCH 1/9] [Clang] support C23 printf width length modifiers
---
clang/docs/ReleaseNotes.rst | 1 +
clang/include/clang/AST/ASTContext.h | 3 +
clang/include/clang/AST/FormatString.h | 31 ++++++-
clang/lib/AST/ASTContext.cpp | 6 ++
clang/lib/AST/FormatString.cpp | 117 ++++++++++++++++++++++++-
clang/lib/AST/PrintfFormatString.cpp | 11 +++
clang/lib/AST/ScanfFormatString.cpp | 9 ++
clang/lib/Sema/SemaChecking.cpp | 14 +--
clang/test/Sema/format-strings-c23.c | 50 +++++++++++
clang/test/Sema/format-strings.c | 5 ++
10 files changed, 234 insertions(+), 13 deletions(-)
create mode 100644 clang/test/Sema/format-strings-c23.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cef93e25f1e7d..1acfb155b974c 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 ``wN`` and ``wfN`` length modifiers. (#GH116962)
Objective-C Language Changes
-----------------------------
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 9ef27cc1eb58e..fa7f531248526 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -949,6 +949,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
QualType getIntTypeForBitwidth(unsigned DestWidth,
unsigned Signed) const;
+ QualType getLeastIntTypeForBitwidth(unsigned DestWidth,
+ unsigned Signed) const;
+
/// getRealTypeForBitwidth -
/// sets floating point QualTy according to specified bitwidth.
/// Returns empty type if there is no appropriate target types.
diff --git a/clang/include/clang/AST/FormatString.h b/clang/include/clang/AST/FormatString.h
index a3382e1a1d007..239d88ea33c7c 100644
--- a/clang/include/clang/AST/FormatString.h
+++ b/clang/include/clang/AST/FormatString.h
@@ -19,6 +19,7 @@
#define LLVM_CLANG_AST_FORMATSTRING_H
#include "clang/AST/CanonicalType.h"
+#include "llvm/ADT/StringRef.h"
#include <optional>
namespace clang {
@@ -80,6 +81,8 @@ class LengthModifier {
AsInt3264, // 'I' (MSVCRT, like __int3264 from MIDL)
AsInt64, // 'I64' (MSVCRT, like __int64)
AsLongDouble, // 'L'
+ AsIntN, // 'wN'
+ AsFastIntN, // 'wfN'
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
@@ -88,6 +91,8 @@ class LengthModifier {
LengthModifier() : Position(nullptr), kind(None) {}
LengthModifier(const char *pos, Kind k) : Position(pos), kind(k) {}
+ LengthModifier(const char *pos, Kind k, unsigned bitWidth, unsigned length)
+ : Position(pos), kind(k), BitWidth(bitWidth), ModifierLength(length) {}
const char *getStart() const { return Position; }
@@ -98,6 +103,9 @@ class LengthModifier {
case AsLongLong:
case AsChar:
return 2;
+ case AsIntN:
+ case AsFastIntN:
+ return ModifierLength;
case AsInt32:
case AsInt64:
return 3;
@@ -109,11 +117,15 @@ class LengthModifier {
Kind getKind() const { return kind; }
void setKind(Kind k) { kind = k; }
- const char *toString() const;
+ unsigned getBitWidth() const { return BitWidth; }
+
+ StringRef toString() const;
private:
const char *Position;
Kind kind;
+ unsigned BitWidth = 0;
+ unsigned ModifierLength = 0;
};
class ConversionSpecifier {
@@ -301,10 +313,18 @@ class ArgType {
const char *Name = nullptr;
bool Ptr = false;
- /// The TypeKind identifies certain well-known types like size_t and
- /// ptrdiff_t.
- enum class TypeKind { DontCare, SizeT, PtrdiffT };
+ /// The TypeKind identifies certain well-known types.
+ enum class TypeKind {
+ DontCare,
+ SizeT,
+ PtrdiffT,
+ IntN,
+ UIntN,
+ FastIntN,
+ FastUIntN,
+ };
TypeKind TK = TypeKind::DontCare;
+ unsigned BitWidth = 0;
public:
ArgType(Kind K = UnknownTy, const char *N = nullptr) : K(K), Name(N) {}
@@ -341,6 +361,9 @@ class ArgType {
return Res;
}
+ static ArgType makeIntNT(ASTContext &Ctx, const LengthModifier &LengthMod,
+ bool Signed);
+
MatchKind matchesType(ASTContext &C, QualType argTy) const;
MatchKind matchesArgType(ASTContext &C, const ArgType &other) const;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a401a7471e6fc..ec483df8aa61b 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13541,6 +13541,12 @@ QualType ASTContext::getIntTypeForBitwidth(unsigned DestWidth,
return QualTy;
}
+QualType ASTContext::getLeastIntTypeForBitwidth(unsigned DestWidth,
+ unsigned Signed) const {
+ return getFromTargetType(
+ getTargetInfo().getLeastIntTypeByWidth(DestWidth, Signed));
+}
+
/// getRealTypeForBitwidth -
/// sets floating point QualTy according to specified bitwidth.
/// Returns empty type if there is no appropriate target types.
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index 7e1ac0de6dcaf..81a77a89268aa 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -14,7 +14,9 @@
#include "FormatStringParsing.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetInfo.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ConvertUTF.h"
+#include <limits>
#include <optional>
using clang::analyze_format_string::ArgType;
@@ -59,6 +61,21 @@ OptionalAmount clang::analyze_format_string::ParseAmount(const char *&Beg,
return OptionalAmount();
}
+static bool ParseWidthModifier(const char *&I, const char *E,
+ unsigned &BitWidth, unsigned &ModifierLength) {
+ StringRef W = StringRef(I, E - I).take_while(llvm::isDigit);
+ if (W.empty() || W.front() == '0')
+ return false;
+
+ if (W.getAsInteger(10, BitWidth))
+ BitWidth = std::numeric_limits<unsigned>::max();
+
+ for (const char *End = W.end(); I != End; ++I)
+ ++ModifierLength;
+
+ return true;
+}
+
OptionalAmount clang::analyze_format_string::ParseNonPositionAmount(
const char *&Beg, const char *E, unsigned &argIndex) {
if (*Beg == '*') {
@@ -287,6 +304,25 @@ bool clang::analyze_format_string::ParseLengthModifier(FormatSpecifier &FS,
lmKind = LengthModifier::AsInt3264;
break;
case 'w':
+ if (LO.C23) {
+ const char *WidthModifier = I + 1;
+ unsigned BitWidth = 0;
+ unsigned ModifierLength = 1;
+
+ LengthModifier::Kind WidthKind = LengthModifier::AsIntN;
+ if (WidthModifier != E && *WidthModifier == 'f') {
+ WidthModifier = I + 2;
+ ModifierLength = 2;
+ WidthKind = LengthModifier::AsFastIntN;
+ }
+
+ if (ParseWidthModifier(WidthModifier, E, BitWidth, ModifierLength)) {
+ I = WidthModifier;
+ FS.setLengthModifier(
+ LengthModifier(lmPosition, WidthKind, BitWidth, ModifierLength));
+ return true;
+ }
+ }
lmKind = LengthModifier::AsWide;
++I;
break;
@@ -774,6 +810,22 @@ ArgType ArgType::makeVectorType(ASTContext &C, unsigned NumElts) const {
return ArgType(Vec, Name);
}
+ArgType ArgType::makeIntNT(ASTContext &Ctx, const LengthModifier &LengthMod,
+ bool Signed) {
+ bool IsFast = LengthMod.getKind() == LengthModifier::AsFastIntN;
+ QualType Ty =
+ IsFast ? Ctx.getLeastIntTypeForBitwidth(LengthMod.getBitWidth(), Signed)
+ : Ctx.getIntTypeForBitwidth(LengthMod.getBitWidth(), Signed);
+ if (Ty.isNull())
+ return ArgType::Invalid();
+
+ ArgType Res(Ty);
+ Res.TK = IsFast ? (Signed ? TypeKind::FastIntN : TypeKind::FastUIntN)
+ : (Signed ? TypeKind::IntN : TypeKind::UIntN);
+ Res.BitWidth = LengthMod.getBitWidth();
+ return Res;
+}
+
QualType ArgType::getRepresentativeType(ASTContext &C) const {
QualType Res;
switch (K) {
@@ -820,6 +872,33 @@ std::string ArgType::getRepresentativeTypeName(ASTContext &C) const {
if (Name) {
// Use a specific name for this type, e.g. "size_t".
Alias = Name;
+ } else {
+ const char *Prefix = nullptr;
+ switch (TK) {
+ case TypeKind::IntN:
+ Prefix = "int";
+ break;
+ case TypeKind::UIntN:
+ Prefix = "uint";
+ break;
+ case TypeKind::FastIntN:
+ Prefix = "int_fast";
+ break;
+ case TypeKind::FastUIntN:
+ Prefix = "uint_fast";
+ break;
+ case TypeKind::DontCare:
+ case TypeKind::SizeT:
+ case TypeKind::PtrdiffT:
+ break;
+ }
+ if (Prefix) {
+ Alias = Prefix;
+ Alias += std::to_string(BitWidth);
+ Alias += "_t";
+ }
+ }
+ if (!Alias.empty()) {
if (Ptr) {
// If ArgType is actually a pointer to T, append an asterisk.
Alias += (Alias[Alias.size() - 1] == '*') ? "*" : " *";
@@ -847,7 +926,7 @@ analyze_format_string::OptionalAmount::getArgType(ASTContext &Ctx) const {
// Methods on LengthModifier.
//===----------------------------------------------------------------------===//
-const char *analyze_format_string::LengthModifier::toString() const {
+StringRef analyze_format_string::LengthModifier::toString() const {
switch (kind) {
case AsChar:
return "hh";
@@ -875,6 +954,9 @@ const char *analyze_format_string::LengthModifier::toString() const {
return "I64";
case AsLongDouble:
return "L";
+ case AsIntN:
+ case AsFastIntN:
+ return StringRef(Position, getLength());
case AsAllocate:
return "a";
case AsMAllocate:
@@ -884,7 +966,7 @@ const char *analyze_format_string::LengthModifier::toString() const {
case None:
return "";
}
- return nullptr;
+ llvm_unreachable("Invalid LengthModifier Kind!");
}
//===----------------------------------------------------------------------===//
@@ -1156,6 +1238,35 @@ bool FormatSpecifier::hasValidLengthModifier(const TargetInfo &Target,
return false;
}
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN: {
+ if (!LO.C23)
+ return false;
+
+ TargetInfo::IntType TargetType =
+ LM.getKind() == LengthModifier::AsIntN
+ ? Target.getIntTypeByWidth(LM.getBitWidth(), /*IsSigned=*/true)
+ : Target.getLeastIntTypeByWidth(LM.getBitWidth(),
+ /*IsSigned=*/true);
+ if (TargetType == TargetInfo::NoInt)
+ return false;
+
+ switch (CS.getKind()) {
+ case ConversionSpecifier::bArg:
+ case ConversionSpecifier::BArg:
+ case ConversionSpecifier::dArg:
+ case ConversionSpecifier::iArg:
+ case ConversionSpecifier::oArg:
+ case ConversionSpecifier::uArg:
+ case ConversionSpecifier::xArg:
+ case ConversionSpecifier::XArg:
+ case ConversionSpecifier::nArg:
+ return true;
+ default:
+ return false;
+ }
+ }
+
case LengthModifier::AsAllocate:
switch (CS.getKind()) {
case ConversionSpecifier::sArg:
@@ -1217,6 +1328,8 @@ bool FormatSpecifier::hasStandardLengthModifier() const {
case LengthModifier::AsSizeT:
case LengthModifier::AsPtrDiff:
case LengthModifier::AsLongDouble:
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
return true;
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
diff --git a/clang/lib/AST/PrintfFormatString.cpp b/clang/lib/AST/PrintfFormatString.cpp
index 6610a2de9e083..64310193e1057 100644
--- a/clang/lib/AST/PrintfFormatString.cpp
+++ b/clang/lib/AST/PrintfFormatString.cpp
@@ -601,6 +601,11 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
case LengthModifier::AsPtrDiff:
return ArgType::makePtrdiffT(
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t"));
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
+ return ArgType::makeIntNT(Ctx, LM,
+ CS.getKind() != ConversionSpecifier::bArg &&
+ CS.getKind() != ConversionSpecifier::BArg);
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsWide:
@@ -639,6 +644,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
case LengthModifier::AsPtrDiff:
return ArgType::makePtrdiffT(
ArgType(Ctx.getUnsignedPointerDiffType(), "unsigned ptrdiff_t"));
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
+ return ArgType::makeIntNT(Ctx, LM, /*Signed=*/false);
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsWide:
@@ -684,6 +692,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
case LengthModifier::AsPtrDiff:
return ArgType::PtrTo(ArgType::makePtrdiffT(
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")));
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
+ return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/true));
case LengthModifier::AsLongDouble:
return ArgType(); // FIXME: Is this a known extension?
case LengthModifier::AsAllocate:
diff --git a/clang/lib/AST/ScanfFormatString.cpp b/clang/lib/AST/ScanfFormatString.cpp
index 90cbbd60bbcf5..9b832376983d2 100644
--- a/clang/lib/AST/ScanfFormatString.cpp
+++ b/clang/lib/AST/ScanfFormatString.cpp
@@ -300,6 +300,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
case LengthModifier::AsPtrDiff:
return ArgType::PtrTo(ArgType::makePtrdiffT(
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")));
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
+ return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/true));
case LengthModifier::AsLongDouble:
// GNU extension.
return ArgType::PtrTo(Ctx.LongLongTy);
@@ -344,6 +347,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
case LengthModifier::AsPtrDiff:
return ArgType::PtrTo(ArgType::makePtrdiffT(
ArgType(Ctx.getUnsignedPointerDiffType(), "unsigned ptrdiff_t")));
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
+ return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/false));
case LengthModifier::AsLongDouble:
// GNU extension.
return ArgType::PtrTo(Ctx.UnsignedLongLongTy);
@@ -443,6 +449,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
case LengthModifier::AsPtrDiff:
return ArgType::PtrTo(ArgType::makePtrdiffT(
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")));
+ case LengthModifier::AsIntN:
+ case LengthModifier::AsFastIntN:
+ return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/true));
case LengthModifier::AsLongDouble:
return ArgType(); // FIXME: Is this a known extension?
case LengthModifier::AsAllocate:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 2309196ee1696..671ac62bfcce0 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -8322,7 +8322,7 @@ class EquatableFormatArgument {
private:
analyze_format_string::ArgType ArgType;
- analyze_format_string::LengthModifier::Kind LengthMod;
+ analyze_format_string::LengthModifier LengthMod;
StringRef SpecifierLetter;
CharSourceRange Range;
SourceLocation ElementLoc;
@@ -8336,7 +8336,7 @@ class EquatableFormatArgument {
public:
EquatableFormatArgument(CharSourceRange Range, SourceLocation ElementLoc,
- analyze_format_string::LengthModifier::Kind LengthMod,
+ analyze_format_string::LengthModifier LengthMod,
StringRef SpecifierLetter,
analyze_format_string::ArgType ArgType,
FormatArgumentRole Role,
@@ -8351,7 +8351,7 @@ class EquatableFormatArgument {
SourceLocation getSourceLocation() const { return ElementLoc; }
CharSourceRange getSourceRange() const { return Range; }
analyze_format_string::LengthModifier getLengthModifier() const {
- return analyze_format_string::LengthModifier(nullptr, LengthMod);
+ return LengthMod;
}
void setModifierFor(unsigned V) { ModifierFor = V; }
@@ -8699,7 +8699,7 @@ bool DecomposePrintfHandler::HandlePrintfSpecifier(
Specs.emplace_back(
getSpecifierRange(startSpecifier, specifierLen),
getLocationOfByte(FieldWidth.getStart()),
- analyze_format_string::LengthModifier::None, FieldWidth.getCharacters(),
+ analyze_format_string::LengthModifier(), FieldWidth.getCharacters(),
FieldWidth.getArgType(S.Context),
EquatableFormatArgument::FAR_FieldWidth,
EquatableFormatArgument::SS_None,
@@ -8714,7 +8714,7 @@ bool DecomposePrintfHandler::HandlePrintfSpecifier(
Specs.emplace_back(
getSpecifierRange(startSpecifier, specifierLen),
getLocationOfByte(Precision.getStart()),
- analyze_format_string::LengthModifier::None, Precision.getCharacters(),
+ analyze_format_string::LengthModifier(), Precision.getCharacters(),
Precision.getArgType(S.Context), EquatableFormatArgument::FAR_Precision,
EquatableFormatArgument::SS_None,
Precision.usesPositionalArg() ? Precision.getPositionalArgIndex() - 1
@@ -8742,7 +8742,7 @@ bool DecomposePrintfHandler::HandlePrintfSpecifier(
Specs.emplace_back(
getSpecifierRange(startSpecifier, specifierLen),
- getLocationOfByte(CS.getStart()), FS.getLengthModifier().getKind(),
+ getLocationOfByte(CS.getStart()), FS.getLengthModifier(),
CS.getCharacters(), FS.getArgType(S.Context, isObjCContext()),
EquatableFormatArgument::FAR_Data, Sensitivity, SpecIndex, 0);
@@ -8751,7 +8751,7 @@ bool DecomposePrintfHandler::HandlePrintfSpecifier(
CS.getKind() == analyze_format_string::ConversionSpecifier::FreeBSDDArg) {
Specs.emplace_back(getSpecifierRange(startSpecifier, specifierLen),
getLocationOfByte(CS.getStart()),
- analyze_format_string::LengthModifier::None,
+ analyze_format_string::LengthModifier(),
CS.getCharacters(),
analyze_format_string::ArgType::CStrTy,
EquatableFormatArgument::FAR_Auxiliary, Sensitivity,
diff --git a/clang/test/Sema/format-strings-c23.c b/clang/test/Sema/format-strings-c23.c
new file mode 100644
index 0000000000000..7156dc21981de
--- /dev/null
+++ b/clang/test/Sema/format-strings-c23.c
@@ -0,0 +1,50 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify %s
+
+typedef __INT32_TYPE__ int32_t;
+typedef __UINT32_TYPE__ uint32_t;
+typedef __INT_FAST32_TYPE__ int_fast32_t;
+typedef __UINT_FAST32_TYPE__ uint_fast32_t;
+
+int printf(const char *restrict, ...);
+int scanf(const char *restrict, ...);
+
+void t1(int32_t i32, uint32_t u32, int_fast32_t if32, uint_fast32_t uf32, int32_t *i_ptr, int_fast32_t *if_ptr, double *d_ptr) {
+ printf("%w32d", i32);
+ printf("%w32i", i32);
+ printf("%w32u", u32);
+ printf("%w32x", u32);
+ printf("%w32b", u32);
+ printf("%wf32d", if32);
+ printf("%wf32u", uf32);
+ printf("%wf32B", uf32);
+
+ printf("%w32d", 1.0); // expected-warning{{format specifies type 'int32_t' (aka 'int') but the argument has type 'double'}}
+ printf("%w32u", 1.0); // expected-warning{{format specifies type 'uint32_t' (aka 'unsigned int') but the argument has type 'double'}}
+ printf("%wf32d", 1.0); // expected-warning{{format specifies type 'int_fast32_t' (aka 'int') but the argument has type 'double'}}
+ printf("%wf32u", 1.0); // expected-warning{{format specifies type 'uint_fast32_t' (aka 'unsigned int') but the argument has type 'double'}}
+
+ printf("%w32n", i_ptr);
+ printf("%wf32n", if_ptr);
+ printf("%w32n", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
+}
+
+void t2(int32_t *i_ptr, uint32_t *u_ptr, int_fast32_t *if_ptr, uint_fast32_t *uf_ptr, double *d_ptr) {
+ scanf("%w32d", i_ptr);
+ scanf("%w32i", i_ptr);
+ scanf("%w32u", u_ptr);
+ scanf("%w32x", u_ptr);
+ scanf("%w32b", u_ptr);
+ scanf("%wf32d", if_ptr);
+ scanf("%wf32u", uf_ptr);
+
+ scanf("%w32d", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
+ scanf("%w32u", d_ptr); // expected-warning{{format specifies type 'uint32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
+ scanf("%wf32d", d_ptr); // expected-warning{{format specifies type 'int_fast32_t *' (aka 'int *') but the argument has type 'double *'}}
+ scanf("%wf32u", d_ptr); // expected-warning{{format specifies type 'uint_fast32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
+}
+
+void t3(const char *fmt) __attribute__((format_matches(printf, 1, "%w32d"))); // expected-note{{comparing with this specifier}}
+void t4(void) {
+ t3("%w32d");
+ t3("%w64d"); // expected-warning{{format specifier 'w64d' is incompatible with 'w32d'}}
+}
diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c
index bdb4466dc6ae8..59cf5562e91ef 100644
--- a/clang/test/Sema/format-strings.c
+++ b/clang/test/Sema/format-strings.c
@@ -790,6 +790,11 @@ void test_opencl_vector_format(int x) {
printf("%hld", x); // expected-warning{{invalid conversion specifier 'l'}}
}
+void test_int_width_modifiers(int x) {
+ printf("%w32d", x); // expected-warning {{invalid conversion specifier '3'}}
+ printf("%wf32d", 1.0); // expected-warning {{length modifier 'w' results in undefined behavior or no effect with 'f' conversion specifier}}
+}
+
// Test that we correctly merge the format in both orders.
extern void test14_foo(const char *, const char *, ...)
__attribute__((__format__(__printf__, 1, 3)));
>From 707543e0360b7a2a7ef3a01d0f3e866cbda97d1d Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 29 May 2026 19:49:57 +0300
Subject: [PATCH 2/9] add assert to handle invalid modifier length
---
clang/include/clang/AST/FormatString.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang/include/clang/AST/FormatString.h b/clang/include/clang/AST/FormatString.h
index 239d88ea33c7c..593af1ebf4f59 100644
--- a/clang/include/clang/AST/FormatString.h
+++ b/clang/include/clang/AST/FormatString.h
@@ -105,6 +105,8 @@ class LengthModifier {
return 2;
case AsIntN:
case AsFastIntN:
+ assert(ModifierLength != 0 &&
+ "C23 wN/wfN length modifiers must have a nonzero length");
return ModifierLength;
case AsInt32:
case AsInt64:
>From f8bce0e4204a8ef09ea48c03840e8e35be347c01 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 29 May 2026 19:51:32 +0300
Subject: [PATCH 3/9] rename makeIntNT to makeIntNType
---
clang/include/clang/AST/FormatString.h | 4 ++--
clang/lib/AST/FormatString.cpp | 4 ++--
clang/lib/AST/PrintfFormatString.cpp | 11 ++++++-----
clang/lib/AST/ScanfFormatString.cpp | 6 +++---
4 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/AST/FormatString.h b/clang/include/clang/AST/FormatString.h
index 593af1ebf4f59..75273651d3548 100644
--- a/clang/include/clang/AST/FormatString.h
+++ b/clang/include/clang/AST/FormatString.h
@@ -363,8 +363,8 @@ class ArgType {
return Res;
}
- static ArgType makeIntNT(ASTContext &Ctx, const LengthModifier &LengthMod,
- bool Signed);
+ static ArgType makeIntNType(ASTContext &Ctx, const LengthModifier &LengthMod,
+ bool Signed);
MatchKind matchesType(ASTContext &C, QualType argTy) const;
MatchKind matchesArgType(ASTContext &C, const ArgType &other) const;
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index 81a77a89268aa..0903dbc656c6a 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -810,8 +810,8 @@ ArgType ArgType::makeVectorType(ASTContext &C, unsigned NumElts) const {
return ArgType(Vec, Name);
}
-ArgType ArgType::makeIntNT(ASTContext &Ctx, const LengthModifier &LengthMod,
- bool Signed) {
+ArgType ArgType::makeIntNType(ASTContext &Ctx, const LengthModifier &LengthMod,
+ bool Signed) {
bool IsFast = LengthMod.getKind() == LengthModifier::AsFastIntN;
QualType Ty =
IsFast ? Ctx.getLeastIntTypeForBitwidth(LengthMod.getBitWidth(), Signed)
diff --git a/clang/lib/AST/PrintfFormatString.cpp b/clang/lib/AST/PrintfFormatString.cpp
index 64310193e1057..6c325eba2a3f7 100644
--- a/clang/lib/AST/PrintfFormatString.cpp
+++ b/clang/lib/AST/PrintfFormatString.cpp
@@ -603,9 +603,10 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t"));
case LengthModifier::AsIntN:
case LengthModifier::AsFastIntN:
- return ArgType::makeIntNT(Ctx, LM,
- CS.getKind() != ConversionSpecifier::bArg &&
- CS.getKind() != ConversionSpecifier::BArg);
+ return ArgType::makeIntNType(Ctx, LM,
+ CS.getKind() != ConversionSpecifier::bArg &&
+ CS.getKind() !=
+ ConversionSpecifier::BArg);
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsWide:
@@ -646,7 +647,7 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
ArgType(Ctx.getUnsignedPointerDiffType(), "unsigned ptrdiff_t"));
case LengthModifier::AsIntN:
case LengthModifier::AsFastIntN:
- return ArgType::makeIntNT(Ctx, LM, /*Signed=*/false);
+ return ArgType::makeIntNType(Ctx, LM, /*Signed=*/false);
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsWide:
@@ -694,7 +695,7 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")));
case LengthModifier::AsIntN:
case LengthModifier::AsFastIntN:
- return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/true));
+ return ArgType::PtrTo(ArgType::makeIntNType(Ctx, LM, /*Signed=*/true));
case LengthModifier::AsLongDouble:
return ArgType(); // FIXME: Is this a known extension?
case LengthModifier::AsAllocate:
diff --git a/clang/lib/AST/ScanfFormatString.cpp b/clang/lib/AST/ScanfFormatString.cpp
index 9b832376983d2..0af0c1458c76e 100644
--- a/clang/lib/AST/ScanfFormatString.cpp
+++ b/clang/lib/AST/ScanfFormatString.cpp
@@ -302,7 +302,7 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")));
case LengthModifier::AsIntN:
case LengthModifier::AsFastIntN:
- return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/true));
+ return ArgType::PtrTo(ArgType::makeIntNType(Ctx, LM, /*Signed=*/true));
case LengthModifier::AsLongDouble:
// GNU extension.
return ArgType::PtrTo(Ctx.LongLongTy);
@@ -349,7 +349,7 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
ArgType(Ctx.getUnsignedPointerDiffType(), "unsigned ptrdiff_t")));
case LengthModifier::AsIntN:
case LengthModifier::AsFastIntN:
- return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/false));
+ return ArgType::PtrTo(ArgType::makeIntNType(Ctx, LM, /*Signed=*/false));
case LengthModifier::AsLongDouble:
// GNU extension.
return ArgType::PtrTo(Ctx.UnsignedLongLongTy);
@@ -451,7 +451,7 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")));
case LengthModifier::AsIntN:
case LengthModifier::AsFastIntN:
- return ArgType::PtrTo(ArgType::makeIntNT(Ctx, LM, /*Signed=*/true));
+ return ArgType::PtrTo(ArgType::makeIntNType(Ctx, LM, /*Signed=*/true));
case LengthModifier::AsLongDouble:
return ArgType(); // FIXME: Is this a known extension?
case LengthModifier::AsAllocate:
>From ac89a5d5497c6d22af12186eaaaa7feb8fe2e6d2 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 29 May 2026 19:54:11 +0300
Subject: [PATCH 4/9] reject overflowing printf width modifiers
---
clang/lib/AST/FormatString.cpp | 3 +--
clang/test/Sema/format-strings-c23.c | 1 +
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index 0903dbc656c6a..65a1aba055988 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -16,7 +16,6 @@
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ConvertUTF.h"
-#include <limits>
#include <optional>
using clang::analyze_format_string::ArgType;
@@ -68,7 +67,7 @@ static bool ParseWidthModifier(const char *&I, const char *E,
return false;
if (W.getAsInteger(10, BitWidth))
- BitWidth = std::numeric_limits<unsigned>::max();
+ return false;
for (const char *End = W.end(); I != End; ++I)
++ModifierLength;
diff --git a/clang/test/Sema/format-strings-c23.c b/clang/test/Sema/format-strings-c23.c
index 7156dc21981de..e2fb8baea4843 100644
--- a/clang/test/Sema/format-strings-c23.c
+++ b/clang/test/Sema/format-strings-c23.c
@@ -22,6 +22,7 @@ void t1(int32_t i32, uint32_t u32, int_fast32_t if32, uint_fast32_t uf32, int32_
printf("%w32u", 1.0); // expected-warning{{format specifies type 'uint32_t' (aka 'unsigned int') but the argument has type 'double'}}
printf("%wf32d", 1.0); // expected-warning{{format specifies type 'int_fast32_t' (aka 'int') but the argument has type 'double'}}
printf("%wf32u", 1.0); // expected-warning{{format specifies type 'uint_fast32_t' (aka 'unsigned int') but the argument has type 'double'}}
+ printf("%w18446744073709551616d", i32); // expected-warning{{invalid conversion specifier '1'}}
printf("%w32n", i_ptr);
printf("%wf32n", if_ptr);
>From 9c0aa3dc3d2e6fef750d52afc6fbacfc856bda02 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 29 May 2026 20:18:30 +0300
Subject: [PATCH 5/9] avoid redundant loop
---
clang/lib/AST/FormatString.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index 65a1aba055988..3a746c7549492 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -69,8 +69,8 @@ static bool ParseWidthModifier(const char *&I, const char *E,
if (W.getAsInteger(10, BitWidth))
return false;
- for (const char *End = W.end(); I != End; ++I)
- ++ModifierLength;
+ I = W.end();
+ ModifierLength += W.size();
return true;
}
>From 93c6d709248104874c753180f361ee32ade7f51a Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 29 May 2026 21:16:40 +0300
Subject: [PATCH 6/9] update tests
---
clang/test/Sema/format-strings-c23.c | 100 +++++++++++++++++++++------
1 file changed, 77 insertions(+), 23 deletions(-)
diff --git a/clang/test/Sema/format-strings-c23.c b/clang/test/Sema/format-strings-c23.c
index e2fb8baea4843..81295ea8914d3 100644
--- a/clang/test/Sema/format-strings-c23.c
+++ b/clang/test/Sema/format-strings-c23.c
@@ -1,42 +1,84 @@
-// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c23 -ffreestanding -fsyntax-only -verify %s
-typedef __INT32_TYPE__ int32_t;
-typedef __UINT32_TYPE__ uint32_t;
-typedef __INT_FAST32_TYPE__ int_fast32_t;
-typedef __UINT_FAST32_TYPE__ uint_fast32_t;
+#include <stdint.h>
-int printf(const char *restrict, ...);
-int scanf(const char *restrict, ...);
+int printf(const char *restrict, ...) __attribute__((format(printf, 1, 2)));
+int scanf(const char *restrict, ...) __attribute__((format(scanf, 1, 2)));
-void t1(int32_t i32, uint32_t u32, int_fast32_t if32, uint_fast32_t uf32, int32_t *i_ptr, int_fast32_t *if_ptr, double *d_ptr) {
+void t1(int8_t i8, uint8_t u8, int16_t i16, uint16_t u16, int32_t i32,
+ uint32_t u32, int64_t i64, uint64_t u64, int_fast8_t if8,
+ uint_fast8_t uf8, int_fast16_t if16, uint_fast16_t uf16,
+ int_fast32_t if32, uint_fast32_t uf32, int_fast64_t if64,
+ uint_fast64_t uf64) {
+ printf("%w8d", i8);
+ printf("%w8u", u8);
+ printf("%w16d", i16);
+ printf("%w16u", u16);
printf("%w32d", i32);
printf("%w32i", i32);
printf("%w32u", u32);
printf("%w32x", u32);
printf("%w32b", u32);
+ printf("%w64d", i64);
+ printf("%w64u", u64);
+ printf("%wf8d", if8);
+ printf("%wf8u", uf8);
+ printf("%wf16d", if16);
+ printf("%wf16u", uf16);
printf("%wf32d", if32);
printf("%wf32u", uf32);
printf("%wf32B", uf32);
+ printf("%wf64d", if64);
+ printf("%wf64u", uf64);
printf("%w32d", 1.0); // expected-warning{{format specifies type 'int32_t' (aka 'int') but the argument has type 'double'}}
printf("%w32u", 1.0); // expected-warning{{format specifies type 'uint32_t' (aka 'unsigned int') but the argument has type 'double'}}
printf("%wf32d", 1.0); // expected-warning{{format specifies type 'int_fast32_t' (aka 'int') but the argument has type 'double'}}
printf("%wf32u", 1.0); // expected-warning{{format specifies type 'uint_fast32_t' (aka 'unsigned int') but the argument has type 'double'}}
printf("%w18446744073709551616d", i32); // expected-warning{{invalid conversion specifier '1'}}
+}
- printf("%w32n", i_ptr);
- printf("%wf32n", if_ptr);
- printf("%w32n", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
+void t2(int8_t *i8_ptr, int16_t *i16_ptr, int32_t *i32_ptr,
+ int64_t *i64_ptr, int_fast8_t *if8_ptr, int_fast16_t *if16_ptr,
+ int_fast32_t *if32_ptr, int_fast64_t *if64_ptr, double *d_ptr) {
+ printf("%w8n", i8_ptr);
+ printf("%w16n", i16_ptr);
+ printf("%w32n", i32_ptr);
+ printf("%w64n", i64_ptr);
+ printf("%wf8n", if8_ptr);
+ printf("%wf16n", if16_ptr);
+ printf("%wf32n", if32_ptr);
+ printf("%wf64n", if64_ptr);
+ printf("%w32n", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
+ printf("%wf32n", d_ptr); // expected-warning{{format specifies type 'int_fast32_t *' (aka 'int *') but the argument has type 'double *'}}
}
-void t2(int32_t *i_ptr, uint32_t *u_ptr, int_fast32_t *if_ptr, uint_fast32_t *uf_ptr, double *d_ptr) {
- scanf("%w32d", i_ptr);
- scanf("%w32i", i_ptr);
- scanf("%w32u", u_ptr);
- scanf("%w32x", u_ptr);
- scanf("%w32b", u_ptr);
- scanf("%wf32d", if_ptr);
- scanf("%wf32u", uf_ptr);
+void t3(int8_t *i8_ptr, uint8_t *u8_ptr, int16_t *i16_ptr,
+ uint16_t *u16_ptr, int32_t *i32_ptr, uint32_t *u32_ptr,
+ int64_t *i64_ptr, uint64_t *u64_ptr, int_fast8_t *if8_ptr,
+ uint_fast8_t *uf8_ptr, int_fast16_t *if16_ptr,
+ uint_fast16_t *uf16_ptr, int_fast32_t *if32_ptr,
+ uint_fast32_t *uf32_ptr, int_fast64_t *if64_ptr,
+ uint_fast64_t *uf64_ptr, double *d_ptr) {
+ scanf("%w8d", i8_ptr);
+ scanf("%w8u", u8_ptr);
+ scanf("%w16d", i16_ptr);
+ scanf("%w16u", u16_ptr);
+ scanf("%w32d", i32_ptr);
+ scanf("%w32i", i32_ptr);
+ scanf("%w32u", u32_ptr);
+ scanf("%w32x", u32_ptr);
+ scanf("%w32b", u32_ptr);
+ scanf("%w64d", i64_ptr);
+ scanf("%w64u", u64_ptr);
+ scanf("%wf8d", if8_ptr);
+ scanf("%wf8u", uf8_ptr);
+ scanf("%wf16d", if16_ptr);
+ scanf("%wf16u", uf16_ptr);
+ scanf("%wf32d", if32_ptr);
+ scanf("%wf32u", uf32_ptr);
+ scanf("%wf64d", if64_ptr);
+ scanf("%wf64u", uf64_ptr);
scanf("%w32d", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
scanf("%w32u", d_ptr); // expected-warning{{format specifies type 'uint32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
@@ -44,8 +86,20 @@ void t2(int32_t *i_ptr, uint32_t *u_ptr, int_fast32_t *if_ptr, uint_fast32_t *uf
scanf("%wf32u", d_ptr); // expected-warning{{format specifies type 'uint_fast32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
}
-void t3(const char *fmt) __attribute__((format_matches(printf, 1, "%w32d"))); // expected-note{{comparing with this specifier}}
-void t4(void) {
- t3("%w32d");
- t3("%w64d"); // expected-warning{{format specifier 'w64d' is incompatible with 'w32d'}}
+void t4(const char *fmt) __attribute__((format_matches(printf, 1, "%w32d"))); // expected-note{{comparing with this specifier}}
+void t5(void) {
+ t4("%w32d");
+ t4("%w64d"); // expected-warning{{format specifier 'w64d' is incompatible with 'w32d'}}
+}
+
+#ifdef INT56_WIDTH
+void t6(int56_t i56, uint56_t u56, int_fast56_t if56,
+ uint_fast56_t uf56, int56_t *i56_ptr, uint56_t *u56_ptr) {
+ printf("%w56d", i56);
+ printf("%w56u", u56);
+ printf("%wf56d", if56);
+ printf("%wf56u", uf56);
+ scanf("%w56d", i56_ptr);
+ scanf("%w56u", u56_ptr);
}
+#endif
>From f3c83dcdfa8b62769f36c8d35be7d37a6c2772ee Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Sat, 30 May 2026 10:15:26 +0300
Subject: [PATCH 7/9] cleanup
---
clang/test/Sema/format-strings-c23.c | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/clang/test/Sema/format-strings-c23.c b/clang/test/Sema/format-strings-c23.c
index 81295ea8914d3..5866951e33a44 100644
--- a/clang/test/Sema/format-strings-c23.c
+++ b/clang/test/Sema/format-strings-c23.c
@@ -91,15 +91,3 @@ void t5(void) {
t4("%w32d");
t4("%w64d"); // expected-warning{{format specifier 'w64d' is incompatible with 'w32d'}}
}
-
-#ifdef INT56_WIDTH
-void t6(int56_t i56, uint56_t u56, int_fast56_t if56,
- uint_fast56_t uf56, int56_t *i56_ptr, uint56_t *u56_ptr) {
- printf("%w56d", i56);
- printf("%w56u", u56);
- printf("%wf56d", if56);
- printf("%wf56u", uf56);
- scanf("%w56d", i56_ptr);
- scanf("%w56u", u56_ptr);
-}
-#endif
>From 34477eb2c271d833e678480c7d40adedde3bc3de Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 4 Jun 2026 21:30:41 +0300
Subject: [PATCH 8/9] add additional tests
---
clang/lib/AST/FormatString.cpp | 2 +-
clang/test/Sema/format-strings-c23.c | 16 ++++++++++++----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index 3a746c7549492..2163eacbe2ad7 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -499,7 +499,7 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const {
}
case SpecificTy: {
- if (TK != TypeKind::DontCare) {
+ if (TK == TypeKind::SizeT || TK == TypeKind::PtrdiffT) {
return matchesSizeTPtrdiffT(C, argTy, T);
}
diff --git a/clang/test/Sema/format-strings-c23.c b/clang/test/Sema/format-strings-c23.c
index 5866951e33a44..0b59b89260b52 100644
--- a/clang/test/Sema/format-strings-c23.c
+++ b/clang/test/Sema/format-strings-c23.c
@@ -31,6 +31,12 @@ void t1(int8_t i8, uint8_t u8, int16_t i16, uint16_t u16, int32_t i32,
printf("%wf64d", if64);
printf("%wf64u", uf64);
+ printf("%w32d", 1);
+ printf("%w32d", i32);
+ printf("%w16d", 1);
+ printf("%w16d", 65536);
+ printf("%w16d", i32);
+
printf("%w32d", 1.0); // expected-warning{{format specifies type 'int32_t' (aka 'int') but the argument has type 'double'}}
printf("%w32u", 1.0); // expected-warning{{format specifies type 'uint32_t' (aka 'unsigned int') but the argument has type 'double'}}
printf("%wf32d", 1.0); // expected-warning{{format specifies type 'int_fast32_t' (aka 'int') but the argument has type 'double'}}
@@ -80,10 +86,12 @@ void t3(int8_t *i8_ptr, uint8_t *u8_ptr, int16_t *i16_ptr,
scanf("%wf64d", if64_ptr);
scanf("%wf64u", uf64_ptr);
- scanf("%w32d", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
- scanf("%w32u", d_ptr); // expected-warning{{format specifies type 'uint32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
- scanf("%wf32d", d_ptr); // expected-warning{{format specifies type 'int_fast32_t *' (aka 'int *') but the argument has type 'double *'}}
- scanf("%wf32u", d_ptr); // expected-warning{{format specifies type 'uint_fast32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
+ scanf("%w32d", d_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'double *'}}
+ scanf("%w32u", d_ptr); // expected-warning{{format specifies type 'uint32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
+ scanf("%wf32d", d_ptr); // expected-warning{{format specifies type 'int_fast32_t *' (aka 'int *') but the argument has type 'double *'}}
+ scanf("%wf32u", d_ptr); // expected-warning{{format specifies type 'uint_fast32_t *' (aka 'unsigned int *') but the argument has type 'double *'}}
+ scanf("%w16d", i32_ptr); // expected-warning{{format specifies type 'int16_t *' (aka 'short *') but the argument has type 'int32_t *' (aka 'int *')}}
+ scanf("%w32d", i16_ptr); // expected-warning{{format specifies type 'int32_t *' (aka 'int *') but the argument has type 'int16_t *' (aka 'short *')}}
}
void t4(const char *fmt) __attribute__((format_matches(printf, 1, "%w32d"))); // expected-note{{comparing with this specifier}}
>From c64868b5de57d05d77ab57b4d388b9d07e5a2db6 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 5 Jun 2026 16:21:18 +0300
Subject: [PATCH 9/9] add additinal cases
---
clang/test/Sema/format-strings-c23.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/clang/test/Sema/format-strings-c23.c b/clang/test/Sema/format-strings-c23.c
index 0b59b89260b52..86ddcaa67a828 100644
--- a/clang/test/Sema/format-strings-c23.c
+++ b/clang/test/Sema/format-strings-c23.c
@@ -5,7 +5,7 @@
int printf(const char *restrict, ...) __attribute__((format(printf, 1, 2)));
int scanf(const char *restrict, ...) __attribute__((format(scanf, 1, 2)));
-void t1(int8_t i8, uint8_t u8, int16_t i16, uint16_t u16, int32_t i32,
+void t1(int i, int8_t i8, uint8_t u8, int16_t i16, uint16_t u16, int32_t i32,
uint32_t u32, int64_t i64, uint64_t u64, int_fast8_t if8,
uint_fast8_t uf8, int_fast16_t if16, uint_fast16_t uf16,
int_fast32_t if32, uint_fast32_t uf32, int_fast64_t if64,
@@ -32,10 +32,10 @@ void t1(int8_t i8, uint8_t u8, int16_t i16, uint16_t u16, int32_t i32,
printf("%wf64u", uf64);
printf("%w32d", 1);
- printf("%w32d", i32);
+ printf("%w32d", i);
printf("%w16d", 1);
printf("%w16d", 65536);
- printf("%w16d", i32);
+ printf("%w16d", i);
printf("%w32d", 1.0); // expected-warning{{format specifies type 'int32_t' (aka 'int') but the argument has type 'double'}}
printf("%w32u", 1.0); // expected-warning{{format specifies type 'uint32_t' (aka 'unsigned int') but the argument has type 'double'}}
@@ -59,13 +59,15 @@ void t2(int8_t *i8_ptr, int16_t *i16_ptr, int32_t *i32_ptr,
printf("%wf32n", d_ptr); // expected-warning{{format specifies type 'int_fast32_t *' (aka 'int *') but the argument has type 'double *'}}
}
-void t3(int8_t *i8_ptr, uint8_t *u8_ptr, int16_t *i16_ptr,
+void t3(int *int_ptr, int8_t *i8_ptr, uint8_t *u8_ptr, int16_t *i16_ptr,
uint16_t *u16_ptr, int32_t *i32_ptr, uint32_t *u32_ptr,
int64_t *i64_ptr, uint64_t *u64_ptr, int_fast8_t *if8_ptr,
uint_fast8_t *uf8_ptr, int_fast16_t *if16_ptr,
uint_fast16_t *uf16_ptr, int_fast32_t *if32_ptr,
uint_fast32_t *uf32_ptr, int_fast64_t *if64_ptr,
uint_fast64_t *uf64_ptr, double *d_ptr) {
+ scanf("%w32d", int_ptr);
+
scanf("%w8d", i8_ptr);
scanf("%w8u", u8_ptr);
scanf("%w16d", i16_ptr);
More information about the cfe-commits
mailing list