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 <[email protected]> Date: Wed, 27 May 2026 16:28:30 +0300 Subject: [PATCH 1/7] [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 <[email protected]> Date: Fri, 29 May 2026 19:49:57 +0300 Subject: [PATCH 2/7] 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 <[email protected]> Date: Fri, 29 May 2026 19:51:32 +0300 Subject: [PATCH 3/7] 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 <[email protected]> Date: Fri, 29 May 2026 19:54:11 +0300 Subject: [PATCH 4/7] 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 <[email protected]> Date: Fri, 29 May 2026 20:18:30 +0300 Subject: [PATCH 5/7] 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 <[email protected]> Date: Fri, 29 May 2026 21:16:40 +0300 Subject: [PATCH 6/7] 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 <[email protected]> Date: Sat, 30 May 2026 10:15:26 +0300 Subject: [PATCH 7/7] 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
