[clang] [Sema] Suggest missing format attributes (PR #166738)
Vladimir Vuksanovic via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 27 00:01:10 PST 2025
https://github.com/vvuksanovic updated https://github.com/llvm/llvm-project/pull/166738
>From aebddd9138b5aedd941739dcf5f8bf08357ad0b7 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Fri, 31 Oct 2025 02:10:05 -0700
Subject: [PATCH 01/12] [Sema] Suggest missing format attributes
Implements the `-Wmissing-format-attribute` diagnostic. It suggests
adding format attributes to function declarations that call other
format functions and pass the format string and arguments to them.
Co-authored-by: Budimir Arandjelovic <budimir.arandjelovic at htecgroup.com>
---
clang/docs/ReleaseNotes.rst | 2 +
clang/include/clang/Basic/DiagnosticGroups.td | 1 -
.../clang/Basic/DiagnosticSemaKinds.td | 5 +
clang/lib/Sema/SemaChecking.cpp | 125 ++++++++--
clang/lib/Sema/SemaDeclAttr.cpp | 4 +-
clang/test/Sema/attr-format-missing.c | 217 ++++++++++++++++++
clang/test/Sema/attr-format-missing.cpp | 68 ++++++
7 files changed, 397 insertions(+), 25 deletions(-)
create mode 100644 clang/test/Sema/attr-format-missing.c
create mode 100644 clang/test/Sema/attr-format-missing.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 32f669f8d70d8..be384b7db72a3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -401,6 +401,8 @@ Improvements to Clang's diagnostics
or continue (#GH166013)
- Clang now emits a diagnostic in case `vector_size` or `ext_vector_type`
attributes are used with a negative size (#GH165463).
+- Clang now detects potential missing format attributes on function declarations
+ when calling format functions. (#GH60718)
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 1e0321de3f4b6..7b534d416fdd9 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -607,7 +607,6 @@ def MainReturnType : DiagGroup<"main-return-type">;
def MaxUnsignedZero : DiagGroup<"max-unsigned-zero">;
def MissingBraces : DiagGroup<"missing-braces">;
def MissingDeclarations: DiagGroup<"missing-declarations">;
-def : DiagGroup<"missing-format-attribute">;
def MissingIncludeDirs : DiagGroup<"missing-include-dirs">;
def MissingNoreturn : DiagGroup<"missing-noreturn">;
def MultiChar : DiagGroup<"multichar">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fa509536bf021..78c5caf79eab7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3478,6 +3478,11 @@ def err_format_attribute_result_not : Error<"function does not return %0">;
def err_format_attribute_implicit_this_format_string : Error<
"format attribute cannot specify the implicit this argument as the format "
"string">;
+def warn_missing_format_attribute
+ : Warning<"diagnostic behavior may be improved by adding the '%0' format "
+ "attribute to the declaration of %1">,
+ InGroup<DiagGroup<"missing-format-attribute">>,
+ DefaultIgnore;
def err_callback_attribute_no_callee : Error<
"'callback' attribute specifies no callback callee">;
def err_callback_attribute_invalid_callee : Error<
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index ad2c2e4a97bb9..502e9fd099144 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -6517,7 +6517,8 @@ static StringLiteralCheckType checkFormatStringExpr(
unsigned format_idx, unsigned firstDataArg, FormatStringType Type,
VariadicCallType CallType, bool InFunctionCall,
llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg,
- llvm::APSInt Offset, bool IgnoreStringsWithoutSpecifiers = false) {
+ llvm::APSInt Offset, std::optional<unsigned> *CallerParamIdx = nullptr,
+ bool IgnoreStringsWithoutSpecifiers = false) {
if (S.isConstantEvaluatedContext())
return SLCT_NotALiteral;
tryAgain:
@@ -6542,7 +6543,7 @@ static StringLiteralCheckType checkFormatStringExpr(
return checkFormatStringExpr(
S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg,
Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs,
- UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
+ UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
}
return SLCT_NotALiteral;
case Stmt::BinaryConditionalOperatorClass:
@@ -6577,7 +6578,7 @@ static StringLiteralCheckType checkFormatStringExpr(
Left = checkFormatStringExpr(
S, ReferenceFormatString, C->getTrueExpr(), Args, APK, format_idx,
firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
- UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
+ UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
if (Left == SLCT_NotALiteral || !CheckRight) {
return Left;
}
@@ -6586,7 +6587,7 @@ static StringLiteralCheckType checkFormatStringExpr(
StringLiteralCheckType Right = checkFormatStringExpr(
S, ReferenceFormatString, C->getFalseExpr(), Args, APK, format_idx,
firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
- UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
+ UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
return (CheckLeft && Left < Right) ? Left : Right;
}
@@ -6635,10 +6636,11 @@ static StringLiteralCheckType checkFormatStringExpr(
if (InitList->isStringLiteralInit())
Init = InitList->getInit(0)->IgnoreParenImpCasts();
}
- return checkFormatStringExpr(
- S, ReferenceFormatString, Init, Args, APK, format_idx,
- firstDataArg, Type, CallType,
- /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, Offset);
+ return checkFormatStringExpr(S, ReferenceFormatString, Init, Args,
+ APK, format_idx, firstDataArg, Type,
+ CallType,
+ /*InFunctionCall*/ false, CheckedVarArgs,
+ UncoveredArg, Offset, CallerParamIdx);
}
}
@@ -6690,6 +6692,8 @@ static StringLiteralCheckType checkFormatStringExpr(
// format arguments, in all cases.
//
if (const auto *PV = dyn_cast<ParmVarDecl>(VD)) {
+ if (CallerParamIdx)
+ *CallerParamIdx = PV->getFunctionScopeIndex();
if (const auto *D = dyn_cast<Decl>(PV->getDeclContext())) {
for (const auto *PVFormatMatches :
D->specific_attrs<FormatMatchesAttr>()) {
@@ -6715,7 +6719,7 @@ static StringLiteralCheckType checkFormatStringExpr(
S, ReferenceFormatString, PVFormatMatches->getFormatString(),
Args, APK, format_idx, firstDataArg, Type, CallType,
/*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg,
- Offset, IgnoreStringsWithoutSpecifiers);
+ Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
}
}
@@ -6770,7 +6774,7 @@ static StringLiteralCheckType checkFormatStringExpr(
StringLiteralCheckType Result = checkFormatStringExpr(
S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg,
Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg,
- Offset, IgnoreStringsWithoutSpecifiers);
+ Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
if (IsFirst) {
CommonResult = Result;
IsFirst = false;
@@ -6784,10 +6788,11 @@ static StringLiteralCheckType checkFormatStringExpr(
if (BuiltinID == Builtin::BI__builtin___CFStringMakeConstantString ||
BuiltinID == Builtin::BI__builtin___NSStringMakeConstantString) {
const Expr *Arg = CE->getArg(0);
- return checkFormatStringExpr(
- S, ReferenceFormatString, Arg, Args, APK, format_idx,
- firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
- UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
+ return checkFormatStringExpr(S, ReferenceFormatString, Arg, Args, APK,
+ format_idx, firstDataArg, Type, CallType,
+ InFunctionCall, CheckedVarArgs,
+ UncoveredArg, Offset, CallerParamIdx,
+ IgnoreStringsWithoutSpecifiers);
}
}
}
@@ -6795,7 +6800,7 @@ static StringLiteralCheckType checkFormatStringExpr(
return checkFormatStringExpr(
S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg,
Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs,
- UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
+ UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
return SLCT_NotALiteral;
}
case Stmt::ObjCMessageExprClass: {
@@ -6821,7 +6826,7 @@ static StringLiteralCheckType checkFormatStringExpr(
return checkFormatStringExpr(
S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg,
Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg,
- Offset, IgnoreStringsWithoutSpecifiers);
+ Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
}
}
@@ -7001,6 +7006,77 @@ bool Sema::CheckFormatString(const FormatMatchesAttr *Format,
return false;
}
+static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
+ unsigned FormatIdx, unsigned FirstArg,
+ ArrayRef<const Expr *> Args,
+ Sema::FormatArgumentPassingKind APK,
+ unsigned CallerParamIdx,
+ SourceLocation Loc) {
+ const FunctionDecl *Caller = S->getCurFunctionDecl();
+ if (!Caller)
+ return;
+
+ // Find the offset to convert between attribute and parameter indexes.
+ unsigned CallerArgumentIndexOffset =
+ hasImplicitObjectParameter(Caller) ? 2 : 1;
+
+ unsigned FirstArgumentIndex = -1;
+ switch (APK) {
+ case Sema::FormatArgumentPassingKind::FAPK_Fixed:
+ case Sema::FormatArgumentPassingKind::FAPK_Variadic: {
+ // As an extension, clang allows the format attribute on non-variadic
+ // functions.
+ // Caller must have fixed arguments to pass them to a fixed or variadic
+ // function. Try to match caller and callee arguments. If successful, then
+ // emit a diag with the caller idx, otherwise we can't determine the callee
+ // arguments.
+ unsigned NumCalleeArgs = Args.size() - FirstArg;
+ if (NumCalleeArgs == 0 || Caller->getNumParams() < NumCalleeArgs) {
+ // There aren't enough arugments in the caller to pass to callee.
+ return;
+ }
+ for (unsigned CalleeIdx = Args.size() - 1,
+ CallerIdx = Caller->getNumParams() - 1;
+ CalleeIdx >= FirstArg; --CalleeIdx, --CallerIdx) {
+ const auto *Arg =
+ dyn_cast<DeclRefExpr>(Args[CalleeIdx]->IgnoreParenCasts());
+ if (!Arg)
+ return;
+ const auto *Param = dyn_cast<ParmVarDecl>(Arg->getDecl());
+ if (!Param || Param->getFunctionScopeIndex() != CallerIdx)
+ return;
+ }
+ FirstArgumentIndex =
+ Caller->getNumParams() + CallerArgumentIndexOffset - NumCalleeArgs;
+ break;
+ }
+ case Sema::FormatArgumentPassingKind::FAPK_VAList:
+ // Caller arguments are either variadic or a va_list.
+ FirstArgumentIndex =
+ Caller->isVariadic()
+ ? (Caller->getNumParams() + CallerArgumentIndexOffset)
+ : 0;
+ break;
+ case Sema::FormatArgumentPassingKind::FAPK_Elsewhere:
+ // Args are not passed to the callee.
+ return;
+ }
+
+ // Emit the diagnostic and fixit.
+ unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
+ StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
+ S->Diag(Loc, diag::warn_missing_format_attribute)
+ << FormatTypeName << Caller
+ << FixItHint::CreateInsertion(Caller->getFirstDecl()->getLocation(),
+ (llvm::Twine("__attribute__((format(") +
+ FormatTypeName + ", " +
+ llvm::Twine(FormatStringIndex) + ", " +
+ llvm::Twine(FirstArgumentIndex) + ")))")
+ .str());
+ S->Diag(Caller->getFirstDecl()->getLocation(), diag::note_callee_decl)
+ << Caller;
+}
+
bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
Sema::FormatArgumentPassingKind APK,
const StringLiteral *ReferenceFormatString,
@@ -7030,11 +7106,12 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
// ObjC string uses the same format specifiers as C string, so we can use
// the same format string checking logic for both ObjC and C strings.
UncoveredArgHandler UncoveredArg;
+ std::optional<unsigned> CallerParamIdx;
StringLiteralCheckType CT = checkFormatStringExpr(
*this, ReferenceFormatString, OrigFormatExpr, Args, APK, format_idx,
firstDataArg, Type, CallType,
/*IsFunctionCall*/ true, CheckedVarArgs, UncoveredArg,
- /*no string offset*/ llvm::APSInt(64, false) = 0);
+ /*no string offset*/ llvm::APSInt(64, false) = 0, &CallerParamIdx);
// Generate a diagnostic where an uncovered argument is detected.
if (UncoveredArg.hasUncoveredArg()) {
@@ -7047,11 +7124,6 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
// Literal format string found, check done!
return CT == SLCT_CheckedLiteral;
- // Strftime is particular as it always uses a single 'time' argument,
- // so it is safe to pass a non-literal string.
- if (Type == FormatStringType::Strftime)
- return false;
-
// Do not emit diag when the string param is a macro expansion and the
// format is either NSString or CFString. This is a hack to prevent
// diag when using the NSLocalizedString and CFCopyLocalizedString macros
@@ -7061,6 +7133,15 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
SourceMgr.isInSystemMacro(FormatLoc))
return false;
+ if (CallerParamIdx)
+ CheckMissingFormatAttributes(this, Type, format_idx, firstDataArg, Args,
+ APK, *CallerParamIdx, Loc);
+
+ // Strftime is particular as it always uses a single 'time' argument,
+ // so it is safe to pass a non-literal string.
+ if (Type == FormatStringType::Strftime)
+ return false;
+
// If there are no arguments specified, warn with -Wformat-security, otherwise
// warn only with -Wformat-nonliteral.
if (Args.size() == firstDataArg) {
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index a9e7b44ac9d73..9849e125889cf 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3572,7 +3572,7 @@ static void handleEnumExtensibilityAttr(Sema &S, Decl *D,
}
/// Handle __attribute__((format_arg((idx)))) attribute based on
-/// http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
+/// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
static void handleFormatArgAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
const Expr *IdxExpr = AL.getArgAsExpr(0);
ParamIdx Idx;
@@ -3771,7 +3771,7 @@ struct FormatAttrCommon {
};
/// Handle __attribute__((format(type,idx,firstarg))) attributes based on
-/// http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
+/// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
static bool handleFormatAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
FormatAttrCommon *Info) {
// Checks the first two arguments of the attribute; this is shared between
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
new file mode 100644
index 0000000000000..7e5131153f661
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.c
@@ -0,0 +1,217 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+
+typedef unsigned long size_t;
+typedef long ssize_t;
+typedef __builtin_va_list va_list;
+
+__attribute__((format(printf, 1, 2)))
+int printf(const char *, ...);
+
+__attribute__((format(scanf, 1, 2)))
+int scanf(const char *, ...);
+
+__attribute__((format(printf, 1, 0)))
+int vprintf(const char *, va_list);
+
+__attribute__((format(scanf, 1, 0)))
+int vscanf(const char *, va_list);
+
+__attribute__((format(printf, 2, 0)))
+int vsprintf(char *, const char *, va_list);
+
+struct tm { unsigned i; };
+__attribute__((format(strftime, 3, 0)))
+size_t strftime(char *, size_t, const char *, const struct tm *);
+
+__attribute__((format(strfmon, 3, 4)))
+ssize_t strfmon(char *, size_t, const char *, ...);
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))"
+void f1(char *out, va_list args) // #f1
+{
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
+ // expected-note@#f1 {{'f1' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 0)))"
+void f2(const char *out, va_list args) // #f2
+{
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}}
+ // expected-note@#f2 {{'f2' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+void f3(const char *out, ... /* args */) // #f3
+{
+ va_list args;
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}}
+ // expected-note@#f3 {{'f3' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))"
+void f4(const char *out, ... /* args */) // #f4
+{
+ va_list args;
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f4'}}
+ // expected-note@#f4 {{'f4' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 2, 3)))"
+__attribute__((format(printf, 1, 3)))
+void f5(char *out, const char *format, ... /* args */) // #f5
+{
+ va_list args;
+ vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f5'}}
+ // expected-note@#f5 {{'f5' declared here}}
+}
+
+__attribute__((format(scanf, 1, 3)))
+void f6(char *out, const char *format, ... /* args */) // #f6
+{
+ va_list args;
+ vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f6'}}
+ // expected-note@#f6 {{'f6' declared here}}
+}
+
+// Ok, out is not passed to print functions.
+void f7(char* out, ... /* args */)
+{
+ va_list args;
+
+ const char *ch = "format";
+ vprintf(ch, args);
+ vprintf("test", args);
+}
+
+// Ok, out is not passed to print functions.
+void f8(char *out, va_list args)
+{
+ const char *ch = "format";
+ vprintf(ch, args);
+ vprintf("test", args);
+}
+
+// Ok, out is not passed to scan functions.
+void f9(va_list args)
+{
+ const char *ch = "format";
+ vscanf(ch, args);
+ vscanf("test", args);
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+void f10(const char *out, ... /* args */) // #f10
+{
+ va_list args;
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}}
+ // expected-note@#f10 {{'f10' declared here}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}}
+ // expected-note@#f10 {{'f10' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+void f11(const char out[], ... /* args */) // #f11
+{
+ va_list args;
+ char ch[10] = "format";
+ vprintf(ch, args);
+ vsprintf(ch, out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f11'}}
+ // expected-note@#f11 {{'f11' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))"
+void f12(char* out) // #f12
+{
+ va_list args;
+ const char *ch = "format";
+ vsprintf(out, ch, args);
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f12'}}
+ // expected-note@#f12 {{'f12' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+void f13(char *out, ... /* args */) // #f13
+{
+ va_list args;
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}}
+ // expected-note@#f13 {{'f13' declared here}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f13'}}
+ // expected-note@#f13 {{'f13' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 0)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))"
+void f14(char *out, va_list args) // #f14
+{
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}}
+ // expected-note@#f14 {{'f14' declared here}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f14'}}
+ // expected-note@#f14 {{'f14' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))"
+void f15(char *out, ... /* args */) // #f15
+{
+ va_list args;
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ // expected-note@#f15 {{'f15' declared here}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ // expected-note@#f15 {{'f15' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 1, 3)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 2, 3)))"
+void f16(char *ch, const char *out, ... /* args */) // #f16
+{
+ va_list args;
+ vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}}
+ // expected-note@#f16 {{'f16' declared here}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}}
+ // expected-note@#f16 {{'f16' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+void f17(const char *a, ...) // #f17
+{
+ va_list ap;
+ const char *const b = a;
+ vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}}
+ // expected-note@#f17 {{'f17' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18
+{
+ printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}}
+ // expected-note@#f18 {{'f18' declared here}}
+}
+
+void f19(char *fmt, unsigned x, unsigned y, unsigned z) // #f19
+{
+ // Arguments are not passed in the same order.
+ printf(fmt, x, z, y);
+}
+
+void f20(char *out, ... /* args */)
+{
+ printf(out, 1); // No warning, arguments are not passed to printf.
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strftime, 3, 0)))"
+void f21(char *out, const size_t len, const char *format) // #f21
+{
+ struct tm tm_arg;
+ tm_arg.i = 0;
+ strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'strftime' format attribute to the declaration of 'f21'}}
+ // expected-note@#f21 {{'f21' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strfmon, 3, 4)))"
+void f22(char *out, const size_t len, const char *format, int x, int y) // #f22
+{
+ strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'strfmon' format attribute to the declaration of 'f22'}}
+ // expected-note@#f22 {{'f22' declared here}}
+}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
new file mode 100644
index 0000000000000..90d667531ce0a
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -0,0 +1,68 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits -std=c++23 %s 2>&1 | FileCheck %s
+
+typedef __SIZE_TYPE__ size_t;
+typedef __builtin_va_list va_list;
+
+__attribute__((__format__(__printf__, 1, 2)))
+int printf(const char *, ...);
+
+__attribute__((__format__(__scanf__, 1, 2)))
+int scanf(const char *, ...);
+
+__attribute__((__format__(__printf__, 1, 0)))
+int vprintf(const char *, va_list);
+
+__attribute__((__format__(__scanf__, 1, 0)))
+int vscanf(const char *, va_list);
+
+__attribute__((__format__(__printf__, 2, 0)))
+int vsprintf(char *, const char *, va_list);
+
+__attribute__((__format__(__printf__, 3, 0)))
+int vsnprintf(char *, size_t, const char *, va_list);
+
+struct S1
+{
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(scanf, 2, 3)))"
+ void fn1(const char *out, ... /* args */) // #S1_fn1
+ {
+ va_list args;
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn1'}}
+ // expected-note@#S1_fn1 {{'fn1' declared here}}
+ }
+
+ __attribute__((format(printf, 2, 0)))
+ void print(const char *out, va_list args);
+
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))"
+ void fn2(const char *out, ... /* args */) // #S1_fn2
+ {
+ va_list args;
+ print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn2'}}
+ // expected-note@#S1_fn2 {{'fn2' declared here}}
+ }
+
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))"
+ void fn3(const char *out, va_list args) // #S1_fn3
+ {
+ print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}}
+ // expected-note@#S1_fn3 {{'fn3' declared here}}
+ }
+
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))"
+ void fn4(this S1& self, const char *out, ... /* args */) // #S1_fn4
+ {
+ va_list args;
+ self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn4'}}
+ // expected-note@#S1_fn4 {{'fn4' declared here}}
+ }
+
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))"
+ void fn5(this S1& self, const char *out, va_list args) // #S1_fn5
+ {
+ self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn5'}}
+ // expected-note@#S1_fn5 {{'fn5' declared here}}
+ }
+};
+
>From c62ec642a3aafd82ae4ec1da0ea46d1a4ca43c60 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Fri, 7 Nov 2025 03:13:04 -0800
Subject: [PATCH 02/12] Switch to portable attribute syntax
---
.../clang/Basic/DiagnosticSemaKinds.td | 9 ++-
clang/lib/Sema/SemaChecking.cpp | 28 ++++++---
clang/test/Sema/attr-format-missing-gnu.c | 20 ++++++
.../Sema/attr-format-missing-unsupported.c | 17 +++++
clang/test/Sema/attr-format-missing.c | 62 +++++++++----------
clang/test/Sema/attr-format-missing.cpp | 24 +++----
6 files changed, 102 insertions(+), 58 deletions(-)
create mode 100644 clang/test/Sema/attr-format-missing-gnu.c
create mode 100644 clang/test/Sema/attr-format-missing-unsupported.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 78c5caf79eab7..37d7e9591d32b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3478,11 +3478,10 @@ def err_format_attribute_result_not : Error<"function does not return %0">;
def err_format_attribute_implicit_this_format_string : Error<
"format attribute cannot specify the implicit this argument as the format "
"string">;
-def warn_missing_format_attribute
- : Warning<"diagnostic behavior may be improved by adding the '%0' format "
- "attribute to the declaration of %1">,
- InGroup<DiagGroup<"missing-format-attribute">>,
- DefaultIgnore;
+def warn_missing_format_attribute : Warning<
+ "diagnostic behavior may be improved by adding the '%0' format "
+ "attribute to the declaration of %1">,
+ InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
def err_callback_attribute_no_callee : Error<
"'callback' attribute specifies no callback callee">;
def err_callback_attribute_invalid_callee : Error<
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 502e9fd099144..bb11b6077e03f 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -6636,11 +6636,10 @@ static StringLiteralCheckType checkFormatStringExpr(
if (InitList->isStringLiteralInit())
Init = InitList->getInit(0)->IgnoreParenImpCasts();
}
- return checkFormatStringExpr(S, ReferenceFormatString, Init, Args,
- APK, format_idx, firstDataArg, Type,
- CallType,
- /*InFunctionCall*/ false, CheckedVarArgs,
- UncoveredArg, Offset, CallerParamIdx);
+ return checkFormatStringExpr(
+ S, ReferenceFormatString, Init, Args, APK, format_idx,
+ firstDataArg, Type, CallType, /*InFunctionCall=*/false,
+ CheckedVarArgs, UncoveredArg, Offset, CallerParamIdx);
}
}
@@ -7065,13 +7064,21 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
// Emit the diagnostic and fixit.
unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
+ StringRef AttrPrefix, AttrSuffix;
+ if (S->getLangOpts().C23 || S->getLangOpts().CPlusPlus11) {
+ AttrPrefix = "[[gnu::format(";
+ AttrSuffix = ")]] ";
+ } else {
+ AttrPrefix = "__attribute__((format(";
+ AttrSuffix = "))) ";
+ }
S->Diag(Loc, diag::warn_missing_format_attribute)
<< FormatTypeName << Caller
- << FixItHint::CreateInsertion(Caller->getFirstDecl()->getLocation(),
- (llvm::Twine("__attribute__((format(") +
- FormatTypeName + ", " +
+ << FixItHint::CreateInsertion(Caller->getFirstDecl()->getBeginLoc(),
+ (AttrPrefix + FormatTypeName + ", " +
llvm::Twine(FormatStringIndex) + ", " +
- llvm::Twine(FirstArgumentIndex) + ")))")
+ llvm::Twine(FirstArgumentIndex) +
+ AttrSuffix)
.str());
S->Diag(Caller->getFirstDecl()->getLocation(), diag::note_callee_decl)
<< Caller;
@@ -7133,7 +7140,8 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
SourceMgr.isInSystemMacro(FormatLoc))
return false;
- if (CallerParamIdx)
+ const LangOptions &LO = getLangOpts();
+ if (CallerParamIdx && (LO.GNUMode || LO.C23 || LO.CPlusPlus11))
CheckMissingFormatAttributes(this, Type, format_idx, firstDataArg, Args,
APK, *CallerParamIdx, Loc);
diff --git a/clang/test/Sema/attr-format-missing-gnu.c b/clang/test/Sema/attr-format-missing-gnu.c
new file mode 100644
index 0000000000000..4ba7d64107d3a
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing-gnu.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=gnu11 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=gnu++98 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -std=gnu11 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -fsyntax-only -x c++ -std=gnu++98 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+
+typedef unsigned long size_t;
+typedef long ssize_t;
+typedef __builtin_va_list va_list;
+
+__attribute__((format(printf, 1, 0)))
+int vprintf(const char *, va_list);
+
+// Test that attribute fixit is specified using the GNU extension format when -std=gnuXY or -std=gnu++XY.
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 1, 0))) "
+void f1(char *out, va_list args) // #f1
+{
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
+ // expected-note@#f1 {{'f1' declared here}}
+}
diff --git a/clang/test/Sema/attr-format-missing-unsupported.c b/clang/test/Sema/attr-format-missing-unsupported.c
new file mode 100644
index 0000000000000..2694ce1b9bb49
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing-unsupported.c
@@ -0,0 +1,17 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c11 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=c++98 -Wmissing-format-attribute %s
+
+typedef unsigned long size_t;
+typedef long ssize_t;
+typedef __builtin_va_list va_list;
+
+__attribute__((format(printf, 1, 0)))
+int vprintf(const char *, va_list);
+
+// Test that diagnostic is disabled when the standard doesn't support a portable attribute syntax.
+// expected-no-diagnostics
+
+void f1(char *out, va_list args) // #f1
+{
+ vprintf(out, args);
+}
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
index 7e5131153f661..071054dfa1e6d 100644
--- a/clang/test/Sema/attr-format-missing.c
+++ b/clang/test/Sema/attr-format-missing.c
@@ -1,47 +1,47 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
-// RUN: %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c23 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -std=c23 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
typedef unsigned long size_t;
typedef long ssize_t;
typedef __builtin_va_list va_list;
-__attribute__((format(printf, 1, 2)))
+[[gnu::format(printf, 1, 2)]]
int printf(const char *, ...);
-__attribute__((format(scanf, 1, 2)))
+[[gnu::format(scanf, 1, 2)]]
int scanf(const char *, ...);
-__attribute__((format(printf, 1, 0)))
+[[gnu::format(printf, 1, 0)]]
int vprintf(const char *, va_list);
-__attribute__((format(scanf, 1, 0)))
+[[gnu::format(scanf, 1, 0)]]
int vscanf(const char *, va_list);
-__attribute__((format(printf, 2, 0)))
+[[gnu::format(printf, 2, 0)]]
int vsprintf(char *, const char *, va_list);
struct tm { unsigned i; };
-__attribute__((format(strftime, 3, 0)))
+[[gnu::format(strftime, 3, 0)]]
size_t strftime(char *, size_t, const char *, const struct tm *);
-__attribute__((format(strfmon, 3, 4)))
+[[gnu::format(strfmon, 3, 4)]]
ssize_t strfmon(char *, size_t, const char *, ...);
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
void f1(char *out, va_list args) // #f1
{
vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
// expected-note@#f1 {{'f1' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 0)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] "
void f2(const char *out, va_list args) // #f2
{
vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}}
// expected-note@#f2 {{'f2' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f3(const char *out, ... /* args */) // #f3
{
va_list args;
@@ -49,7 +49,7 @@ void f3(const char *out, ... /* args */) // #f3
// expected-note@#f3 {{'f3' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
void f4(const char *out, ... /* args */) // #f4
{
va_list args;
@@ -57,8 +57,8 @@ void f4(const char *out, ... /* args */) // #f4
// expected-note@#f4 {{'f4' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 2, 3)))"
-__attribute__((format(printf, 1, 3)))
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
+[[gnu::format(printf, 1, 3)]]
void f5(char *out, const char *format, ... /* args */) // #f5
{
va_list args;
@@ -66,7 +66,7 @@ void f5(char *out, const char *format, ... /* args */) // #f5
// expected-note@#f5 {{'f5' declared here}}
}
-__attribute__((format(scanf, 1, 3)))
+[[gnu::format(scanf, 1, 3)]]
void f6(char *out, const char *format, ... /* args */) // #f6
{
va_list args;
@@ -100,8 +100,8 @@ void f9(va_list args)
vscanf("test", args);
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))"
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f10(const char *out, ... /* args */) // #f10
{
va_list args;
@@ -111,7 +111,7 @@ void f10(const char *out, ... /* args */) // #f10
// expected-note@#f10 {{'f10' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f11(const char out[], ... /* args */) // #f11
{
va_list args;
@@ -121,7 +121,7 @@ void f11(const char out[], ... /* args */) // #f11
// expected-note@#f11 {{'f11' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
void f12(char* out) // #f12
{
va_list args;
@@ -131,8 +131,8 @@ void f12(char* out) // #f12
// expected-note@#f12 {{'f12' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))"
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f13(char *out, ... /* args */) // #f13
{
va_list args;
@@ -142,8 +142,8 @@ void f13(char *out, ... /* args */) // #f13
// expected-note@#f13 {{'f13' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 0)))"
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] "
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
void f14(char *out, va_list args) // #f14
{
vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}}
@@ -152,7 +152,7 @@ void f14(char *out, va_list args) // #f14
// expected-note@#f14 {{'f14' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
void f15(char *out, ... /* args */) // #f15
{
va_list args;
@@ -162,8 +162,8 @@ void f15(char *out, ... /* args */) // #f15
// expected-note@#f15 {{'f15' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 1, 3)))"
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 2, 3)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 1, 3)]] "
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
void f16(char *ch, const char *out, ... /* args */) // #f16
{
va_list args;
@@ -173,7 +173,7 @@ void f16(char *ch, const char *out, ... /* args */) // #f16
// expected-note@#f16 {{'f16' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f17(const char *a, ...) // #f17
{
va_list ap;
@@ -182,7 +182,7 @@ void f17(const char *a, ...) // #f17
// expected-note@#f17 {{'f17' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18
{
printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}}
@@ -200,7 +200,7 @@ void f20(char *out, ... /* args */)
printf(out, 1); // No warning, arguments are not passed to printf.
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strftime, 3, 0)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strftime, 3, 0)]] "
void f21(char *out, const size_t len, const char *format) // #f21
{
struct tm tm_arg;
@@ -209,7 +209,7 @@ void f21(char *out, const size_t len, const char *format) // #f21
// expected-note@#f21 {{'f21' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strfmon, 3, 4)))"
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] "
void f22(char *out, const size_t len, const char *format, int x, int y) // #f22
{
strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'strfmon' format attribute to the declaration of 'f22'}}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
index 90d667531ce0a..23f643bb80058 100644
--- a/clang/test/Sema/attr-format-missing.cpp
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -4,27 +4,27 @@
typedef __SIZE_TYPE__ size_t;
typedef __builtin_va_list va_list;
-__attribute__((__format__(__printf__, 1, 2)))
+[[gnu::format(printf, 1, 2)]]
int printf(const char *, ...);
-__attribute__((__format__(__scanf__, 1, 2)))
+[[gnu::format(scanf, 1, 2)]]
int scanf(const char *, ...);
-__attribute__((__format__(__printf__, 1, 0)))
+[[gnu::format(printf, 1, 0)]]
int vprintf(const char *, va_list);
-__attribute__((__format__(__scanf__, 1, 0)))
+[[gnu::format(scanf, 1, 0)]]
int vscanf(const char *, va_list);
-__attribute__((__format__(__printf__, 2, 0)))
+[[gnu::format(printf, 2, 0)]]
int vsprintf(char *, const char *, va_list);
-__attribute__((__format__(__printf__, 3, 0)))
+[[gnu::format(printf, 3, 0)]]
int vsnprintf(char *, size_t, const char *, va_list);
struct S1
{
- // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(scanf, 2, 3)))"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(scanf, 2, 3)]] "
void fn1(const char *out, ... /* args */) // #S1_fn1
{
va_list args;
@@ -32,10 +32,10 @@ struct S1
// expected-note@#S1_fn1 {{'fn1' declared here}}
}
- __attribute__((format(printf, 2, 0)))
+ [[gnu::format(printf, 2, 0)]]
void print(const char *out, va_list args);
- // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
void fn2(const char *out, ... /* args */) // #S1_fn2
{
va_list args;
@@ -43,14 +43,14 @@ struct S1
// expected-note@#S1_fn2 {{'fn2' declared here}}
}
- // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] "
void fn3(const char *out, va_list args) // #S1_fn3
{
print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}}
// expected-note@#S1_fn3 {{'fn3' declared here}}
}
- // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
void fn4(this S1& self, const char *out, ... /* args */) // #S1_fn4
{
va_list args;
@@ -58,7 +58,7 @@ struct S1
// expected-note@#S1_fn4 {{'fn4' declared here}}
}
- // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))"
+ // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] "
void fn5(this S1& self, const char *out, va_list args) // #S1_fn5
{
self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn5'}}
>From a0a4ca6383a88215b8edc9d2814ee0694a28f406 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Mon, 17 Nov 2025 00:52:50 -0800
Subject: [PATCH 03/12] Address review comments
- Check now works or Objective-C
- Add attribute arguments to diagnostic message
- Rename CallerParamIdx to CallerFormatParamIdx
- Fix typo
---
.../clang/Basic/DiagnosticSemaKinds.td | 4 +-
clang/lib/Sema/SemaChecking.cpp | 118 +++++++++---------
clang/test/Sema/attr-format-missing-gnu.c | 2 +-
clang/test/Sema/attr-format-missing.c | 57 ++++-----
clang/test/Sema/attr-format-missing.cpp | 10 +-
clang/test/Sema/attr-format-missing.m | 31 +++++
6 files changed, 130 insertions(+), 92 deletions(-)
create mode 100644 clang/test/Sema/attr-format-missing.m
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 37d7e9591d32b..c42ee4ddb4e65 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3479,8 +3479,8 @@ def err_format_attribute_implicit_this_format_string : Error<
"format attribute cannot specify the implicit this argument as the format "
"string">;
def warn_missing_format_attribute : Warning<
- "diagnostic behavior may be improved by adding the '%0' format "
- "attribute to the declaration of %1">,
+ "diagnostic behavior may be improved by adding the '%0' attribute to the "
+ "declaration of %1">,
InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
def err_callback_attribute_no_callee : Error<
"'callback' attribute specifies no callback callee">;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index bb11b6077e03f..b78aec68bec62 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -17,6 +17,7 @@
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/Attr.h"
#include "clang/AST/AttrIterator.h"
+#include "clang/AST/Attrs.inc"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
@@ -6511,14 +6512,16 @@ static const Expr *maybeConstEvalStringLiteral(ASTContext &Context,
// If this function returns false on the arguments to a function expecting a
// format string, we will usually need to emit a warning.
// True string literals are then checked by CheckFormatString.
-static StringLiteralCheckType checkFormatStringExpr(
- Sema &S, const StringLiteral *ReferenceFormatString, const Expr *E,
- ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
- unsigned format_idx, unsigned firstDataArg, FormatStringType Type,
- VariadicCallType CallType, bool InFunctionCall,
- llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg,
- llvm::APSInt Offset, std::optional<unsigned> *CallerParamIdx = nullptr,
- bool IgnoreStringsWithoutSpecifiers = false) {
+static StringLiteralCheckType
+checkFormatStringExpr(Sema &S, const StringLiteral *ReferenceFormatString,
+ const Expr *E, ArrayRef<const Expr *> Args,
+ Sema::FormatArgumentPassingKind APK, unsigned format_idx,
+ unsigned firstDataArg, FormatStringType Type,
+ VariadicCallType CallType, bool InFunctionCall,
+ llvm::SmallBitVector &CheckedVarArgs,
+ UncoveredArgHandler &UncoveredArg, llvm::APSInt Offset,
+ std::optional<unsigned> *CallerFormatParamIdx = nullptr,
+ bool IgnoreStringsWithoutSpecifiers = false) {
if (S.isConstantEvaluatedContext())
return SLCT_NotALiteral;
tryAgain:
@@ -6540,10 +6543,11 @@ static StringLiteralCheckType checkFormatStringExpr(
case Stmt::InitListExprClass:
// Handle expressions like {"foobar"}.
if (const clang::Expr *SLE = maybeConstEvalStringLiteral(S.Context, E)) {
- return checkFormatStringExpr(
- S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg,
- Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs,
- UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ return checkFormatStringExpr(S, ReferenceFormatString, SLE, Args, APK,
+ format_idx, firstDataArg, Type, CallType,
+ /*InFunctionCall*/ false, CheckedVarArgs,
+ UncoveredArg, Offset, CallerFormatParamIdx,
+ IgnoreStringsWithoutSpecifiers);
}
return SLCT_NotALiteral;
case Stmt::BinaryConditionalOperatorClass:
@@ -6575,10 +6579,11 @@ static StringLiteralCheckType checkFormatStringExpr(
if (!CheckLeft)
Left = SLCT_UncheckedLiteral;
else {
- Left = checkFormatStringExpr(
- S, ReferenceFormatString, C->getTrueExpr(), Args, APK, format_idx,
- firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
- UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ Left = checkFormatStringExpr(S, ReferenceFormatString, C->getTrueExpr(),
+ Args, APK, format_idx, firstDataArg, Type,
+ CallType, InFunctionCall, CheckedVarArgs,
+ UncoveredArg, Offset, CallerFormatParamIdx,
+ IgnoreStringsWithoutSpecifiers);
if (Left == SLCT_NotALiteral || !CheckRight) {
return Left;
}
@@ -6587,7 +6592,8 @@ static StringLiteralCheckType checkFormatStringExpr(
StringLiteralCheckType Right = checkFormatStringExpr(
S, ReferenceFormatString, C->getFalseExpr(), Args, APK, format_idx,
firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
- UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ UncoveredArg, Offset, CallerFormatParamIdx,
+ IgnoreStringsWithoutSpecifiers);
return (CheckLeft && Left < Right) ? Left : Right;
}
@@ -6639,7 +6645,7 @@ static StringLiteralCheckType checkFormatStringExpr(
return checkFormatStringExpr(
S, ReferenceFormatString, Init, Args, APK, format_idx,
firstDataArg, Type, CallType, /*InFunctionCall=*/false,
- CheckedVarArgs, UncoveredArg, Offset, CallerParamIdx);
+ CheckedVarArgs, UncoveredArg, Offset, CallerFormatParamIdx);
}
}
@@ -6691,8 +6697,8 @@ static StringLiteralCheckType checkFormatStringExpr(
// format arguments, in all cases.
//
if (const auto *PV = dyn_cast<ParmVarDecl>(VD)) {
- if (CallerParamIdx)
- *CallerParamIdx = PV->getFunctionScopeIndex();
+ if (CallerFormatParamIdx)
+ *CallerFormatParamIdx = PV->getFunctionScopeIndex();
if (const auto *D = dyn_cast<Decl>(PV->getDeclContext())) {
for (const auto *PVFormatMatches :
D->specific_attrs<FormatMatchesAttr>()) {
@@ -6718,7 +6724,7 @@ static StringLiteralCheckType checkFormatStringExpr(
S, ReferenceFormatString, PVFormatMatches->getFormatString(),
Args, APK, format_idx, firstDataArg, Type, CallType,
/*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg,
- Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers);
}
}
@@ -6773,7 +6779,7 @@ static StringLiteralCheckType checkFormatStringExpr(
StringLiteralCheckType Result = checkFormatStringExpr(
S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg,
Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg,
- Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers);
if (IsFirst) {
CommonResult = Result;
IsFirst = false;
@@ -6787,19 +6793,20 @@ static StringLiteralCheckType checkFormatStringExpr(
if (BuiltinID == Builtin::BI__builtin___CFStringMakeConstantString ||
BuiltinID == Builtin::BI__builtin___NSStringMakeConstantString) {
const Expr *Arg = CE->getArg(0);
- return checkFormatStringExpr(S, ReferenceFormatString, Arg, Args, APK,
- format_idx, firstDataArg, Type, CallType,
- InFunctionCall, CheckedVarArgs,
- UncoveredArg, Offset, CallerParamIdx,
- IgnoreStringsWithoutSpecifiers);
+ return checkFormatStringExpr(
+ S, ReferenceFormatString, Arg, Args, APK, format_idx,
+ firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
+ UncoveredArg, Offset, CallerFormatParamIdx,
+ IgnoreStringsWithoutSpecifiers);
}
}
}
if (const Expr *SLE = maybeConstEvalStringLiteral(S.Context, E))
- return checkFormatStringExpr(
- S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg,
- Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs,
- UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ return checkFormatStringExpr(S, ReferenceFormatString, SLE, Args, APK,
+ format_idx, firstDataArg, Type, CallType,
+ /*InFunctionCall*/ false, CheckedVarArgs,
+ UncoveredArg, Offset, CallerFormatParamIdx,
+ IgnoreStringsWithoutSpecifiers);
return SLCT_NotALiteral;
}
case Stmt::ObjCMessageExprClass: {
@@ -6825,7 +6832,7 @@ static StringLiteralCheckType checkFormatStringExpr(
return checkFormatStringExpr(
S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg,
Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg,
- Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers);
+ Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers);
}
}
@@ -7011,10 +7018,12 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
Sema::FormatArgumentPassingKind APK,
unsigned CallerParamIdx,
SourceLocation Loc) {
- const FunctionDecl *Caller = S->getCurFunctionDecl();
+ NamedDecl *Caller = S->getCurFunctionOrMethodDecl();
if (!Caller)
return;
+ unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller);
+
// Find the offset to convert between attribute and parameter indexes.
unsigned CallerArgumentIndexOffset =
hasImplicitObjectParameter(Caller) ? 2 : 1;
@@ -7030,12 +7039,11 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
// emit a diag with the caller idx, otherwise we can't determine the callee
// arguments.
unsigned NumCalleeArgs = Args.size() - FirstArg;
- if (NumCalleeArgs == 0 || Caller->getNumParams() < NumCalleeArgs) {
- // There aren't enough arugments in the caller to pass to callee.
+ if (NumCalleeArgs == 0 || NumCallerParams < NumCalleeArgs) {
+ // There aren't enough arguments in the caller to pass to callee.
return;
}
- for (unsigned CalleeIdx = Args.size() - 1,
- CallerIdx = Caller->getNumParams() - 1;
+ for (unsigned CalleeIdx = Args.size() - 1, CallerIdx = NumCallerParams - 1;
CalleeIdx >= FirstArg; --CalleeIdx, --CallerIdx) {
const auto *Arg =
dyn_cast<DeclRefExpr>(Args[CalleeIdx]->IgnoreParenCasts());
@@ -7046,15 +7054,14 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
return;
}
FirstArgumentIndex =
- Caller->getNumParams() + CallerArgumentIndexOffset - NumCalleeArgs;
+ NumCallerParams + CallerArgumentIndexOffset - NumCalleeArgs;
break;
}
case Sema::FormatArgumentPassingKind::FAPK_VAList:
// Caller arguments are either variadic or a va_list.
- FirstArgumentIndex =
- Caller->isVariadic()
- ? (Caller->getNumParams() + CallerArgumentIndexOffset)
- : 0;
+ FirstArgumentIndex = isFunctionOrMethodVariadic(Caller)
+ ? (NumCallerParams + CallerArgumentIndexOffset)
+ : 0;
break;
case Sema::FormatArgumentPassingKind::FAPK_Elsewhere:
// Args are not passed to the callee.
@@ -7063,25 +7070,24 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
// Emit the diagnostic and fixit.
unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
- StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
StringRef AttrPrefix, AttrSuffix;
if (S->getLangOpts().C23 || S->getLangOpts().CPlusPlus11) {
- AttrPrefix = "[[gnu::format(";
- AttrSuffix = ")]] ";
+ AttrPrefix = "[[gnu::";
+ AttrSuffix = "]] ";
} else {
- AttrPrefix = "__attribute__((format(";
- AttrSuffix = "))) ";
+ AttrPrefix = "__attribute__((";
+ AttrSuffix = ")) ";
}
+ StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
+ std::string Attr =
+ ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) +
+ ", " + llvm::Twine(FirstArgumentIndex) + ")")
+ .str();
S->Diag(Loc, diag::warn_missing_format_attribute)
- << FormatTypeName << Caller
- << FixItHint::CreateInsertion(Caller->getFirstDecl()->getBeginLoc(),
- (AttrPrefix + FormatTypeName + ", " +
- llvm::Twine(FormatStringIndex) + ", " +
- llvm::Twine(FirstArgumentIndex) +
- AttrSuffix)
- .str());
- S->Diag(Caller->getFirstDecl()->getLocation(), diag::note_callee_decl)
- << Caller;
+ << Attr << Caller
+ << FixItHint::CreateInsertion(Caller->getBeginLoc(),
+ (AttrPrefix + Attr + AttrSuffix).str());
+ S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller;
}
bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
diff --git a/clang/test/Sema/attr-format-missing-gnu.c b/clang/test/Sema/attr-format-missing-gnu.c
index 4ba7d64107d3a..83f20b8b85a7b 100644
--- a/clang/test/Sema/attr-format-missing-gnu.c
+++ b/clang/test/Sema/attr-format-missing-gnu.c
@@ -15,6 +15,6 @@ int vprintf(const char *, va_list);
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 1, 0))) "
void f1(char *out, va_list args) // #f1
{
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f1'}}
// expected-note@#f1 {{'f1' declared here}}
}
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
index 071054dfa1e6d..5d4eeac18773b 100644
--- a/clang/test/Sema/attr-format-missing.c
+++ b/clang/test/Sema/attr-format-missing.c
@@ -28,33 +28,33 @@ size_t strftime(char *, size_t, const char *, const struct tm *);
ssize_t strfmon(char *, size_t, const char *, ...);
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
-void f1(char *out, va_list args) // #f1
+void f1(const char *fmt, va_list args) // #f1
{
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
+ vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f1'}}
// expected-note@#f1 {{'f1' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] "
-void f2(const char *out, va_list args) // #f2
+void f2(const char *fmt, va_list args) // #f2
{
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}}
- // expected-note@#f2 {{'f2' declared here}}
+ vscanf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 0)' attribute to the declaration of 'f2'}}
+ // expected-note@#f2 {{'f2' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
-void f3(const char *out, ... /* args */) // #f3
+void f3(const char *fmt, ... /* args */) // #f3
{
va_list args;
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}}
+ vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f3'}}
// expected-note@#f3 {{'f3' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
-void f4(const char *out, ... /* args */) // #f4
+void f4(const char *fmt, ... /* args */) // #f4
{
va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f4'}}
- // expected-note@#f4 {{'f4' declared here}}
+ vscanf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f4'}}
+ // expected-note@#f4 {{'f4' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
@@ -62,15 +62,16 @@ void f4(const char *out, ... /* args */) // #f4
void f5(char *out, const char *format, ... /* args */) // #f5
{
va_list args;
- vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f5'}}
+ vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f5'}}
// expected-note@#f5 {{'f5' declared here}}
}
+// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
[[gnu::format(scanf, 1, 3)]]
void f6(char *out, const char *format, ... /* args */) // #f6
{
va_list args;
- vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f6'}}
+ vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f6'}}
// expected-note@#f6 {{'f6' declared here}}
}
@@ -105,9 +106,9 @@ void f9(va_list args)
void f10(const char *out, ... /* args */) // #f10
{
va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f10'}}
// expected-note@#f10 {{'f10' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f10'}}
// expected-note@#f10 {{'f10' declared here}}
}
@@ -117,7 +118,7 @@ void f11(const char out[], ... /* args */) // #f11
va_list args;
char ch[10] = "format";
vprintf(ch, args);
- vsprintf(ch, out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f11'}}
+ vsprintf(ch, out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f11'}}
// expected-note@#f11 {{'f11' declared here}}
}
@@ -127,7 +128,7 @@ void f12(char* out) // #f12
va_list args;
const char *ch = "format";
vsprintf(out, ch, args);
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f12'}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f12'}}
// expected-note@#f12 {{'f12' declared here}}
}
@@ -136,9 +137,9 @@ void f12(char* out) // #f12
void f13(char *out, ... /* args */) // #f13
{
va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f13'}}
// expected-note@#f13 {{'f13' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f13'}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f13'}}
// expected-note@#f13 {{'f13' declared here}}
}
@@ -146,9 +147,9 @@ void f13(char *out, ... /* args */) // #f13
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
void f14(char *out, va_list args) // #f14
{
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 0)' attribute to the declaration of 'f14'}}
// expected-note@#f14 {{'f14' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f14'}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f14'}}
// expected-note@#f14 {{'f14' declared here}}
}
@@ -156,9 +157,9 @@ void f14(char *out, va_list args) // #f14
void f15(char *out, ... /* args */) // #f15
{
va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}}
// expected-note@#f15 {{'f15' declared here}}
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}}
// expected-note@#f15 {{'f15' declared here}}
}
@@ -167,9 +168,9 @@ void f15(char *out, ... /* args */) // #f15
void f16(char *ch, const char *out, ... /* args */) // #f16
{
va_list args;
- vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}}
+ vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'f16'}}
// expected-note@#f16 {{'f16' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f16'}}
// expected-note@#f16 {{'f16' declared here}}
}
@@ -178,14 +179,14 @@ void f17(const char *a, ...) // #f17
{
va_list ap;
const char *const b = a;
- vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}}
+ vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f17'}}
// expected-note@#f17 {{'f17' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18
{
- printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}}
+ printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f18'}}
// expected-note@#f18 {{'f18' declared here}}
}
@@ -205,13 +206,13 @@ void f21(char *out, const size_t len, const char *format) // #f21
{
struct tm tm_arg;
tm_arg.i = 0;
- strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'strftime' format attribute to the declaration of 'f21'}}
+ strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strftime, 3, 0)' attribute to the declaration of 'f21'}}
// expected-note@#f21 {{'f21' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] "
void f22(char *out, const size_t len, const char *format, int x, int y) // #f22
{
- strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'strfmon' format attribute to the declaration of 'f22'}}
+ strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f22'}}
// expected-note@#f22 {{'f22' declared here}}
}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
index 23f643bb80058..06b5ad1a68f3c 100644
--- a/clang/test/Sema/attr-format-missing.cpp
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -28,7 +28,7 @@ struct S1
void fn1(const char *out, ... /* args */) // #S1_fn1
{
va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn1'}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 2, 3)' attribute to the declaration of 'fn1'}}
// expected-note@#S1_fn1 {{'fn1' declared here}}
}
@@ -39,14 +39,14 @@ struct S1
void fn2(const char *out, ... /* args */) // #S1_fn2
{
va_list args;
- print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn2'}}
+ print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'fn2'}}
// expected-note@#S1_fn2 {{'fn2' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] "
void fn3(const char *out, va_list args) // #S1_fn3
{
- print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}}
+ print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'fn3'}}
// expected-note@#S1_fn3 {{'fn3' declared here}}
}
@@ -54,14 +54,14 @@ struct S1
void fn4(this S1& self, const char *out, ... /* args */) // #S1_fn4
{
va_list args;
- self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn4'}}
+ self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'fn4'}}
// expected-note@#S1_fn4 {{'fn4' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] "
void fn5(this S1& self, const char *out, va_list args) // #S1_fn5
{
- self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn5'}}
+ self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'fn5'}}
// expected-note@#S1_fn5 {{'fn5' declared here}}
}
};
diff --git a/clang/test/Sema/attr-format-missing.m b/clang/test/Sema/attr-format-missing.m
new file mode 100644
index 0000000000000..3896bcef4634a
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.m
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+
+#include <stdarg.h>
+
+ at interface Print
+-(void)printf:(const char *)fmt, ... __attribute__((format(printf, 1, 2)));
+-(void)vprintf:(const char *)fmt list:(va_list)ap __attribute__((format(printf, 1, 0)));
+ at end
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) "
+void f1(Print *p, const char *fmt, int x) // #f1
+{
+ [p printf:fmt, x]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f1'}}
+ // expected-note@#f1 {{'f1' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) "
+void f2(Print *p, const char *fmt, ...) // #f2
+{
+ va_list ap;
+ [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f2'}}
+ // expected-note@#f2 {{'f2' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 0))) "
+void f3(Print *p, const char *fmt, va_list ap) // #f3
+{
+ [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'f3'}}
+ // expected-note@#f3 {{'f3' declared here}}
+}
>From ba5e3d8cee04ba236495c8e440688533f93f928d Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Mon, 17 Nov 2025 00:55:32 -0800
Subject: [PATCH 04/12] Emit fixit only when supported
---
clang/lib/Sema/SemaChecking.cpp | 37 +++++++++++++++++++--------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index b78aec68bec62..5dbac022d7c4d 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -7070,23 +7070,28 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
// Emit the diagnostic and fixit.
unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
- StringRef AttrPrefix, AttrSuffix;
- if (S->getLangOpts().C23 || S->getLangOpts().CPlusPlus11) {
- AttrPrefix = "[[gnu::";
- AttrSuffix = "]] ";
- } else {
- AttrPrefix = "__attribute__((";
- AttrSuffix = ")) ";
- }
StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
- std::string Attr =
- ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) +
- ", " + llvm::Twine(FirstArgumentIndex) + ")")
- .str();
- S->Diag(Loc, diag::warn_missing_format_attribute)
- << Attr << Caller
- << FixItHint::CreateInsertion(Caller->getBeginLoc(),
- (AttrPrefix + Attr + AttrSuffix).str());
+ {
+ std::string Attr =
+ ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) +
+ ", " + llvm::Twine(FirstArgumentIndex) + ")")
+ .str();
+ auto DB = S->Diag(Loc, diag::warn_missing_format_attribute)
+ << Attr << Caller;
+ const LangOptions &LO = S->getLangOpts();
+ StringRef AttrPrefix, AttrSuffix;
+ if (LO.C23 || LO.CPlusPlus11) {
+ AttrPrefix = "[[gnu::";
+ AttrSuffix = "]] ";
+ } else if (LO.ObjC || LO.GNUMode) {
+ AttrPrefix = "__attribute__((";
+ AttrSuffix = ")) ";
+ }
+ if (!AttrPrefix.empty()) {
+ DB << FixItHint::CreateInsertion(Caller->getBeginLoc(),
+ (AttrPrefix + Attr + AttrSuffix).str());
+ }
+ }
S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller;
}
>From c1b001a8e4fb5ba5842e6daf438d94d43abfcaa8 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Tue, 18 Nov 2025 02:01:28 -0800
Subject: [PATCH 05/12] Fix ObjC fixit location
---
clang/lib/Sema/SemaChecking.cpp | 49 +++++++++++++---------
clang/test/Sema/attr-format-missing.c | 10 +++++
clang/test/Sema/attr-format-missing.m | 59 ++++++++++++++++++++++-----
3 files changed, 88 insertions(+), 30 deletions(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 5dbac022d7c4d..1c756e65fa9b7 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -7021,6 +7021,7 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
NamedDecl *Caller = S->getCurFunctionOrMethodDecl();
if (!Caller)
return;
+ Caller = dyn_cast<NamedDecl>(Caller->getCanonicalDecl());
unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller);
@@ -7069,29 +7070,39 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
}
// Emit the diagnostic and fixit.
- unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
- StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
- {
- std::string Attr =
- ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) +
- ", " + llvm::Twine(FirstArgumentIndex) + ")")
- .str();
+ do {
+ unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
+ StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
+ std::string Attr, Fixit;
+ llvm::raw_string_ostream(Attr)
+ << "format(" << FormatTypeName << ", " << FormatStringIndex << ", "
+ << FirstArgumentIndex << ")";
auto DB = S->Diag(Loc, diag::warn_missing_format_attribute)
<< Attr << Caller;
- const LangOptions &LO = S->getLangOpts();
- StringRef AttrPrefix, AttrSuffix;
- if (LO.C23 || LO.CPlusPlus11) {
- AttrPrefix = "[[gnu::";
- AttrSuffix = "]] ";
- } else if (LO.ObjC || LO.GNUMode) {
- AttrPrefix = "__attribute__((";
- AttrSuffix = ")) ";
+
+ SourceLocation SL;
+ llvm::raw_string_ostream IS(Fixit);
+ // The attribute goes at the start of the declaration in C/C++ functions
+ // and methods, but after the declaration for Objective-C methods.
+ if (isa<ObjCMethodDecl>(Caller)) {
+ IS << ' ';
+ SL = Caller->getEndLoc();
}
- if (!AttrPrefix.empty()) {
- DB << FixItHint::CreateInsertion(Caller->getBeginLoc(),
- (AttrPrefix + Attr + AttrSuffix).str());
+ const LangOptions &LO = S->getLangOpts();
+ if (LO.C23 || LO.CPlusPlus11)
+ IS << "[[gnu::" << Attr << "]]";
+ else if (LO.ObjC || LO.GNUMode)
+ IS << "__attribute__((" << Attr << "))";
+ else
+ break;
+ if (!isa<ObjCMethodDecl>(Caller)) {
+ IS << ' ';
+ SL = Caller->getBeginLoc();
}
- }
+ IS.flush();
+
+ DB << FixItHint::CreateInsertion(SL, Fixit);
+ } while (false);
S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller;
}
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
index 5d4eeac18773b..10a8b2e193447 100644
--- a/clang/test/Sema/attr-format-missing.c
+++ b/clang/test/Sema/attr-format-missing.c
@@ -216,3 +216,13 @@ void f22(char *out, const size_t len, const char *format, int x, int y) // #f22
strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f22'}}
// expected-note@#f22 {{'f22' declared here}}
}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
+void f23(const char *fmt, ... /* args */); // #f23
+
+void f23(const char *fmt, ... /* args */)
+{
+ va_list args;
+ vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f23'}}
+ // expected-note@#f23 {{'f23' declared here}}
+}
diff --git a/clang/test/Sema/attr-format-missing.m b/clang/test/Sema/attr-format-missing.m
index 3896bcef4634a..159b4bad47e98 100644
--- a/clang/test/Sema/attr-format-missing.m
+++ b/clang/test/Sema/attr-format-missing.m
@@ -3,29 +3,66 @@
#include <stdarg.h>
- at interface Print
+ at interface PrintCallee
-(void)printf:(const char *)fmt, ... __attribute__((format(printf, 1, 2)));
-(void)vprintf:(const char *)fmt list:(va_list)ap __attribute__((format(printf, 1, 0)));
@end
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) "
-void f1(Print *p, const char *fmt, int x) // #f1
+void f1(PrintCallee *p, const char *fmt, int x) // #f1
{
- [p printf:fmt, x]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f1'}}
- // expected-note@#f1 {{'f1' declared here}}
+ [p printf:fmt, x]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f1'}}
+ // expected-note@#f1 {{'f1' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) "
-void f2(Print *p, const char *fmt, ...) // #f2
+void f2(PrintCallee *p, const char *fmt, ...) // #f2
{
- va_list ap;
- [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f2'}}
- // expected-note@#f2 {{'f2' declared here}}
+ va_list ap;
+ [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f2'}}
+ // expected-note@#f2 {{'f2' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 0))) "
-void f3(Print *p, const char *fmt, va_list ap) // #f3
+void f3(PrintCallee *p, const char *fmt, va_list ap) // #f3
{
- [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'f3'}}
- // expected-note@#f3 {{'f3' declared here}}
+ [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'f3'}}
+ // expected-note@#f3 {{'f3' declared here}}
}
+
+__attribute__((format(printf, 1, 2)))
+int printf(const char *, ...);
+__attribute__((format(printf, 1, 0)))
+int vprintf(const char *, va_list ap);
+
+__attribute__((objc_root_class))
+ at interface PrintCaller
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:33-[[@LINE+1]]:33}:" __attribute__((format(printf, 1, 2)))"
+-(void)f4:(const char *)fmt, ...; // #f4
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:45-[[@LINE+1]]:45}:" __attribute__((format(printf, 1, 0)))"
+-(void)f5:(const char *)fmt list:(va_list)ap; // #f5
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:37-[[@LINE+1]]:37}:" __attribute__((format(printf, 1, 2)))"
+-(void)f6:(const char *)fmt x:(int)x; // #f6
+ at end
+
+ at implementation PrintCaller
+-(void)f4:(const char *)fmt, ... {
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f4:'}}
+ // expected-note@#f4 {{'f4:' declared here}}
+ va_end(ap);
+}
+
+-(void)f5:(const char *)fmt list:(va_list)ap {
+ vprintf(fmt, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f5:list:'}}
+ // expected-note@#f5 {{'f5:list:' declared here}}
+}
+
+-(void)f6:(const char *)fmt x:(int)x {
+ printf(fmt, x); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f6:x:'}}
+ // expected-note@#f6 {{'f6:x:' declared here}}
+}
+ at end
>From 948c282a11daa9b32ed76234c5b32ffedc3eca43 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Tue, 18 Nov 2025 06:22:38 -0800
Subject: [PATCH 06/12] Suggest format_matches when applicable
---
clang/lib/Sema/SemaChecking.cpp | 54 +++++++++++++++++++--------
clang/test/Sema/attr-format-missing.c | 11 ++++++
2 files changed, 49 insertions(+), 16 deletions(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 1c756e65fa9b7..93b0d32d0621c 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -41,6 +41,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/AST/UnresolvedSet.h"
#include "clang/Basic/AddressSpaces.h"
+#include "clang/Basic/CharInfo.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/IdentifierTable.h"
@@ -7012,12 +7013,24 @@ bool Sema::CheckFormatString(const FormatMatchesAttr *Format,
return false;
}
-static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
- unsigned FormatIdx, unsigned FirstArg,
- ArrayRef<const Expr *> Args,
- Sema::FormatArgumentPassingKind APK,
- unsigned CallerParamIdx,
- SourceLocation Loc) {
+std::string escapeFormatString(StringRef Input) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ for (char C : Input) {
+ StringRef Escaped = escapeCStyle<EscapeChar::Double>(C);
+ if (Escaped.empty())
+ OS << C;
+ else
+ OS << Escaped;
+ }
+ return Result;
+}
+
+static void CheckMissingFormatAttributes(
+ Sema *S, ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
+ const StringLiteral *ReferenceFormatString, unsigned FormatIdx,
+ unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx,
+ SourceLocation Loc) {
NamedDecl *Caller = S->getCurFunctionOrMethodDecl();
if (!Caller)
return;
@@ -7039,13 +7052,13 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
// function. Try to match caller and callee arguments. If successful, then
// emit a diag with the caller idx, otherwise we can't determine the callee
// arguments.
- unsigned NumCalleeArgs = Args.size() - FirstArg;
+ unsigned NumCalleeArgs = Args.size() - FirstDataArg;
if (NumCalleeArgs == 0 || NumCallerParams < NumCalleeArgs) {
// There aren't enough arguments in the caller to pass to callee.
return;
}
for (unsigned CalleeIdx = Args.size() - 1, CallerIdx = NumCallerParams - 1;
- CalleeIdx >= FirstArg; --CalleeIdx, --CallerIdx) {
+ CalleeIdx >= FirstDataArg; --CalleeIdx, --CallerIdx) {
const auto *Arg =
dyn_cast<DeclRefExpr>(Args[CalleeIdx]->IgnoreParenCasts());
if (!Arg)
@@ -7065,8 +7078,10 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
: 0;
break;
case Sema::FormatArgumentPassingKind::FAPK_Elsewhere:
- // Args are not passed to the callee.
- return;
+ // The callee has a format_matches attribute. We will emit that instead.
+ if (!ReferenceFormatString)
+ return;
+ break;
}
// Emit the diagnostic and fixit.
@@ -7074,12 +7089,18 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType,
unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
std::string Attr, Fixit;
- llvm::raw_string_ostream(Attr)
- << "format(" << FormatTypeName << ", " << FormatStringIndex << ", "
- << FirstArgumentIndex << ")";
+ if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere)
+ llvm::raw_string_ostream(Attr)
+ << "format(" << FormatTypeName << ", " << FormatStringIndex << ", "
+ << FirstArgumentIndex << ")";
+ else
+ llvm::raw_string_ostream(Attr)
+ << "format_matches(" << FormatTypeName << ", " << FormatStringIndex
+ << ", \"" << escapeFormatString(ReferenceFormatString->getString())
+ << "\")";
auto DB = S->Diag(Loc, diag::warn_missing_format_attribute)
<< Attr << Caller;
-
+
SourceLocation SL;
llvm::raw_string_ostream IS(Fixit);
// The attribute goes at the start of the declaration in C/C++ functions
@@ -7164,8 +7185,9 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
const LangOptions &LO = getLangOpts();
if (CallerParamIdx && (LO.GNUMode || LO.C23 || LO.CPlusPlus11))
- CheckMissingFormatAttributes(this, Type, format_idx, firstDataArg, Args,
- APK, *CallerParamIdx, Loc);
+ CheckMissingFormatAttributes(this, Args, APK, ReferenceFormatString,
+ format_idx, firstDataArg, Type,
+ *CallerParamIdx, Loc);
// Strftime is particular as it always uses a single 'time' argument,
// so it is safe to pass a non-literal string.
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
index 10a8b2e193447..63d30940fd637 100644
--- a/clang/test/Sema/attr-format-missing.c
+++ b/clang/test/Sema/attr-format-missing.c
@@ -27,6 +27,9 @@ size_t strftime(char *, size_t, const char *, const struct tm *);
[[gnu::format(strfmon, 3, 4)]]
ssize_t strfmon(char *, size_t, const char *, ...);
+[[gnu::format_matches(printf, 1, "%d %f \"'")]]
+int custom_print(const char *, va_list);
+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
void f1(const char *fmt, va_list args) // #f1
{
@@ -226,3 +229,11 @@ void f23(const char *fmt, ... /* args */)
vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f23'}}
// expected-note@#f23 {{'f23' declared here}}
}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format_matches(printf, 1, \"%d %f \\\"'\")]] "
+void f24(const char *fmt, ...) // #f24
+{
+ va_list args;
+ custom_print(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format_matches(printf, 1, "%d %f \"'")' attribute to the declaration of 'f24'}}
+ // expected-note@#f24 {{'f24' declared here}}
+}
>From 4fd960b18b93039665a33a0e279f37a948af5cf7 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Wed, 19 Nov 2025 00:49:08 -0800
Subject: [PATCH 07/12] Add implicit attribute
---
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Sema/SemaChecking.cpp | 18 +++-
clang/test/Sema/attr-format-missing.c | 119 ++++++++++++--------------
clang/test/Sema/format-strings.c | 83 ++++++++++++++++--
4 files changed, 146 insertions(+), 76 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index eb8d7d1112016..4d45511894887 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3019,7 +3019,7 @@ class Sema final : public SemaBase {
llvm::SmallBitVector &CheckedVarArgs);
bool CheckFormatArguments(ArrayRef<const Expr *> Args,
FormatArgumentPassingKind FAPK,
- const StringLiteral *ReferenceFormatString,
+ StringLiteral *ReferenceFormatString,
unsigned format_idx, unsigned firstDataArg,
FormatStringType Type, VariadicCallType CallType,
SourceLocation Loc, SourceRange range,
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 93b0d32d0621c..c14011ed38ccd 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -7028,7 +7028,7 @@ std::string escapeFormatString(StringRef Input) {
static void CheckMissingFormatAttributes(
Sema *S, ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
- const StringLiteral *ReferenceFormatString, unsigned FormatIdx,
+ StringLiteral *ReferenceFormatString, unsigned FormatIdx,
unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx,
SourceLocation Loc) {
NamedDecl *Caller = S->getCurFunctionOrMethodDecl();
@@ -7085,9 +7085,9 @@ static void CheckMissingFormatAttributes(
}
// Emit the diagnostic and fixit.
+ unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
+ StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
do {
- unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
- StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
std::string Attr, Fixit;
if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere)
llvm::raw_string_ostream(Attr)
@@ -7125,11 +7125,21 @@ static void CheckMissingFormatAttributes(
DB << FixItHint::CreateInsertion(SL, Fixit);
} while (false);
S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller;
+
+ if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) {
+ Caller->addAttr(FormatAttr::CreateImplicit(
+ S->getASTContext(), &S->getASTContext().Idents.get(FormatTypeName),
+ FormatStringIndex, FirstArgumentIndex));
+ } else {
+ Caller->addAttr(FormatMatchesAttr::CreateImplicit(
+ S->getASTContext(), &S->getASTContext().Idents.get(FormatTypeName),
+ FormatStringIndex, ReferenceFormatString));
+ }
}
bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
Sema::FormatArgumentPassingKind APK,
- const StringLiteral *ReferenceFormatString,
+ StringLiteral *ReferenceFormatString,
unsigned format_idx, unsigned firstDataArg,
FormatStringType Type,
VariadicCallType CallType, SourceLocation Loc,
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
index 63d30940fd637..8cfc2b2d07dab 100644
--- a/clang/test/Sema/attr-format-missing.c
+++ b/clang/test/Sema/attr-format-missing.c
@@ -88,31 +88,42 @@ void f7(char* out, ... /* args */)
vprintf("test", args);
}
-// Ok, out is not passed to print functions.
-void f8(char *out, va_list args)
+// Ok, format string is not passed to format functions.
+void f8(va_list args)
{
- const char *ch = "format";
+ const char * const ch = "format";
vprintf(ch, args);
vprintf("test", args);
-}
-// Ok, out is not passed to scan functions.
-void f9(va_list args)
-{
- const char *ch = "format";
vscanf(ch, args);
vscanf("test", args);
+
+ char out[10];
+
+ struct tm tm_arg;
+ tm_arg.i = 0;
+ strftime(out, sizeof(out), ch, &tm_arg);
+ strftime(out, sizeof(out), "test", &tm_arg);
+
+ strfmon(out, sizeof(out), ch);
+ strfmon(out, sizeof(out), "test");
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format_matches(printf, 1, \"%d %f \\\"'\")]] "
+void f9(const char *fmt, ...) // #f9
+{
+ va_list args;
+ custom_print(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format_matches(printf, 1, "%d %f \"'")' attribute to the declaration of 'f9'}}
+ // expected-note@#f9 {{'f9' declared here}}
+}
+
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
void f10(const char *out, ... /* args */) // #f10
{
va_list args;
vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f10'}}
// expected-note@#f10 {{'f10' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f10'}}
- // expected-note@#f10 {{'f10' declared here}}
+ vprintf(out, args); // expected-warning {{passing 'scanf' format string where 'printf' format string is expected}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
@@ -135,105 +146,83 @@ void f12(char* out) // #f12
// expected-note@#f12 {{'f12' declared here}}
}
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
-void f13(char *out, ... /* args */) // #f13
-{
- va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f13'}}
- // expected-note@#f13 {{'f13' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f13'}}
- // expected-note@#f13 {{'f13' declared here}}
-}
-
-// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] "
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] "
-void f14(char *out, va_list args) // #f14
+void f13(char *out, va_list args) // #f13
{
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 0)' attribute to the declaration of 'f14'}}
- // expected-note@#f14 {{'f14' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f14'}}
- // expected-note@#f14 {{'f14' declared here}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f13'}}
+ // expected-note@#f13 {{'f13' declared here}}
+ vscanf(out, args); // expected-warning {{passing 'printf' format string where 'scanf' format string is expected}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] "
-void f15(char *out, ... /* args */) // #f15
+void f14(char *out, ... /* args */) // #f14
{
va_list args;
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}}
- // expected-note@#f15 {{'f15' declared here}}
- vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}}
- // expected-note@#f15 {{'f15' declared here}}
+ vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f14'}}
+ // expected-note@#f14 {{'f14' declared here}}
+ vscanf(out, args);
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 1, 3)]] "
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] "
-void f16(char *ch, const char *out, ... /* args */) // #f16
+void f15(char *ch, const char *out, ... /* args */) // #f15
{
va_list args;
- vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'f16'}}
- // expected-note@#f16 {{'f16' declared here}}
- vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f16'}}
- // expected-note@#f16 {{'f16' declared here}}
+ vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'f15'}}
+ // expected-note@#f15 {{'f15' declared here}}
+ vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f15'}}
+ // expected-note@#f15 {{'f15' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
-void f17(const char *a, ...) // #f17
+void f16(const char *a, ...) // #f16
{
va_list ap;
const char *const b = a;
- vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f17'}}
- // expected-note@#f17 {{'f17' declared here}}
+ vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f16'}}
+ // expected-note@#f16 {{'f16' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
-void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18
+void f17(char *fmt, unsigned x, unsigned y, unsigned z) // #f17
{
- printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f18'}}
- // expected-note@#f18 {{'f18' declared here}}
+ printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f17'}}
+ // expected-note@#f17 {{'f17' declared here}}
}
-void f19(char *fmt, unsigned x, unsigned y, unsigned z) // #f19
+void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18
{
// Arguments are not passed in the same order.
printf(fmt, x, z, y);
}
-void f20(char *out, ... /* args */)
+void f19(char *out, ... /* args */)
{
printf(out, 1); // No warning, arguments are not passed to printf.
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strftime, 3, 0)]] "
-void f21(char *out, const size_t len, const char *format) // #f21
+void f20(char *out, const size_t len, const char *format) // #f20
{
struct tm tm_arg;
tm_arg.i = 0;
- strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strftime, 3, 0)' attribute to the declaration of 'f21'}}
- // expected-note@#f21 {{'f21' declared here}}
+ strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strftime, 3, 0)' attribute to the declaration of 'f20'}}
+ // expected-note@#f20 {{'f20' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] "
-void f22(char *out, const size_t len, const char *format, int x, int y) // #f22
+void f21(char *out, const size_t len, const char *format, int x, int y) // #f21
{
- strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f22'}}
- // expected-note@#f22 {{'f22' declared here}}
+ strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f21'}}
+ // expected-note@#f21 {{'f21' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
-void f23(const char *fmt, ... /* args */); // #f23
-
-void f23(const char *fmt, ... /* args */)
-{
- va_list args;
- vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f23'}}
- // expected-note@#f23 {{'f23' declared here}}
-}
+void f22(const char *fmt, ... /* args */); // #f22
-// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format_matches(printf, 1, \"%d %f \\\"'\")]] "
-void f24(const char *fmt, ...) // #f24
+void f22(const char *fmt, ... /* args */)
{
va_list args;
- custom_print(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format_matches(printf, 1, "%d %f \"'")' attribute to the declaration of 'f24'}}
- // expected-note@#f24 {{'f24' declared here}}
+ vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f22'}}
+ // expected-note@#f22 {{'f22' declared here}}
}
diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c
index 103dd8ab5a85c..b46fc2c008b3a 100644
--- a/clang/test/Sema/format-strings.c
+++ b/clang/test/Sema/format-strings.c
@@ -24,37 +24,108 @@ int vscanf(const char *restrict format, va_list arg);
char * global_fmt;
-void check_string_literal( FILE* fp, const char* s, char *buf, ... ) {
-
- char * b;
+void check_string_literal1( const char* s, ... ) {
va_list ap;
- va_start(ap,buf);
-
+ va_start(ap,s);
printf(s); // expected-warning {{format string is not a string literal}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal2( const char* s, ... ) {
+ va_list ap;
+ va_start(ap,s);
vprintf(s,ap); // expected-warning {{format string is not a string literal}}
+}
+
+void check_string_literal3( FILE* fp, const char* s, ... ) {
+ va_list ap;
+ va_start(ap,s);
fprintf(fp,s); // expected-warning {{format string is not a string literal}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal4( FILE* fp, const char* s, ... ) {
+ va_list ap;
+ va_start(ap,s);
vfprintf(fp,s,ap); // expected-warning {{format string is not a string literal}}
+}
+
+void check_string_literal5( const char* s, ... ) {
+ char * b;
+ va_list ap;
+ va_start(ap,s);
asprintf(&b,s); // expected-warning {{format string is not a string lit}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal6( const char* s, ... ) {
+ char * b;
+ va_list ap;
+ va_start(ap,s);
vasprintf(&b,s,ap); // expected-warning {{format string is not a string literal}}
+}
+
+void check_string_literal7( const char* s, char *buf ) {
sprintf(buf,s); // expected-warning {{format string is not a string literal}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal8( const char* s, char *buf ) {
snprintf(buf,2,s); // expected-warning {{format string is not a string lit}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal9( const char* s, char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
__builtin___sprintf_chk(buf,0,-1,s); // expected-warning {{format string is not a string literal}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal10( const char* s, char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
__builtin___snprintf_chk(buf,2,0,-1,s); // expected-warning {{format string is not a string lit}}
// expected-note at -1{{treat the string as an argument to avoid this}}
+}
+
+void check_string_literal11( const char* s, char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
vsprintf(buf,s,ap); // expected-warning {{format string is not a string lit}}
+}
+
+void check_string_literal12( const char* s, char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
vsnprintf(buf,2,s,ap); // expected-warning {{format string is not a string lit}}
+}
+
+void check_string_literal13( char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
vsnprintf(buf,2,global_fmt,ap); // expected-warning {{format string is not a string literal}}
+}
+
+void check_string_literal14( FILE* fp, const char* s, char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
__builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // expected-warning {{format string is not a string lit}}
+}
+
+void check_string_literal15( FILE* fp, const char* s, char *buf, ... ) {
+ va_list ap;
+ va_start(ap,buf);
__builtin___vsnprintf_chk(buf,2,0,-1,global_fmt,ap); // expected-warning {{format string is not a string literal}}
+}
+void check_string_literal16(const char* s, ... ) {
+ va_list ap;
+ va_start(ap,s);
vscanf(s, ap); // expected-warning {{format string is not a string literal}}
+}
+void check_string_literal17() {
const char *const fmt = "%d"; // FIXME -- defined here
printf(fmt, 1, 2); // expected-warning{{data argument not used}}
@@ -74,7 +145,7 @@ def"
// warn only if the format string argument is a parameter that is not itself
// declared as a format string with compatible format.
__attribute__((__format__ (__printf__, 2, 4)))
-void check_string_literal2( FILE* fp, const char* s, char *buf, ... ) {
+void check_string_literal18( FILE* fp, const char* s, char *buf, ... ) {
char * b;
va_list ap;
va_start(ap,buf);
>From f1f719caf42546b574c54793929b213aafc328d5 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Wed, 19 Nov 2025 13:14:26 -0800
Subject: [PATCH 08/12] Make diagnostic a subgroup of format-nonliteral
---
clang/include/clang/Basic/DiagnosticGroups.td | 3 +-
.../clang/Basic/DiagnosticSemaKinds.td | 2 +-
clang/lib/Sema/SemaChecking.cpp | 54 +++++++++++++------
clang/test/Sema/attr-format-missing-gnu.c | 43 +++++++++++++--
.../Sema/attr-format-missing-unsupported.c | 17 ------
clang/test/Sema/attr-format-missing.c | 6 +--
clang/test/Sema/format-strings-scanf.c | 5 +-
clang/test/Sema/format-strings.c | 40 ++++++++------
8 files changed, 109 insertions(+), 61 deletions(-)
delete mode 100644 clang/test/Sema/attr-format-missing-unsupported.c
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 7b534d416fdd9..6d2042f159c18 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1182,6 +1182,7 @@ def FormatY2K : DiagGroup<"format-y2k">;
def FormatPedantic : DiagGroup<"format-pedantic">;
def FormatSignedness : DiagGroup<"format-signedness">;
def FormatTypeConfusion : DiagGroup<"format-type-confusion">;
+def MissingFormatAttribute : DiagGroup<"missing-format-attribute">;
def FormatOverflowNonKprintf: DiagGroup<"format-overflow-non-kprintf">;
def FormatOverflow: DiagGroup<"format-overflow", [FormatOverflowNonKprintf]>;
@@ -1193,7 +1194,7 @@ def Format : DiagGroup<"format",
FormatSecurity, FormatY2K, FormatInvalidSpecifier,
FormatInsufficientArgs, FormatOverflow, FormatTruncation]>,
DiagCategory<"Format String Issue">;
-def FormatNonLiteral : DiagGroup<"format-nonliteral">;
+def FormatNonLiteral : DiagGroup<"format-nonliteral", [MissingFormatAttribute]>;
def Format2 : DiagGroup<"format=2",
[FormatNonLiteral, FormatSecurity, FormatY2K]>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c42ee4ddb4e65..c42a0f65383cf 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3481,7 +3481,7 @@ def err_format_attribute_implicit_this_format_string : Error<
def warn_missing_format_attribute : Warning<
"diagnostic behavior may be improved by adding the '%0' attribute to the "
"declaration of %1">,
- InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
+ InGroup<MissingFormatAttribute>, DefaultIgnore;
def err_callback_attribute_no_callee : Error<
"'callback' attribute specifies no callback callee">;
def err_callback_attribute_invalid_callee : Error<
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index c14011ed38ccd..5326ac60a7b41 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -7026,15 +7026,19 @@ std::string escapeFormatString(StringRef Input) {
return Result;
}
-static void CheckMissingFormatAttributes(
+static bool CheckMissingFormatAttribute(
Sema *S, ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
StringLiteral *ReferenceFormatString, unsigned FormatIdx,
unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx,
SourceLocation Loc) {
- NamedDecl *Caller = S->getCurFunctionOrMethodDecl();
- if (!Caller)
- return;
- Caller = dyn_cast<NamedDecl>(Caller->getCanonicalDecl());
+ if (S->getDiagnostics().isIgnored(diag::warn_missing_format_attribute,
+ SourceLocation()))
+ return false;
+
+ DeclContext *DC = S->CurContext;
+ if (!isa<ObjCMethodDecl>(DC) && !isa<FunctionDecl>(DC) && !isa<BlockDecl>(DC))
+ return false;
+ Decl *Caller = cast<Decl>(DC)->getCanonicalDecl();
unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller);
@@ -7055,17 +7059,17 @@ static void CheckMissingFormatAttributes(
unsigned NumCalleeArgs = Args.size() - FirstDataArg;
if (NumCalleeArgs == 0 || NumCallerParams < NumCalleeArgs) {
// There aren't enough arguments in the caller to pass to callee.
- return;
+ return false;
}
for (unsigned CalleeIdx = Args.size() - 1, CallerIdx = NumCallerParams - 1;
CalleeIdx >= FirstDataArg; --CalleeIdx, --CallerIdx) {
const auto *Arg =
dyn_cast<DeclRefExpr>(Args[CalleeIdx]->IgnoreParenCasts());
if (!Arg)
- return;
+ return false;
const auto *Param = dyn_cast<ParmVarDecl>(Arg->getDecl());
if (!Param || Param->getFunctionScopeIndex() != CallerIdx)
- return;
+ return false;
}
FirstArgumentIndex =
NumCallerParams + CallerArgumentIndexOffset - NumCalleeArgs;
@@ -7080,13 +7084,14 @@ static void CheckMissingFormatAttributes(
case Sema::FormatArgumentPassingKind::FAPK_Elsewhere:
// The callee has a format_matches attribute. We will emit that instead.
if (!ReferenceFormatString)
- return;
+ return false;
break;
}
// Emit the diagnostic and fixit.
unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset;
StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType);
+ NamedDecl *ND = dyn_cast<NamedDecl>(Caller);
do {
std::string Attr, Fixit;
if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere)
@@ -7098,8 +7103,15 @@ static void CheckMissingFormatAttributes(
<< "format_matches(" << FormatTypeName << ", " << FormatStringIndex
<< ", \"" << escapeFormatString(ReferenceFormatString->getString())
<< "\")";
- auto DB = S->Diag(Loc, diag::warn_missing_format_attribute)
- << Attr << Caller;
+ auto DB = S->Diag(Loc, diag::warn_missing_format_attribute) << Attr;
+ if (ND)
+ DB << ND;
+ else
+ DB << "block";
+
+ // Blocks don't provide a correct end loc, so skip emitting a fixit.
+ if (isa<BlockDecl>(Caller))
+ break;
SourceLocation SL;
llvm::raw_string_ostream IS(Fixit);
@@ -7124,8 +7136,8 @@ static void CheckMissingFormatAttributes(
DB << FixItHint::CreateInsertion(SL, Fixit);
} while (false);
- S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller;
+ // Add implicit format or format_matches attribute.
if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) {
Caller->addAttr(FormatAttr::CreateImplicit(
S->getASTContext(), &S->getASTContext().Idents.get(FormatTypeName),
@@ -7135,6 +7147,15 @@ static void CheckMissingFormatAttributes(
S->getASTContext(), &S->getASTContext().Idents.get(FormatTypeName),
FormatStringIndex, ReferenceFormatString));
}
+
+ {
+ auto DB = S->Diag(Caller->getLocation(), diag::note_entity_declared_at);
+ if (ND)
+ DB << ND;
+ else
+ DB << "block";
+ }
+ return true;
}
bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
@@ -7193,11 +7214,10 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
SourceMgr.isInSystemMacro(FormatLoc))
return false;
- const LangOptions &LO = getLangOpts();
- if (CallerParamIdx && (LO.GNUMode || LO.C23 || LO.CPlusPlus11))
- CheckMissingFormatAttributes(this, Args, APK, ReferenceFormatString,
- format_idx, firstDataArg, Type,
- *CallerParamIdx, Loc);
+ if (CallerParamIdx && CheckMissingFormatAttribute(
+ this, Args, APK, ReferenceFormatString, format_idx,
+ firstDataArg, Type, *CallerParamIdx, Loc))
+ return false;
// Strftime is particular as it always uses a single 'time' argument,
// so it is safe to pass a non-literal string.
diff --git a/clang/test/Sema/attr-format-missing-gnu.c b/clang/test/Sema/attr-format-missing-gnu.c
index 83f20b8b85a7b..196b45cf58d48 100644
--- a/clang/test/Sema/attr-format-missing-gnu.c
+++ b/clang/test/Sema/attr-format-missing-gnu.c
@@ -1,12 +1,15 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -std=gnu11 -Wmissing-format-attribute %s
-// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=gnu++98 -Wmissing-format-attribute %s
-// RUN: %clang_cc1 -fsyntax-only -std=gnu11 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
-// RUN: %clang_cc1 -fsyntax-only -x c++ -std=gnu++98 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -std=gnu11 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c++ -std=gnu++98 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -std=gnu11 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -x c++ -std=gnu++98 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
typedef unsigned long size_t;
typedef long ssize_t;
typedef __builtin_va_list va_list;
+__attribute__((format(printf, 1, 2)))
+int printf(const char *, ...);
+
__attribute__((format(printf, 1, 0)))
int vprintf(const char *, va_list);
@@ -18,3 +21,35 @@ void f1(char *out, va_list args) // #f1
vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f1'}}
// expected-note@#f1 {{'f1' declared here}}
}
+
+void f2(void) {
+ void (^b1)(const char *, ...) = ^(const char *fmt, ...) { // #b1
+ va_list args;
+ vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of block}}
+ // expected-note@#b1 {{block declared here}}
+ };
+
+ void (^b2)(const char *, va_list) = ^(const char *fmt, va_list args) { // #b2
+ vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of block}}.
+ // expected-note@#b2 {{block declared here}}
+ };
+
+ void (^b3)(const char *, int x, float y) = ^(const char *fmt, int x, float y) { // #b3
+ printf(fmt, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of block}}.
+ // expected-note@#b3 {{block declared here}}
+ };
+
+ void __attribute__((__format__(__printf__, 1, 2))) (^b4)(const char *, ...) =
+ ^(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))) {
+ va_list args;
+ vprintf(fmt, args);
+ };
+
+ void __attribute__((__format__(__printf__, 2, 3))) (^b5)(const char *, const char *, ...) =
+ ^(const char *not_fmt, const char *fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { // #b5
+ va_list args;
+ vprintf(fmt, args);
+ vprintf(not_fmt, args); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of block}}
+ // expected-note@#b5 {{block declared here}}
+ };
+}
diff --git a/clang/test/Sema/attr-format-missing-unsupported.c b/clang/test/Sema/attr-format-missing-unsupported.c
deleted file mode 100644
index 2694ce1b9bb49..0000000000000
--- a/clang/test/Sema/attr-format-missing-unsupported.c
+++ /dev/null
@@ -1,17 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c11 -Wmissing-format-attribute %s
-// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=c++98 -Wmissing-format-attribute %s
-
-typedef unsigned long size_t;
-typedef long ssize_t;
-typedef __builtin_va_list va_list;
-
-__attribute__((format(printf, 1, 0)))
-int vprintf(const char *, va_list);
-
-// Test that diagnostic is disabled when the standard doesn't support a portable attribute syntax.
-// expected-no-diagnostics
-
-void f1(char *out, va_list args) // #f1
-{
- vprintf(out, args);
-}
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
index 8cfc2b2d07dab..5133e595315d7 100644
--- a/clang/test/Sema/attr-format-missing.c
+++ b/clang/test/Sema/attr-format-missing.c
@@ -177,10 +177,10 @@ void f15(char *ch, const char *out, ... /* args */) // #f15
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
void f16(const char *a, ...) // #f16
{
- va_list ap;
+ va_list args;
const char *const b = a;
- vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f16'}}
- // expected-note@#f16 {{'f16' declared here}}
+ vprintf(b, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f16'}}
+ // expected-note@#f16 {{'f16' declared here}}
}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] "
diff --git a/clang/test/Sema/format-strings-scanf.c b/clang/test/Sema/format-strings-scanf.c
index 22c1cce2f989b..941e3f7513bd6 100644
--- a/clang/test/Sema/format-strings-scanf.c
+++ b/clang/test/Sema/format-strings-scanf.c
@@ -36,8 +36,9 @@ int vscanf(const char * restrict, va_list);
int vfscanf(FILE * restrict, const char * restrict, va_list);
int vsscanf(const char * restrict, const char * restrict, va_list);
-void test(const char *s, int *i) {
- scanf(s, i); // expected-warning{{format string is not a string literal}}
+void test(const char *s, int *i) { // #test
+ scanf(s, i); // expected-warning{{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'test'}}
+ // expected-note@#test {{'test' declared here}}
scanf("%0d", i); // expected-warning{{zero field width in scanf format string is unused}}
scanf("%00d", i); // expected-warning{{zero field width in scanf format string is unused}}
scanf("%d%[asdfasdfd", i, s); // expected-warning{{no closing ']' for '%[' in scanf format string}}
diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c
index b46fc2c008b3a..4240c143948a6 100644
--- a/clang/test/Sema/format-strings.c
+++ b/clang/test/Sema/format-strings.c
@@ -31,10 +31,11 @@ void check_string_literal1( const char* s, ... ) {
// expected-note at -1{{treat the string as an argument to avoid this}}
}
-void check_string_literal2( const char* s, ... ) {
+void check_string_literal2( const char* s, ... ) { // #check_string_literal2
va_list ap;
va_start(ap,s);
- vprintf(s,ap); // expected-warning {{format string is not a string literal}}
+ vprintf(s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'check_string_literal2'}}
+ // expected-note@#check_string_literal2 {{'check_string_literal2' declared here}}
}
void check_string_literal3( FILE* fp, const char* s, ... ) {
@@ -44,10 +45,11 @@ void check_string_literal3( FILE* fp, const char* s, ... ) {
// expected-note at -1{{treat the string as an argument to avoid this}}
}
-void check_string_literal4( FILE* fp, const char* s, ... ) {
+void check_string_literal4( FILE* fp, const char* s, ... ) { // #check_string_literal4
va_list ap;
va_start(ap,s);
- vfprintf(fp,s,ap); // expected-warning {{format string is not a string literal}}
+ vfprintf(fp,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'check_string_literal4'}}
+ // expected-note@#check_string_literal4 {{'check_string_literal4' declared here}}
}
void check_string_literal5( const char* s, ... ) {
@@ -58,11 +60,12 @@ void check_string_literal5( const char* s, ... ) {
// expected-note at -1{{treat the string as an argument to avoid this}}
}
-void check_string_literal6( const char* s, ... ) {
+void check_string_literal6( const char* s, ... ) { // #check_string_literal6
char * b;
va_list ap;
va_start(ap,s);
- vasprintf(&b,s,ap); // expected-warning {{format string is not a string literal}}
+ vasprintf(&b,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'check_string_literal6'}}
+ // expected-note@#check_string_literal6 {{'check_string_literal6' declared here}}
}
void check_string_literal7( const char* s, char *buf ) {
@@ -89,16 +92,18 @@ void check_string_literal10( const char* s, char *buf, ... ) {
// expected-note at -1{{treat the string as an argument to avoid this}}
}
-void check_string_literal11( const char* s, char *buf, ... ) {
+void check_string_literal11( const char* s, char *buf, ... ) { // #check_string_literal11
va_list ap;
va_start(ap,buf);
- vsprintf(buf,s,ap); // expected-warning {{format string is not a string lit}}
+ vsprintf(buf,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'check_string_literal11'}}
+ // expected-note@#check_string_literal11 {{'check_string_literal11' declared here}}
}
-void check_string_literal12( const char* s, char *buf, ... ) {
+void check_string_literal12( const char* s, char *buf, ... ) { // #check_string_literal12
va_list ap;
va_start(ap,buf);
- vsnprintf(buf,2,s,ap); // expected-warning {{format string is not a string lit}}
+ vsnprintf(buf,2,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'check_string_literal12'}}
+ // expected-note@#check_string_literal12 {{'check_string_literal12' declared here}}
}
void check_string_literal13( char *buf, ... ) {
@@ -107,10 +112,11 @@ void check_string_literal13( char *buf, ... ) {
vsnprintf(buf,2,global_fmt,ap); // expected-warning {{format string is not a string literal}}
}
-void check_string_literal14( FILE* fp, const char* s, char *buf, ... ) {
+void check_string_literal14( FILE* fp, const char* s, char *buf, ... ) { // #check_string_literal14
va_list ap;
va_start(ap,buf);
- __builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // expected-warning {{format string is not a string lit}}
+ __builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 4)' attribute to the declaration of 'check_string_literal14'}}
+ // expected-note@#check_string_literal14 {{'check_string_literal14' declared here}}
}
void check_string_literal15( FILE* fp, const char* s, char *buf, ... ) {
@@ -119,10 +125,11 @@ void check_string_literal15( FILE* fp, const char* s, char *buf, ... ) {
__builtin___vsnprintf_chk(buf,2,0,-1,global_fmt,ap); // expected-warning {{format string is not a string literal}}
}
-void check_string_literal16(const char* s, ... ) {
+void check_string_literal16(const char* s, ... ) { // #check_string_literal16
va_list ap;
va_start(ap,s);
- vscanf(s, ap); // expected-warning {{format string is not a string literal}}
+ vscanf(s, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'check_string_literal16'}}
+ // expected-note@#check_string_literal16 {{'check_string_literal16' declared here}}
}
void check_string_literal17() {
@@ -911,12 +918,13 @@ void test_block(void) {
void __attribute__((__format__(__printf__, 2, 3))) (^printf_arg2)(
const char *, const char *, ...) =
- ^(const char *not_fmt, const char *fmt, ...)
+ ^(const char *not_fmt, const char *fmt, ...) // #printf_arg2
__attribute__((__format__(__printf__, 2, 3))) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
- vprintf(not_fmt, ap); // expected-warning{{format string is not a string literal}}
+ vprintf(not_fmt, ap); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of block}}
+ // expected-note@#printf_arg2 {{block declared here}}
va_end(ap);
};
>From 9a0a50984e528ecd0828ac42dd39134d08dc93e8 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Mon, 24 Nov 2025 00:46:00 -0800
Subject: [PATCH 09/12] Cleanup
---
clang/docs/ReleaseNotes.rst | 5 +++--
clang/lib/Sema/SemaChecking.cpp | 33 +++++++++++----------------------
2 files changed, 14 insertions(+), 24 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index be384b7db72a3..1d04f62c632b9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -401,8 +401,9 @@ Improvements to Clang's diagnostics
or continue (#GH166013)
- Clang now emits a diagnostic in case `vector_size` or `ext_vector_type`
attributes are used with a negative size (#GH165463).
-- Clang now detects potential missing format attributes on function declarations
- when calling format functions. (#GH60718)
+- Clang now detects potential missing format and format_matches attributes on function,
+ Objective-C method and block declarations when calling format functions. It is part
+ of the format-nonliteral diagnostic (#GH60718)
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 5326ac60a7b41..cc53f4b93ed06 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -7013,19 +7013,6 @@ bool Sema::CheckFormatString(const FormatMatchesAttr *Format,
return false;
}
-std::string escapeFormatString(StringRef Input) {
- std::string Result;
- llvm::raw_string_ostream OS(Result);
- for (char C : Input) {
- StringRef Escaped = escapeCStyle<EscapeChar::Double>(C);
- if (Escaped.empty())
- OS << C;
- else
- OS << Escaped;
- }
- return Result;
-}
-
static bool CheckMissingFormatAttribute(
Sema *S, ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
StringLiteral *ReferenceFormatString, unsigned FormatIdx,
@@ -7094,15 +7081,17 @@ static bool CheckMissingFormatAttribute(
NamedDecl *ND = dyn_cast<NamedDecl>(Caller);
do {
std::string Attr, Fixit;
- if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere)
- llvm::raw_string_ostream(Attr)
- << "format(" << FormatTypeName << ", " << FormatStringIndex << ", "
- << FirstArgumentIndex << ")";
- else
- llvm::raw_string_ostream(Attr)
- << "format_matches(" << FormatTypeName << ", " << FormatStringIndex
- << ", \"" << escapeFormatString(ReferenceFormatString->getString())
- << "\")";
+ llvm::raw_string_ostream AttrOS(Attr);
+ if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) {
+ AttrOS << "format(" << FormatTypeName << ", " << FormatStringIndex << ", "
+ << FirstArgumentIndex << ")";
+ } else {
+ AttrOS << "format_matches(" << FormatTypeName << ", " << FormatStringIndex
+ << ", \"";
+ AttrOS.write_escaped(ReferenceFormatString->getString());
+ AttrOS << "\")";
+ }
+ AttrOS.flush();
auto DB = S->Diag(Loc, diag::warn_missing_format_attribute) << Attr;
if (ND)
DB << ND;
>From e139e68e559a2de3a98118fe655ed469adfa96e9 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Mon, 24 Nov 2025 06:58:20 -0800
Subject: [PATCH 10/12] Fix cpp test
---
clang/test/SemaCXX/format-strings.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/test/SemaCXX/format-strings.cpp b/clang/test/SemaCXX/format-strings.cpp
index 48cf23999a94f..5890f56cfc952 100644
--- a/clang/test/SemaCXX/format-strings.cpp
+++ b/clang/test/SemaCXX/format-strings.cpp
@@ -33,7 +33,7 @@ class Foo {
int scanf(const char *, ...) __attribute__((format(scanf, 2, 3)));
int printf(const char *, ...) __attribute__((format(printf, 2, 3)));
- int printf2(const char *, ...);
+ int printf2(const char *, ...); // #Foo_printf2
static const char *gettext_static(const char *fmt) __attribute__((format_arg(1)));
static int printf_static(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
@@ -86,8 +86,8 @@ int Foo::printf(const char *fmt, ...) {
int Foo::printf2(const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
- vprintf(fmt, ap); // expected-warning{{format string is not a string literal}}
-
+ vprintf(fmt, ap); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'printf2'}}
+ // expected-note@#Foo_printf2 {{'printf2' declared here}}
return 0;
}
>From 649cfebb36697c2f2ee39d0588f01387ee26baf6 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Mon, 24 Nov 2025 09:07:00 -0800
Subject: [PATCH 11/12] Fix objc test
---
clang/test/SemaObjC/format-strings-objc.m | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/test/SemaObjC/format-strings-objc.m b/clang/test/SemaObjC/format-strings-objc.m
index 40c1d31b1fd4c..df1da441941eb 100644
--- a/clang/test/SemaObjC/format-strings-objc.m
+++ b/clang/test/SemaObjC/format-strings-objc.m
@@ -203,7 +203,7 @@ void test_toll_free_bridging(CFStringRef x, id y) {
}
@interface Bar
-+ (void)log:(NSString *)fmt, ...;
++ (void)log:(NSString *)fmt, ...; // #log
+ (void)log2:(NSString *)fmt, ... __attribute__((format(NSString, 1, 2)));
@end
@@ -212,7 +212,8 @@ @implementation Bar
+ (void)log:(NSString *)fmt, ... {
va_list ap;
va_start(ap,fmt);
- NSLogv(fmt, ap); // expected-warning{{format string is not a string literal}}
+ NSLogv(fmt, ap); // expected-warning{{diagnostic behavior may be improved by adding the 'format(NSString, 1, 2)' attribute to the declaration of 'log:'}}
+ // expected-note@#log {{'log:' declared here}}
va_end(ap);
}
>From 7420002b5414d65c79426a1c1b94fe0446824466 Mon Sep 17 00:00:00 2001
From: Vladimir Vuksanovic <vladimir.vuksanovic at htecgroup.com>
Date: Wed, 26 Nov 2025 23:44:02 -0800
Subject: [PATCH 12/12] Cleanup
---
clang/lib/Sema/SemaChecking.cpp | 4 +---
.../{attr-format-missing-gnu.c => format-attr-missing-gnu.c} | 0
.../Sema/{attr-format-missing.c => format-attr-missing.c} | 0
.../Sema/{attr-format-missing.cpp => format-attr-missing.cpp} | 0
.../Sema/{attr-format-missing.m => format-attr-missing.m} | 0
5 files changed, 1 insertion(+), 3 deletions(-)
rename clang/test/Sema/{attr-format-missing-gnu.c => format-attr-missing-gnu.c} (100%)
rename clang/test/Sema/{attr-format-missing.c => format-attr-missing.c} (100%)
rename clang/test/Sema/{attr-format-missing.cpp => format-attr-missing.cpp} (100%)
rename clang/test/Sema/{attr-format-missing.m => format-attr-missing.m} (100%)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index b8d8bf48944ef..6929173feee23 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -17,7 +17,6 @@
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/Attr.h"
#include "clang/AST/AttrIterator.h"
-#include "clang/AST/Attrs.inc"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
@@ -41,7 +40,6 @@
#include "clang/AST/TypeLoc.h"
#include "clang/AST/UnresolvedSet.h"
#include "clang/Basic/AddressSpaces.h"
-#include "clang/Basic/CharInfo.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/IdentifierTable.h"
@@ -7111,7 +7109,7 @@ static bool CheckMissingFormatAttribute(
unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx,
SourceLocation Loc) {
if (S->getDiagnostics().isIgnored(diag::warn_missing_format_attribute,
- SourceLocation()))
+ Loc))
return false;
DeclContext *DC = S->CurContext;
diff --git a/clang/test/Sema/attr-format-missing-gnu.c b/clang/test/Sema/format-attr-missing-gnu.c
similarity index 100%
rename from clang/test/Sema/attr-format-missing-gnu.c
rename to clang/test/Sema/format-attr-missing-gnu.c
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/format-attr-missing.c
similarity index 100%
rename from clang/test/Sema/attr-format-missing.c
rename to clang/test/Sema/format-attr-missing.c
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/format-attr-missing.cpp
similarity index 100%
rename from clang/test/Sema/attr-format-missing.cpp
rename to clang/test/Sema/format-attr-missing.cpp
diff --git a/clang/test/Sema/attr-format-missing.m b/clang/test/Sema/format-attr-missing.m
similarity index 100%
rename from clang/test/Sema/attr-format-missing.m
rename to clang/test/Sema/format-attr-missing.m
More information about the cfe-commits
mailing list