[clang] [clang] Catch missing format attributes (PR #105479)

Budimir Aranđelović via cfe-commits cfe-commits at lists.llvm.org
Fri Nov 8 03:42:03 PST 2024


https://github.com/budimirarandjelovichtec updated https://github.com/llvm/llvm-project/pull/105479

>From 33135f737faf2a39f9f9b102414aba268544691e Mon Sep 17 00:00:00 2001
From: budimirarandjelovicsyrmia <budimir.arandjelovic at syrmia.com>
Date: Fri, 5 Apr 2024 15:20:37 +0200
Subject: [PATCH] [clang] Catch missing format attributes

---
 clang/docs/ReleaseNotes.rst                   |   2 +
 clang/include/clang/Basic/DiagnosticGroups.td |   1 -
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +
 clang/include/clang/Sema/Attr.h               |   7 +
 clang/include/clang/Sema/Sema.h               |   5 +
 clang/lib/Sema/SemaChecking.cpp               |   4 +-
 clang/lib/Sema/SemaDecl.cpp                   |   2 +
 clang/lib/Sema/SemaDeclAttr.cpp               | 179 +++++++++++-
 clang/test/Sema/attr-format-missing.c         | 259 ++++++++++++++++++
 clang/test/Sema/attr-format-missing.cpp       | 189 +++++++++++++
 10 files changed, 648 insertions(+), 4 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 d60d88920c6d0f..36dc2bffa85bf6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -464,6 +464,8 @@ Improvements to Clang's diagnostics
 
 - Clang now diagnoses ``[[deprecated]]`` attribute usage on local variables (#GH90073).
 
+- Clang now diagnoses missing format attributes for non-template functions and class/struct/union members. (#GH60718)
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..f941f3883b78d7 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -530,7 +530,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 ae3e243bdc58bd..7087457975c6c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1055,6 +1055,10 @@ def err_opencl_invalid_param : Error<
   "declaring function parameter of type %0 is not allowed%select{; did you forget * ?|}1">;
 def err_opencl_invalid_return : Error<
   "declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
+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 note_format_function : Note<"%0 format function">;
 def warn_pragma_options_align_reset_failed : Warning<
   "#pragma options align=reset failed: %0">,
   InGroup<IgnoredPragmas>;
diff --git a/clang/include/clang/Sema/Attr.h b/clang/include/clang/Sema/Attr.h
index 3f0b10212789a4..37c124ca7b454a 100644
--- a/clang/include/clang/Sema/Attr.h
+++ b/clang/include/clang/Sema/Attr.h
@@ -123,6 +123,13 @@ inline bool isInstanceMethod(const Decl *D) {
   return false;
 }
 
+inline bool checkIfMethodHasImplicitObjectParameter(const Decl *D) {
+  if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(D))
+    return MethodDecl->isInstance() &&
+           !MethodDecl->hasCXXExplicitFunctionObjectParameter();
+  return false;
+}
+
 /// Diagnose mutually exclusive attributes when present on a given
 /// declaration. Returns true if diagnosed.
 template <typename AttrTy>
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index aa2f5ff3ef7207..b01a361233a91e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4576,6 +4576,11 @@ class Sema final : public SemaBase {
 
   enum class RetainOwnershipKind { NS, CF, OS };
 
+  void DetectMissingFormatAttributes(const FunctionDecl *Callee,
+                                     ArrayRef<const Expr *> Args,
+                                     SourceLocation Loc);
+  void EmitMissingFormatAttributesDiagnostic(const FunctionDecl *Caller);
+
   UuidAttr *mergeUuidAttr(Decl *D, const AttributeCommonInfo &CI,
                           StringRef UuidAsWritten, MSGuidDecl *GuidDecl);
 
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index d78968179b1fdc..fd86355abce745 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3406,8 +3406,10 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
     }
   }
 
-  if (FD)
+  if (FD) {
     diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc);
+    DetectMissingFormatAttributes(FD, Args, Loc);
+  }
 }
 
 void Sema::CheckConstrainedAuto(const AutoType *AutoT, SourceLocation Loc) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index cc0019eaf4f021..2e37b7b49db6b0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16062,6 +16062,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
         }
       }
 
+      EmitMissingFormatAttributesDiagnostic(FD);
+
       // We might not have found a prototype because we didn't wish to warn on
       // the lack of a missing prototype. Try again without the checks for
       // whether we want to warn on the missing prototype.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index d05d326178e1b8..f9f34e81754151 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3624,7 +3624,7 @@ static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
 
   // In C++ the implicit 'this' function parameter also counts, and they are
   // counted from one.
-  bool HasImplicitThisParam = isInstanceMethod(D);
+  bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
   unsigned NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
 
   IdentifierInfo *II = AL.getArgAsIdent(0)->Ident;
@@ -3737,7 +3737,7 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
     return;
   }
 
-  bool HasImplicitThisParam = isInstanceMethod(D);
+  bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
   int32_t NumArgs = getFunctionOrMethodNumParams(D);
 
   FunctionDecl *FD = D->getAsFunction();
@@ -5430,6 +5430,181 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
 }
 
+// Diagnosing missing format attributes is implemented in two steps:
+// 1. Detect missing format attributes while checking function calls.
+// 2. Emit diagnostic in part that processes function body.
+// For this purpose it is created vector that stores information about format
+// attributes. There are no two format attributes with same arguments in a
+// vector. Vector could contains attributes that only store information about
+// format type (format string and first to check argument are set to -1).
+namespace {
+std::vector<FormatAttr *> MissingAttributes;
+} // end anonymous namespace
+
+// This function is called only if function call is not inside template body.
+// TODO: Add call for function calls inside template body.
+// Detects and stores missing format attributes in a vector.
+void Sema::DetectMissingFormatAttributes(const FunctionDecl *Callee,
+                                         ArrayRef<const Expr *> Args,
+                                         SourceLocation Loc) {
+  assert(Callee);
+
+  // If there is no caller, exit.
+  const FunctionDecl *Caller = getCurFunctionDecl();
+  if (!getCurFunctionDecl())
+    return;
+
+  // Check if callee function is a format function.
+  // If it is, check if caller function misses format attributes.
+
+  if (!Callee->hasAttr<FormatAttr>())
+    return;
+
+  // va_list is not intended to be passed to variadic function.
+  if (Callee->isVariadic())
+    return;
+
+  // Check if va_list is passed to callee function.
+  // If va_list is not passed, return.
+  bool hasVaList = false;
+  for (const auto *Param : Callee->parameters()) {
+    if (Param->getOriginalType().getCanonicalType() ==
+        getASTContext().getBuiltinVaListType().getCanonicalType()) {
+      hasVaList = true;
+      break;
+    }
+  }
+  if (!hasVaList)
+    return;
+
+  unsigned int FormatArgumentIndexOffset =
+      checkIfMethodHasImplicitObjectParameter(Callee) ? 2 : 1;
+
+  // If callee function is format function and format arguments are not
+  // relevant to emit diagnostic, save only information about format type
+  // (format index and first-to-check argument index are set to -1).
+  // Information about format type is later used to determine if there are
+  // more than one format type found.
+
+  unsigned int NumArgs = Args.size();
+  // Check if function has format attribute with forwarded format string.
+  IdentifierInfo *AttrType;
+  const ParmVarDecl *FormatStringArg;
+  if (!llvm::any_of(
+          Callee->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
+            AttrType = Attr->getType();
+
+            int OffsetFormatIndex =
+                Attr->getFormatIdx() - FormatArgumentIndexOffset;
+            if (OffsetFormatIndex < 0 || (unsigned)OffsetFormatIndex >= NumArgs)
+              return false;
+
+            if (const auto *FormatArgExpr = dyn_cast<DeclRefExpr>(
+                    Args[OffsetFormatIndex]->IgnoreParenCasts()))
+              if (FormatStringArg = dyn_cast_or_null<ParmVarDecl>(
+                      FormatArgExpr->getReferencedDeclOfCallee()))
+                return true;
+            return false;
+          })) {
+    MissingAttributes.push_back(
+        FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+    return;
+  }
+
+  unsigned ArgumentIndexOffset =
+      checkIfMethodHasImplicitObjectParameter(Caller) ? 2 : 1;
+
+  unsigned NumOfCallerFunctionParams = Caller->getNumParams();
+
+  // Compare caller and callee function format attribute arguments (archetype
+  // and format string). If they don't match, caller misses format attribute.
+  if (llvm::any_of(
+          Caller->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
+            if (Attr->getType() != AttrType)
+              return false;
+            int OffsetFormatIndex = Attr->getFormatIdx() - ArgumentIndexOffset;
+
+            if (OffsetFormatIndex < 0 ||
+                (unsigned)OffsetFormatIndex >= NumOfCallerFunctionParams)
+              return false;
+
+            if (Caller->parameters()[OffsetFormatIndex] != FormatStringArg)
+              return false;
+
+            return true;
+          })) {
+    MissingAttributes.push_back(
+        FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+    return;
+  }
+
+  // Get format string index
+  int FormatStringIndex =
+      FormatStringArg->getFunctionScopeIndex() + ArgumentIndexOffset;
+
+  // Get first argument index
+  int FirstToCheck = Caller->isVariadic()
+                         ? (NumOfCallerFunctionParams + ArgumentIndexOffset)
+                         : 0;
+
+  // Do not add duplicate in a vector of missing format attributes.
+  if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
+        return Attr->getType() == AttrType &&
+               Attr->getFormatIdx() == FormatStringIndex &&
+               Attr->getFirstArg() == FirstToCheck;
+      }))
+    MissingAttributes.push_back(FormatAttr::CreateImplicit(
+        getASTContext(), AttrType, FormatStringIndex, FirstToCheck, Loc));
+}
+
+// This function is called only if caller function is not template.
+// TODO: Add call for template functions.
+// Emits missing format attribute diagnostics.
+void Sema::EmitMissingFormatAttributesDiagnostic(const FunctionDecl *Caller) {
+  const clang::IdentifierInfo *AttrType = MissingAttributes[0]->getType();
+  for (unsigned i = 1; i < MissingAttributes.size(); ++i) {
+    if (AttrType != MissingAttributes[i]->getType()) {
+      // Clear vector of missing attributes because it could be used in
+      // diagnosing missing format attributes in another caller.
+      MissingAttributes.clear();
+      return;
+    }
+  }
+
+  for (const FormatAttr *FA : MissingAttributes) {
+    // If format index and first-to-check argument index are negative, it means
+    // that this attribute is only saved for multiple format types checking.
+    if (FA->getFormatIdx() < 0 || FA->getFirstArg() < 0)
+      continue;
+
+    // If caller function has format attributes and callee format attribute type
+    // mismatches caller attribute type, do not emit diagnostic.
+    if (Caller->hasAttr<FormatAttr>() &&
+        !llvm::any_of(Caller->specific_attrs<FormatAttr>(),
+                      [FA](const FormatAttr *FunctionAttr) {
+                        return FA->getType() == FunctionAttr->getType();
+                      }))
+      continue;
+
+    // Emit diagnostic
+    SourceLocation Loc = Caller->getFirstDecl()->getLocation();
+    Diag(Loc, diag::warn_missing_format_attribute)
+        << FA->getType() << Caller
+        << FixItHint::CreateInsertion(Loc,
+                                      (llvm::Twine("__attribute__((format(") +
+                                       FA->getType()->getName() + ", " +
+                                       llvm::Twine(FA->getFormatIdx()) + ", " +
+                                       llvm::Twine(FA->getFirstArg()) + ")))")
+                                          .str());
+    Diag(FA->getLocation(), diag::note_format_function) << FA->getType();
+  }
+
+  // Clear vector of missing attributes after emitting diagnostics for caller
+  // function because it could be used in diagnosing missing format attributes
+  // in another caller.
+  MissingAttributes.clear();
+}
+
 //===----------------------------------------------------------------------===//
 // Microsoft specific attribute handlers.
 //===----------------------------------------------------------------------===//
diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c
new file mode 100644
index 00000000000000..5f6a11844fc2fb
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.c
@@ -0,0 +1,259 @@
+// 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 -x c++ -verify -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -x c++ -verify -std=c++23 -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -x c++ -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+
+#ifndef __cplusplus
+typedef __CHAR16_TYPE__ char16_t;
+typedef __CHAR32_TYPE__ char32_t;
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+typedef __builtin_va_list va_list;
+
+__attribute__((__format__(__printf__, 1, 2)))
+int printf(const char *, ...); // #printf
+
+__attribute__((__format__(__scanf__, 1, 2)))
+int scanf(const char *, ...); // #scanf
+
+__attribute__((__format__(__printf__, 1, 0)))
+int vprintf(const char *, va_list); // #vprintf
+
+__attribute__((__format__(__scanf__, 1, 0)))
+int vscanf(const char *, va_list); // #vscanf
+
+__attribute__((__format__(__printf__, 2, 0)))
+int vsprintf(char *, const char *, va_list); // #vsprintf
+
+__attribute__((__format__(__printf__, 3, 0)))
+int vsnprintf(char *, size_t, const char *, va_list); // #vsnprintf
+
+#ifndef __cplusplus
+int vwscanf(const wchar_t *, va_list); // #vwscanf
+#endif
+
+__attribute__((__format__(__scanf__, 1, 4)))
+void f1(char *out, const size_t len, const char *format, ... /* args */) // #f1
+{
+    va_list args;
+    vsnprintf(out, len, format, args);
+}
+
+__attribute__((__format__(__printf__, 1, 4)))
+void f2(char *out, const size_t len, const char *format, ... /* args */) // #f2
+{
+    va_list args;
+    vsnprintf(out, len, format, args); // expected-warning@#f2 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f2'}}
+                                       // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 3, 4)))"
+                                       // expected-note at -2 {{'printf' format function}}
+}
+
+void f3(char *out, va_list args) // #f3
+{
+    vprintf(out, args); // expected-warning@#f3 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}}
+                        // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:6-[[@LINE-3]]:6}:"__attribute__((format(printf, 1, 0)))"
+                        // expected-note at -2 {{'printf' format function}}
+}
+
+void f4(char* out, ... /* args */) // #f4
+{
+    va_list args;
+    vprintf("test", args);
+
+    const char *ch;
+    vprintf(ch, args);
+}
+
+void f5(va_list args) // #f5
+{
+    char *ch;
+    vscanf(ch, args);
+}
+
+void f6(char *out, va_list args) // #f6
+{
+    char *ch;
+    vprintf(ch, args);
+    vprintf("test", args);
+    vprintf(out, args); // expected-warning@#f6 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f6'}}
+                        // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 0)))"
+                        // expected-note at -2 {{'printf' format function}}
+}
+
+void f7(const char *out, ... /* args */) // #f7
+{
+    va_list args;
+
+    vscanf(out, args); // expected-warning@#f7 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f7'}}
+                       // CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:6-[[@LINE-5]]:6}:"__attribute__((format(scanf, 1, 2)))"
+                       // expected-note at -2 {{'scanf' format function}}
+}
+
+void f8(const char *out, ... /* args */) // #f8
+{
+    va_list args;
+
+    vscanf(out, args);
+    vprintf(out, args);
+}
+
+void f9(const char out[], ... /* args */) // #f9
+{
+    va_list args;
+    char *ch;
+    vprintf(ch, args);
+    vsprintf(ch, out, args); // expected-warning@#f9 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f9'}}
+                             // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 2)))"
+                             // expected-note at -2 {{'printf' format function}}
+}
+
+#ifndef __cplusplus
+void f10(const wchar_t *out, ... /* args */) // #f10
+{
+    va_list args;
+    vwscanf(out, args);
+}
+#endif
+
+void f11(const char *out) // #f11
+{
+    va_list args;
+    vscanf(out, args); // expected-warning@#f11 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f11'}}
+                       // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 0)))"
+                       // expected-note at -2 {{'scanf' format function}}
+}
+
+void f12(char* out) // #f12
+{
+    va_list args;
+    const char* ch;
+    vsprintf(out, ch, args);
+    vprintf(out, args); // expected-warning@#f12 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f12'}}
+                        // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 0)))"
+                        // expected-note at -2 {{'printf' format function}}
+}
+
+void f13(const char *out, ... /* args */) // #f13
+{
+    va_list args;
+    printf(out, args);
+}
+
+void f14(char *out, ... /* args */) // #f14
+{
+    va_list args;
+    vscanf(out, args);
+    vprintf(out, args);
+}
+
+void f15(char *out, ... /* args */) // #f15
+{
+    va_list args;
+    vscanf(out, args);
+    {
+        vprintf(out, args);
+    }
+}
+
+void f16(char *out, va_list args) // #f16
+{
+    {
+        vscanf(out, args);
+        vprintf(out, args);
+    }
+}
+
+// expected-warning@#f17 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}}
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))"
+void f17(char *out, ... /* args */) // #f17
+{
+    va_list args;
+    vscanf(out, args); // expected-note {{'scanf' format function}}
+    {
+        vscanf(out, args);
+    }
+}
+
+void f18(char *out, int n, ... /* args */) // #f18
+{
+    va_list args;
+    if (n > 0) {
+        vprintf(out, args); // expected-warning@#f18 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}}
+                            // CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:6-[[@LINE-5]]:6}:"__attribute__((format(printf, 1, 3)))"
+                            // expected-note at -2 {{'printf' format function}}
+    }
+}
+
+void f19(char *out, int n, ... /* args */) // #f19
+{
+    va_list args;
+    if (n > 0) {}
+    else {
+        vprintf(out, args); // expected-warning@#f19 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}}
+                            // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 3)))"
+                            // expected-note at -2 {{'printf' format function}}
+    }
+}
+
+void f20(char *out, int n, ... /* args */) // #f20
+{
+    va_list args;
+    if (n > 0) {
+        vprintf(out, args);
+    } else {
+        vscanf(out, args);
+    }
+}
+
+void f21(char *ch, const char *out, ... /* args */) // #f21
+{
+    va_list args;
+    vprintf(ch, args); // expected-warning@#f21 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f21'}}
+                       // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 3)))"
+                       // expected-note at -2 {{'printf' format function}}
+    vprintf(out, args); // expected-warning@#f21 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f21'}}
+                        // CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:6-[[@LINE-7]]:6}:"__attribute__((format(printf, 2, 3)))"
+                        // expected-note at -2 {{'printf' format function}}
+}
+
+typedef va_list tdVaList;
+typedef int tdInt;
+
+void f22(const char *out, ... /* args */) // #f22
+{
+    tdVaList args;
+    vprintf(out, args); // expected-warning@#f22 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f22'}}
+                        // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 2)))"
+                        // expected-note at -2 {{'printf' format function}}
+}
+
+void f23(const char *out, tdVaList args) // #f23
+{
+    vscanf(out, args); // expected-warning@#f23 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f23'}}
+                       // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:6-[[@LINE-3]]:6}:"__attribute__((format(scanf, 1, 0)))"
+                       // expected-note at -2 {{'scanf' format function}}
+}
+
+void f24(const char *out, tdVaList args) // #f24
+{
+    vscanf(out, args);
+    vprintf(out, args);
+}
+
+void f25(char *out, ... /* args */) // #f25
+{
+    va_list args;
+    char *ch;
+    vscanf(ch, args);
+    vprintf(out, args);
+}
+
+void f26(char *out, ... /* args */) // #f26
+{
+    va_list args;
+    vscanf("%s", args);
+    vprintf(out, args);
+}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
new file mode 100644
index 00000000000000..24ea3ff0758a21
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -0,0 +1,189 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 -Wmissing-format-attribute %s
+// RUN: not %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: not %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits -std=c++23 %s 2>&1
+// FileCheck %s --check-prefixes=CHECK,CHECK-EXPLICIT-THIS-PARAMETER
+
+typedef __SIZE_TYPE__ size_t;
+typedef __builtin_va_list va_list;
+
+namespace std
+{
+    template<class Elem> struct basic_string_view {};
+    template<class Elem> struct basic_string {
+        const Elem *c_str() const noexcept;
+        basic_string(const basic_string_view<Elem> SW);
+    };
+
+    using string = basic_string<char>;
+    using wstring = basic_string<wchar_t>;
+    using string_view = basic_string_view<char>;
+    using wstring_view = basic_string_view<wchar_t>;
+}
+
+__attribute__((__format__(__printf__, 1, 2)))
+int printf(const char *, ...); // #printf
+
+__attribute__((__format__(__scanf__, 1, 2)))
+int scanf(const char *, ...); // #scanf
+
+__attribute__((__format__(__printf__, 1, 0)))
+int vprintf(const char *, va_list); // #vprintf
+
+__attribute__((__format__(__scanf__, 1, 0)))
+int vscanf(const char *, va_list); // #vscanf
+
+__attribute__((__format__(__printf__, 2, 0)))
+int vsprintf(char *, const char *, va_list); // #vsprintf
+
+__attribute__((__format__(__printf__, 3, 0)))
+int vsnprintf(char *, size_t, const char *, va_list); // #vsnprintf
+
+int vwprintf(const wchar_t *, va_list); // #vwprintf
+
+void f1(const std::string &str, ... /* args */) // #f1
+{
+    va_list args;
+    vscanf(str.c_str(), args);
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f2(const std::string &str, ... /* args */); // #f2
+
+void f3(std::string_view str, ... /* args */) // #f3
+{
+    va_list args;
+    vscanf(std::string(str).c_str(), args);
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f4(std::string_view str, ... /* args */); // #f4
+
+void f5(const std::wstring &str, ... /* args */) // #f5
+{
+    va_list args;
+    vwprintf(str.c_str(), args);
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f6(const std::wstring &str, ... /* args */); // #f6
+
+void f7(std::wstring_view str, ... /* args */) // #f7
+{
+    va_list args;
+    vwprintf(std::wstring(str).c_str(), args);
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f8(std::wstring_view str, ... /* args */); // #f8
+
+struct S1
+{
+    void fn1(const char *out, ... /* args */) // #S1_fn1
+    {
+        va_list args;
+        vscanf(out, args); // expected-warning@#S1_fn1 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn1'}}
+                           // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(scanf, 2, 3)))"
+                           // expected-note at -2 {{'scanf' format function}}
+    }
+
+    __attribute__((format(scanf, 2, 0)))
+    void fn2(const char *out, va_list args); // #S1_fn2
+
+    void fn3(const char *out, ... /* args */); // #S1_fn3
+
+#if __has_extension(cxx_explicit_this_parameter)
+    void fn4(this S1& explicitThis, const char *out, va_list args) // #S1_fn4
+    {
+        explicitThis.fn2(out, args); // expected-warning@#S1_fn4 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn4'}}
+                                     // CHECK-EXPLICIT-THIS-PARAMETER: fix-it:"{{.*}}":{[[@LINE-3]]:10-[[@LINE-3]]:10}:"__attribute__((format(scanf, 2, 0)))"
+                                     // expected-note at -2 {{'scanf' format function}}
+    }
+#endif
+};
+
+void S1::fn3(const char *out, ... /* args */)
+{
+    va_list args;
+    fn2(out, args); // expected-warning@#S1_fn3 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn3'}}
+                    // CHECK: fix-it:"{{.*}}":{[[@LINE-16]]:10-[[@LINE-16]]:10}:"__attribute__((format(scanf, 2, 3)))"
+                    // expected-note at -2 {{'scanf' format function}}
+}
+
+union U1
+{
+    __attribute__((format(printf, 2, 0)))
+    void fn1(const char *out, va_list args); // #U1_fn1
+
+    void fn2(const char *out, ... /* args */) // #U1_fn2
+    {
+        va_list args;
+        fn1(out, args); // expected-warning@#U1_fn2 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn2'}}
+                        // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(printf, 2, 3)))"
+                        // expected-note at -2 {{'printf' format function}}
+    }
+
+#if __has_extension(cxx_explicit_this_parameter)
+    void fn3(this U1&, const char *out) // #U1_fn3
+    {
+        va_list args;
+        vprintf(out, args); // expected-warning@#U1_fn3 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}}
+                            // CHECK-EXPLICIT-THIS-PARAMETER: fix-it:"{{.*}}":{[[@LINE-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(printf, 2, 0)))"
+                            // expected-note at -2 {{'printf' format function}}
+    }
+#endif
+};
+
+class C1
+{
+    __attribute__((format(printf, 3, 0)))
+    void fn1(const int n, const char *out, va_list args); // #C1_fn1
+
+    void fn2(const char *out, const int n, ... /* args */) // #C1_fn2
+    {
+        va_list args;
+        fn1(n, out, args); // expected-warning@#C1_fn2 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn2'}}
+                           // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(printf, 2, 4)))"
+                           // expected-note at -2 {{'printf' format function}}
+    }
+
+#if __has_extension(cxx_explicit_this_parameter)
+    void fn3(this const C1&, const char *out, va_list args) // #C1_fn3
+    {
+        vscanf(out, args); // expected-warning@#C1_fn3 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn3'}}
+                           // CHECK-EXPLICIT-THIS-PARAMETER: fix-it:"{{.*}}":{[[@LINE-3]]:10-[[@LINE-3]]:10}:"__attribute__((format(scanf, 2, 0)))"
+                           // expected-note at -2 {{'scanf' format function}}
+    }
+#endif
+
+    C1(const int n, const char *out) //#C1_C1a
+    {
+        va_list args;
+        fn1(n, out, args); // expected-warning@#C1_C1a {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'C1'}}
+                           // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:5-[[@LINE-4]]:5}:"__attribute__((format(printf, 3, 0)))"
+                           // expected-note at -2 {{'printf' format function}}
+    }
+
+    C1(const char *out, ... /* args */) // #C1_C1b
+    {
+        va_list args;
+        vprintf(out, args); // expected-warning@#C1_C1b {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'C1'}}
+                            // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:5-[[@LINE-4]]:5}:"__attribute__((format(printf, 2, 3)))"
+                            // expected-note at -2 {{'printf' format function}}
+    }
+
+    ~C1() // #d_C1
+    {
+        const char *out;
+        va_list args;
+        vprintf(out, args);
+    }
+};
+
+// TODO: implement for templates
+template <int N>
+void func(char (&str)[N], ... /* args */) // #func
+{
+    va_list args;
+    vprintf(str, args);
+}



More information about the cfe-commits mailing list