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 <[email protected]>
Date: Fri, 31 Oct 2025 02:10:05 -0700
Subject: [PATCH 1/4] [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 <[email protected]>
---
 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 <[email protected]>
Date: Fri, 7 Nov 2025 03:13:04 -0800
Subject: [PATCH 2/4] 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 <[email protected]>
Date: Mon, 17 Nov 2025 00:52:50 -0800
Subject: [PATCH 3/4] 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>
+
+@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)));
+@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 <[email protected]>
Date: Mon, 17 Nov 2025 00:55:32 -0800
Subject: [PATCH 4/4] 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;
 }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to