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

Budimir Aranđelović via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 7 07:15:56 PST 2023


https://github.com/budimirarandjelovicsyrmia updated https://github.com/llvm/llvm-project/pull/70024

>From 795a2db56ac2c2cae61c8f3474bb907810595aef Mon Sep 17 00:00:00 2001
From: budimirarandjelovicsyrmia <budimir.arandjelovic at syrmia.com>
Date: Fri, 13 Oct 2023 14:45:15 +0200
Subject: [PATCH] [clang] Catch missing format attributes

---
 clang/docs/ReleaseNotes.rst                   |   4 +-
 clang/include/clang/Basic/DiagnosticGroups.td |   1 -
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/include/clang/Sema/Sema.h               |   4 +
 clang/lib/Sema/SemaChecking.cpp               |   4 +-
 clang/lib/Sema/SemaDeclAttr.cpp               |  67 ++++++++
 clang/test/Sema/attr-format-missing.c         | 143 ++++++++++++++++++
 clang/test/Sema/attr-format-missing.cpp       | 132 ++++++++++++++++
 8 files changed, 355 insertions(+), 3 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 9cc2a72f4c864..1ca055f65f211 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -65,7 +65,9 @@ Improvements to Clang's diagnostics
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - We now generate a diagnostic for signed integer overflow due to unary minus
   in a non-constant expression context. This fixes
-  `Issue 31643 <https://github.com/llvm/llvm-project/issues/31643>`_
+  `Issue 31643 <https://github.com/llvm/llvm-project/issues/31643>`_.
+- We now generate a diagnostic for missing format attributes
+  `Issue 60718 <https://github.com/llvm/llvm-project/issues/60718>`_.
 
 Non-comprehensive list of changes in this release
 -------------------------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 17fdcffa2d427..114cbbc2e82b8 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -482,7 +482,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 : 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 6d6f474f6dcda..964166b70c2ae 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -936,6 +936,9 @@ def err_opencl_invalid_param : Error<
 def err_opencl_invalid_return : Error<
   "declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
 def warn_enum_value_overflow : Warning<"overflow in enumeration value">;
+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_pragma_options_align_reset_failed : Warning<
   "#pragma options align=reset failed: %0">,
   InGroup<IgnoredPragmas>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 741c2503127af..064506e709603 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10615,6 +10615,10 @@ class Sema final {
     ChangedStateAtExit
   };
 
+  void DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl,
+                                       ArrayRef<const Expr *> Args,
+                                       SourceLocation Loc);
+
   void DiagnoseNonDefaultPragmaAlignPack(PragmaAlignPackDiagnoseKind Kind,
                                          SourceLocation IncludeLoc);
   void DiagnoseUnterminatedPragmaAlignPack();
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 4602284309491..d3ac6cb519c56 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -6014,8 +6014,10 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
     }
   }
 
-  if (FD)
+  if (FD) {
     diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc);
+    DiagnoseMissingFormatAttributes(FD, Args, Range.getBegin());
+  }
 }
 
 /// CheckConstructorCall - Check a constructor call for correctness and safety
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 1a0bfb3d91bcc..aca0da90e25cb 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6849,6 +6849,73 @@ static void handleSwiftAsyncAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
     checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr);
 }
 
+// Warn if parent function misses format attribute. Parent function misses
+// format attribute if there is an argument format string forwarded to calling
+// function with format attribute, parent function has a parameter which type
+// is either string or pointer to char and parent function format attribute
+// type does not match with calling function format attribute type.
+void Sema::DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl,
+                                           ArrayRef<const Expr *> Args,
+                                           SourceLocation Loc) {
+  assert(FDecl);
+
+  // Check if function has format attribute with forwarded format string.
+  IdentifierInfo *AttrType;
+  if (!llvm::any_of(
+          FDecl->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
+            if (!Args[Attr->getFirstArg()]->getReferencedDeclOfCallee())
+              return false;
+
+            AttrType = Attr->getType();
+            return true;
+          }))
+    return;
+
+  const FunctionDecl *ParentFuncDecl = getCurFunctionDecl();
+  if (!ParentFuncDecl)
+    return;
+
+  // Check if parent function has string or pointer to char parameter.
+  unsigned int StringIndex = 0;
+  if (!llvm::any_of(
+          ParentFuncDecl->parameters(), [&](const ParmVarDecl *Param) {
+            StringIndex = Param->getFunctionScopeIndex() + 1;
+            QualType Ty = Param->getType();
+            if (isNSStringType(Ty, Context, true))
+              return true;
+            if (isCFStringType(Ty, Context))
+              return true;
+            if (Ty->isPointerType() &&
+                Ty->castAs<PointerType>()
+                    ->getPointeeType()
+                    ->isSpecificBuiltinType(BuiltinType::Char_S))
+              return true;
+            return false;
+          }))
+    return;
+
+  // Check if there is parent function format attribute which type matches
+  // calling function format attribute type.
+  if (llvm::any_of(
+          ParentFuncDecl->specific_attrs<FormatAttr>(),
+          [&](const FormatAttr *Attr) { return Attr->getType() == AttrType; }))
+    return;
+
+  unsigned int FirstToCheck =
+      ParentFuncDecl->isVariadic() ? (ParentFuncDecl->getNumParams() + 1) : 0;
+
+  // Diagnose missing format attributes
+  std::string InsertionText;
+  llvm::raw_string_ostream OS(InsertionText);
+  OS << "__attribute__((format(" << AttrType->getName() << ", "
+     << std::to_string(StringIndex) << ", " << std::to_string(FirstToCheck)
+     << ")))";
+  SourceLocation ParentFuncLoc = ParentFuncDecl->getLocation();
+  Diag(ParentFuncLoc, diag::warn_missing_format_attribute)
+      << AttrType << ParentFuncDecl
+      << FixItHint::CreateInsertion(ParentFuncLoc, InsertionText);
+}
+
 //===----------------------------------------------------------------------===//
 // 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 0000000000000..450bc4e9861fc
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.c
@@ -0,0 +1,143 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <uchar.h>
+#include <wchar.h>
+
+__attribute__((__format__ (__scanf__, 1, 4)))
+void f1(char *out, const size_t len, const char *format, ... /* args */)
+{
+    va_list args;
+    vsnprintf(out, len, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
+                                       // CHECK-FIXES: __attribute__((format(printf, 1, 4)))
+}
+
+void f2(char *out, va_list args)
+{
+    vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f2'}}
+                        // CHECK-FIXES: __attribute__((format(printf, 1, 0)))
+    vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}}
+                       // CHECK-FIXES: __attribute__((format(scanf, 1, 0)))
+}
+
+void f3(char* out, ... /* args */)
+{
+    va_list args;
+    vprintf("test", args); // no warning
+}
+
+void f4(char *out, ... /* args */)
+{
+    const char *ch;
+    va_list args;
+    vscanf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f4'}}
+                      // CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
+}
+
+void f5(va_list args)
+{
+    char *ch;
+    vscanf(ch, args); // no warning
+}
+
+void f6(char *out, va_list args)
+{
+    char *ch;
+    vscanf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f6'}}
+                      // CHECK-FIXES: __attribute__((format(scanf, 1, 0)))
+}
+
+void f7(const char *out, ... /* args */)
+{
+    va_list args;
+
+    vscanf(out, &args[0]); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f7'}}
+                           // CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
+    vprintf(out, &args[0]); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f7'}}
+                            // CHECK-FIXES: __attribute__((format(printf, 1, 2)))
+}
+
+void f8(const char out[], ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f8'}}
+                       // CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
+    char *ch;
+    vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f8'}}
+                       // CHECK-FIXES: __attribute__((format(printf, 1, 2)))
+}
+
+void f9(const wchar_t *out, ... /* args */)
+{
+    va_list args;
+    vprintf(out, args); // expected-warning {{incompatible pointer types passing 'const wchar_t *' (aka 'const int *') to parameter of type 'const char *'}}
+    vscanf((const char *) out, args); // no warning
+    vscanf((char *) out, args); // no warning
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f10(const wchar_t *out, ... /* args */);
+
+void f11(const char16_t *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-warning {{incompatible pointer types passing 'const char16_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}}
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f12(const char16_t *out, ... /* args */);
+
+void f13(const char32_t *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-warning {{incompatible pointer types passing 'const char32_t *' (aka 'const unsigned int *') to parameter of type 'const char *'}}
+}
+
+__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}}
+void f14(const char32_t *out, ... /* args */);
+
+void f15(const unsigned char *out, ... /* args */)
+{
+    va_list args;
+    vprintf(out, args); // expected-warning {{passing 'const unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
+    vscanf((const char *) out, args); // no warning
+    vscanf((char *) out, args); // no warning
+}
+
+__attribute__((format(printf, 1, 2)))
+void f16(const unsigned char *out, ... /* args */)
+{
+    va_list args;
+    vprintf(out, args); // expected-warning {{passing 'const unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
+    vscanf((const char *) out, args); // no warning
+    vprintf((const char *) out, args); // no warning
+    vscanf((char *) out, args); // no warning
+    vprintf((char *) out, args); // no warning
+}
+
+void f17(signed char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-warning {{passing 'signed char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
+    vscanf((const char *) out, args); // no warning
+    vprintf((char *) out, args); // no warning
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f18(signed char *out, ... /* args */)
+{
+    va_list args;
+    vprintf(out, args); // expected-warning {{passing 'signed char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
+    vscanf((const char *) out, args); // no warning
+    vprintf((const char *) out, args); // no warning
+    vscanf((char *) out, args); // no warning
+    vprintf((char *) out, args); // no warning
+}
+
+__attribute__((format(printf, 1, 2)))
+void f19(unsigned char out[], ... /* args */)
+{
+    va_list args;
+    vprintf(out, args); // expected-warning {{passing 'unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
+}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
new file mode 100644
index 0000000000000..a934d68cfaa90
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -0,0 +1,132 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
+
+#include <iostream>
+#include <cstdarg>
+
+void f1(const std::string &str, ... /* args */)
+{
+    va_list args;
+    vscanf(str.c_str(), args); // no warning
+    vprintf(str.c_str(), args); // no warning
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error: {{format argument not a string type}}
+void f2(const std::string &str, ... /* args */);
+
+void f3(std::string_view str, ... /* args */)
+{
+    va_list args;
+    vscanf(std::string(str).c_str(), args); // no warning
+    vprintf(std::string(str).c_str(), args); // no warning
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f4(std::string_view str, ... /* args */);
+
+void f5(const std::wstring &str, ... /* args */)
+{
+    va_list args;
+    vscanf((const char *)str.c_str(), args); // no warning
+    vprintf((const char *)str.c_str(), args); // no warning
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f6(const std::wstring &str, ... /* args */);
+
+void f7(std::wstring_view str, ... /* args */)
+{
+    va_list args;
+    vscanf((const char *) std::wstring(str).c_str(), args); // no warning
+    vprintf((const char *) std::wstring(str).c_str(), args); // no warning
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f8(std::wstring_view str, ... /* args */);
+
+void f9(const wchar_t *out, ... /* args */)
+{
+    va_list args;
+    vprintf(out, args); // expected-error {{no matching function for call to 'vprintf'}}
+    vscanf((const char *) out, args); // no warning
+    vscanf((char *) out, args); // no warning
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f10(const wchar_t *out, ... /* args */);
+
+void f11(const char16_t *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}}
+}
+
+__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
+void f12(const char16_t *out, ... /* args */);
+
+void f13(const char32_t *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}}
+}
+
+__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}}
+void f14(const char32_t *out, ... /* args */);
+
+void f15(const char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f16(const char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // no warning
+}
+
+void f17(const unsigned char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}}
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f18(const unsigned char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}}
+}
+
+void f19(const signed char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}}
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f20(const signed char *out, ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}}
+}
+
+void f21(const char out[], ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f21'}}
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f22(const char out[], ... /* args */)
+{
+    va_list args;
+    vscanf(out, args); // no warning
+}
+
+template <int N>
+void func(char (&str)[N], ...)
+{
+    va_list args;
+    vprintf(str, args); // no warning
+}



More information about the cfe-commits mailing list