[clang] [clang] Catch missing format attributes (PR #70024)
Budimir Aranđelović via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 9 03:00:33 PDT 2024
https://github.com/budimirarandjelovicsyrmia updated https://github.com/llvm/llvm-project/pull/70024
>From a67db17140dca5f0b7e4099c08f2753eda7dd4f2 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 | 3 +
clang/include/clang/Basic/DiagnosticGroups.td | 1 -
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/include/clang/Sema/Attr.h | 7 +
clang/include/clang/Sema/Sema.h | 3 +
clang/lib/Sema/SemaDecl.cpp | 2 +
clang/lib/Sema/SemaDeclAttr.cpp | 192 ++++++++-
clang/test/Sema/attr-format-missing.c | 390 ++++++++++++++++++
clang/test/Sema/attr-format-missing.cpp | 174 ++++++++
9 files changed, 772 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 9ed3ff4507671..63be1b80b983a 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -626,6 +626,9 @@ Improvements to Clang's diagnostics
used rather than when they are needed for constant evaluation or when code is generated for them.
The check is now stricter to prevent crashes for some unsupported declarations (Fixes #GH95495).
+- Clang now diagnoses missing format attributes for non-template functions and
+ class/struct/union members. Fixes #GH60718
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 1c4f305fb5d00..4836b499fbbee 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -505,7 +505,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 46ad359751d7d..e4fd8904b9f4d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1021,6 +1021,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/Attr.h b/clang/include/clang/Sema/Attr.h
index 3f0b10212789a..37c124ca7b454 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 2e7af0f691cbb..80f7c27e33dfd 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3854,6 +3854,9 @@ class Sema final : public SemaBase {
enum class RetainOwnershipKind { NS, CF, OS };
+ void DiagnoseMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
+ std::vector<FormatAttr *> GetMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
+
UuidAttr *mergeUuidAttr(Decl *D, const AttributeCommonInfo &CI,
StringRef UuidAsWritten, MSGuidDecl *GuidDecl);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 029ccf944c513..580ae510e384f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16253,6 +16253,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
}
}
+ DiagnoseMissingFormatAttributes(Body, 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 99b400307d644..11d4a7bc0f60e 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3547,7 +3547,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;
@@ -3660,7 +3660,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();
@@ -5361,6 +5361,194 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
}
+// This function is called only if function call is not inside template body.
+// TODO: Add call for function calls inside template body.
+// Emit warnings if parent function misses format attributes.
+void Sema::DiagnoseMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl)
+{
+ // Get missing format attributes
+ std::vector<FormatAttr *> MissingFormatAttributes = GetMissingFormatAttributes(Body, FDecl);
+ if (MissingFormatAttributes.empty())
+ return;
+
+ // Check if there are more than one format type found. In that case do not emit diagnostic.
+ const FormatAttr *FirstAttr = MissingFormatAttributes[0];
+ if (llvm::any_of(MissingFormatAttributes, [&](const FormatAttr *Attr) {
+ return FirstAttr->getType() != Attr->getType();
+ }))
+ return;
+
+ for (const FormatAttr *FA : MissingFormatAttributes) {
+ // 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;
+
+ // Emit diagnostic
+ SourceLocation Loc = FDecl->getLocation();
+ Diag(Loc, diag::warn_missing_format_attribute)
+ << FA->getType() << FDecl
+ << FixItHint::CreateInsertion(Loc,
+ (llvm::Twine("__attribute__((format(") +
+ FA->getType()->getName() + ", " +
+ llvm::Twine(FA->getFormatIdx()) + ", " +
+ llvm::Twine(FA->getFirstArg()) + ")))")
+ .str());
+ }
+}
+
+// Returns vector of format attributes. There are no two attributes with same arguments in returning vector.
+// There can be attributes that effectivelly only store information about format type.
+std::vector<FormatAttr *> Sema::GetMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl)
+{
+ assert(Body && FDecl);
+
+ unsigned int FunctionFormatArgumentIndexOffset =
+ checkIfMethodHasImplicitObjectParameter(FDecl) ? 2 : 1;
+
+ std::vector<FormatAttr *> MissingAttributes;
+
+ // Iterate over body statements.
+ for (auto *Child : Body->children()) {
+ // If child statement is compound statement, recursively get missing attributes.
+ if (dyn_cast_or_null<CompoundStmt>(Child)) {
+ std::vector<FormatAttr *> CompoundStmtMissingAttributes = GetMissingFormatAttributes(Child, FDecl);
+
+ // If there are already missing attributes with same arguments, do not add duplicates.
+ for (FormatAttr *FA : CompoundStmtMissingAttributes) {
+ if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
+ return FA->getType() == Attr->getType() && FA->getFormatIdx() == Attr->getFormatIdx() && FA->getFirstArg() == Attr->getFirstArg();
+ }))
+ MissingAttributes.push_back(FA);
+ }
+
+ continue;
+ }
+
+ ValueStmt *VS = dyn_cast_or_null<ValueStmt>(Child);
+ if (!VS) continue;
+ Expr *TheExpr = VS->getExprStmt();
+ if (!TheExpr) continue;
+ CallExpr *TheCall = dyn_cast_or_null<CallExpr>(TheExpr);
+ if (!TheCall) continue;
+ const FunctionDecl *ChildFunction = dyn_cast_or_null<FunctionDecl>(TheCall->getCalleeDecl());
+ if (!ChildFunction) continue;
+
+ Expr** Args = TheCall->getArgs();
+ unsigned int NumArgs = TheCall->getNumArgs();
+
+ // If child expression is function, check if it is format function.
+ // If it is, check if parent function misses format attributes.
+
+ // If child 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 ChildFunctionFormatArgumentIndexOffset =
+ checkIfMethodHasImplicitObjectParameter(ChildFunction) ? 2 : 1;
+
+ // Check if function has format attribute with forwarded format string.
+ IdentifierInfo *AttrType;
+ const ParmVarDecl *FormatArg;
+ if (!llvm::any_of(ChildFunction->specific_attrs<FormatAttr>(),
+ [&](const FormatAttr *Attr) {
+ AttrType = Attr->getType();
+
+ int OffsetFormatIndex =
+ Attr->getFormatIdx() - ChildFunctionFormatArgumentIndexOffset;
+ if (OffsetFormatIndex < 0 ||
+ (unsigned)OffsetFormatIndex >= NumArgs)
+ return false;
+
+ const auto *FormatArgExpr = dyn_cast<DeclRefExpr>(
+ Args[OffsetFormatIndex]->IgnoreParenCasts());
+ if (!FormatArgExpr)
+ return false;
+
+ FormatArg = dyn_cast_or_null<ParmVarDecl>(
+ FormatArgExpr->getReferencedDeclOfCallee());
+ if (!FormatArg)
+ return false;
+
+ return true;
+ })) {
+ MissingAttributes.push_back(FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+ continue;
+ }
+
+ // Do not add in a vector format attributes whose type is different than parent function attribute type.
+ if (llvm::any_of(FDecl->specific_attrs<FormatAttr>(), [&](const FormatAttr *FunctionAttr) {
+ return AttrType != FunctionAttr->getType();
+ }))
+ continue;
+
+ // Check if format string argument is parent function parameter.
+ unsigned int StringIndex = 0;
+ if (!llvm::any_of(FDecl->parameters(),
+ [&](const ParmVarDecl *Param) {
+ if (Param != FormatArg)
+ return false;
+
+ StringIndex = Param->getFunctionScopeIndex() +
+ FunctionFormatArgumentIndexOffset;
+
+ return true;
+ })) {
+ MissingAttributes.push_back(FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+ continue;
+ }
+
+ unsigned NumOfParentFunctionParams = FDecl->getNumParams();
+
+ // Compare parent and calling function format attribute arguments (archetype
+ // and format string).
+ if (llvm::any_of(
+ FDecl->specific_attrs<FormatAttr>(),
+ [&](const FormatAttr *Attr) {
+ if (Attr->getType() != AttrType)
+ return false;
+ int OffsetFormatIndex =
+ Attr->getFormatIdx() - FunctionFormatArgumentIndexOffset;
+
+ if (OffsetFormatIndex < 0 ||
+ (unsigned)OffsetFormatIndex >= NumOfParentFunctionParams)
+ return false;
+
+ if (FDecl->parameters()[OffsetFormatIndex] != FormatArg)
+ return false;
+
+ return true;
+ })) {
+ MissingAttributes.push_back(FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+ continue;
+ }
+
+ // Get first argument index
+ unsigned FirstToCheck = [&]() -> unsigned {
+ if (!FDecl->isVariadic())
+ return 0;
+ const auto *FirstToCheckArg =
+ dyn_cast<DeclRefExpr>(Args[NumArgs - 1]->IgnoreParenCasts());
+ if (!FirstToCheckArg)
+ return 0;
+
+ if (FirstToCheckArg->getType().getCanonicalType() !=
+ Context.getBuiltinVaListType().getCanonicalType())
+ return 0;
+ return NumOfParentFunctionParams + FunctionFormatArgumentIndexOffset;
+ }();
+
+ // If there are already attributes which arguments matches arguments detected in this iteration, do not add new attribute as it would be duplicate.
+ if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
+ return Attr->getType() == AttrType && Attr->getFormatIdx() == StringIndex && Attr->getFirstArg() == FirstToCheck;
+ }))
+ MissingAttributes.push_back(FormatAttr::CreateImplicit(getASTContext(), AttrType, StringIndex, FirstToCheck));
+ }
+
+ return MissingAttributes;
+}
+
//===----------------------------------------------------------------------===//
// 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..f256b85c0acd4
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.c
@@ -0,0 +1,390 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,c_diagnostics -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s --check-prefixes=CHECK,C-CHECK
+// RUN: %clang_cc1 -fsyntax-only -x c++ -verify=expected,cpp_diagnostics -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -x c++ -verify=expected,cpp_diagnostics -std=c++2b -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -x c++ -verify=expected,cpp_diagnostics -std=c++23 -Wmissing-format-attribute %s
+// RUN: not %clang_cc1 -fsyntax-only -x c++ -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s --check-prefixes=CHECK
+
+#ifndef __cplusplus
+typedef unsigned short char16_t;
+typedef unsigned int 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 *ch, size_t, const char *, va_list); // #vsnprintf
+
+__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); // expected-no-warning@#f1
+}
+
+__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)))"
+}
+
+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)))"
+}
+
+void f4(char* out, ... /* args */) // #f4
+{
+ va_list args;
+ vprintf("test", args); // expected-no-warning@#f4
+
+ const char *ch;
+ vprintf(ch, args); // expected-no-warning@#f4
+}
+
+void f5(va_list args) // #f5
+{
+ char *ch;
+ vscanf(ch, args); // expected-no-warning@#f5
+}
+
+void f6(char *out, va_list args) // #f6
+{
+ char *ch;
+ vprintf(ch, args); // expected-no-warning@#f6
+ vprintf("test", args); // expected-no-warning@#f6
+ 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)))"
+}
+
+void f7(const char *out, ... /* args */) // #f7
+{
+ va_list args;
+
+ vscanf(out, &args[0]); // 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, 0)))"
+}
+
+void f8(const char *out, ... /* args */) // #f8
+{
+ va_list args;
+
+ vscanf(out, &args[0]); // expected-no-warning@#f8
+ vprintf(out, &args[0]); // expected-no-warning@#f8
+}
+
+void f9(const char out[], ... /* args */) // #f9
+{
+ va_list args;
+ char *ch;
+ vprintf(ch, args); // expected-no-warning
+ 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)))"
+}
+
+void f10(const wchar_t *out, ... /* args */) // #f10
+{
+ va_list args;
+ vscanf(out, args);
+#if __SIZEOF_WCHAR_T__ == 4
+ // c_diagnostics-warning at -2 {{incompatible pointer types passing 'const wchar_t *' (aka 'const int *') to parameter of type 'const char *'}}
+#else
+ // c_diagnostics-warning at -4 {{incompatible pointer types passing 'const wchar_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}}
+#endif
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f10 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}}
+ // cpp_diagnostics-error at -8 {{no matching function for call to 'vscanf'}}
+ // cpp_diagnostics-note@#vscanf {{candidate function not viable: no known conversion from 'const wchar_t *' to 'const char *' for 1st argument}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-13]]:6-[[@LINE-13]]:6}:"__attribute__((format(scanf, 1, 2)))"
+}
+
+void f11(const wchar_t *out, ... /* args */) // #f11
+{
+ va_list args;
+ vscanf((const char *) 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, 2)))"
+}
+
+void f12(const wchar_t *out, ... /* args */) // #f12
+{
+ va_list args;
+ vscanf((char *) out, args); // expected-warning@#f12 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f12'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 2)))"
+}
+
+void f13(const wchar_t *out, ... /* args */) // #f13
+{
+ va_list args;
+ vscanf(out, args);
+#if __SIZEOF_WCHAR_T__ == 4
+ // c_diagnostics-warning at -2 {{incompatible pointer types passing 'const wchar_t *' (aka 'const int *') to parameter of type 'const char *'}}
+#else
+ // c_diagnostics-warning at -4 {{incompatible pointer types passing 'const wchar_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}}
+#endif
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // cpp_diagnostics-error at -7 {{no matching function for call to 'vscanf'}}
+ // cpp_diagnostics-note@#vscanf {{candidate function not viable: no known conversion from 'const wchar_t *' to 'const char *' for 1st argument}}
+ // expected-warning@#f13 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-13]]:6-[[@LINE-13]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ vscanf((const char *) out, args);
+ vscanf((char *) out, args);
+}
+
+void f14(const char *out) // #f14
+{
+ va_list args;
+ vscanf(out, args); // expected-warning@#f14 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 0)))"
+}
+
+void f15(const char16_t *out, ... /* args */) // #f15
+{
+ va_list args;
+ vscanf(out, args); // c_diagnostics-warning {{incompatible pointer types passing 'const char16_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}}
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f15 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ // cpp_diagnostics-error at -4 {{no matching function for call to 'vscanf'}}
+ // cpp_diagnostics-note@#vscanf {{candidate function not viable: no known conversion from 'const char16_t *' to 'const char *' for 1st argument}}
+}
+
+void f16(const char32_t *out, ... /* args */) // #f16
+{
+ va_list args;
+ vscanf(out, args); // c_diagnostics-warning {{incompatible pointer types passing 'const char32_t *' (aka 'const unsigned int *') to parameter of type 'const char *'}}
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f16 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ // cpp_diagnostics-error at -4 {{no matching function for call to 'vscanf'}}
+ // cpp_diagnostics-note@#vscanf {{candidate function not viable: no known conversion from 'const char32_t *' to 'const char *' for 1st argument}}
+}
+
+void f17(const unsigned char *out, ... /* args */) // #f17
+{
+ va_list args;
+ vscanf(out, args); // c_diagnostics-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}}
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f17 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ // cpp_diagnostics-error at -4 {{no matching function for call to 'vscanf'}}
+ // cpp_diagnostics-note@#vprintf {{candidate function not viable: no known conversion from 'const unsigned char *' to 'const char *' for 1st argument}}
+}
+
+void f18(const unsigned char *out, ... /* args */) // #f18
+{
+ va_list args;
+ vscanf((const char *) out, args); // expected-warning@#f18 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f18'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 2)))"
+}
+
+void f19(const unsigned char *out, ... /* args */) // #f19
+{
+ va_list args;
+ vscanf((char *) out, args); // expected-warning@#f19 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f19'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 2)))"
+}
+
+__attribute__((format(printf, 1, 2)))
+void f20(const unsigned char *out, ... /* args */) // #f20
+{
+ va_list args;
+ vprintf(out, args); // c_diagnostics-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}}
+ // c_diagnostics-note@#vprintf {{passing argument to parameter here}}
+ // cpp_diagnostics-error at -2 {{no matching function for call to 'vprintf'}}
+ // cpp_diagnostics-note@#vscanf {{candidate function not viable: no known conversion from 'const unsigned char *' to 'const char *' for 1st argument}}
+ vscanf((const char *) out, args); // expected-no-warning
+ vprintf((const char *) out, args); // expected-no-warning
+ vscanf((char *) out, args); // expected-no-warning
+ vprintf((char *) out, args); // expected-no-warning
+}
+
+void f21(signed char *out, ... /* args */) // #f21
+{
+ va_list args;
+ vscanf(out, args); // c_diagnostics-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}} \
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f21 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f21'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ // cpp_diagnostics-error at -4 {{no matching function for call to 'vscanf'}}
+ // cpp_diagnostics-note@#vscanf {{candidate function not viable: no known conversion from 'signed char *' to 'const char *' for 1st argument}}
+}
+
+void f22(signed char *out, ... /* args */) // #f22
+{
+ va_list args;
+ vscanf((const char *) out, args); // expected-warning@#f22 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f22'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 2)))"
+}
+
+void f23(signed char *out, ... /* args */) // #f23
+{
+ va_list args;
+ vprintf((char *) out, args); // expected-warning@#f23 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f23'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 2)))"
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f24(signed char *out, ... /* args */) // #f24
+{
+ va_list args;
+ vprintf((const char *) out, args); // expected-no-warning@#f24
+ vprintf((char *) out, args); // expected-no-warning@#f24
+}
+
+__attribute__((format(printf, 1, 2)))
+void f25(unsigned char out[], ... /* args */) // #f25
+{
+ va_list args;
+ vscanf((const char *) out, args); // expected-no-warning@#f25
+ vscanf((char *) out, args); // expected-no-warning@#f25
+}
+
+void f26(char* out) // #f26
+{
+ va_list args;
+ const char* ch;
+ vsprintf(out, ch, args); // expected-no-warning@#f26
+ vprintf(out, args); // expected-warning@#f26 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f26'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
+
+void f27(const char *out, ... /* args */) // #f27
+{
+ int a;
+ printf(out, a); // expected-warning@#f27 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f27'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
+
+void f28(const char *out, ... /* args */) // #f28
+{
+ printf(out, 1); // expected-warning@#f28 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f28'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:6-[[@LINE-3]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
+
+__attribute__((format(printf, 1, 2)))
+void f29(const char *out, ... /* args */) // #f29
+{
+ int a;
+ printf(out, a); // expected-no-warning@#f29
+}
+
+__attribute__((format(printf, 1, 2)))
+void f30(const char *out, ... /* args */) // #f30
+{
+ printf(out, 1); // expected-no-warning@#f30
+}
+
+__attribute__((format(printf, 1, 2)))
+void f31(const char *out, ... /* args */) // #f31
+{
+ int a;
+ printf(out, a); // expected-no-warning@#f31
+ printf(out, 1); // expected-no-warning@#f31
+}
+
+void f32(char *out, ... /* args */) // #f32
+{
+ va_list args;
+ scanf(out, args); // expected-no-warning@#f32
+ {
+ printf(out, args); // expected-no-warning@#f32
+ }
+}
+
+void f33(char *out, va_list args) // #f33
+{
+ {
+ scanf(out, args); // expected-no-warning@#f33
+ printf(out, args); // expected-no-warning@#f33
+ }
+}
+
+// expected-warning@#f34 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f34'}}
+// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))"
+void f34(char *out, ... /* args */) // #f34
+{
+ va_list args;
+ scanf(out, args); // expected-no-warning@#f34
+ {
+ scanf(out, args); // expected-no-warning@#f34
+ }
+}
+
+void f35(char* ch, const char *out, ... /* args */) // #f35
+{
+ va_list args;
+ printf(ch, args); // expected-warning@#f35 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f35}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 3)))"
+ int a;
+ printf(out, a); // expected-warning@#f35 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f35'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:6-[[@LINE-7]]:6}:"__attribute__((format(printf, 2, 0)))"
+ printf(out, 1); // no warning because first command above emitted same warning with same fix-it text
+ printf(out, args); // expected-warning@#f35 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f35'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-10]]:6-[[@LINE-10]]:6}:"__attribute__((format(printf, 2, 3)))"
+}
+
+typedef va_list tdVaList;
+typedef int tdInt;
+
+void f36(const char *out, ... /* args */) // #f36
+{
+ tdVaList args;
+ printf(out, args); // expected-warning@#f36 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f36'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 2)))"
+}
+
+void f37(const char *out, ... /* args */) // #f37
+{
+ tdInt a;
+ scanf(out, a); // expected-warning@#f37 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f37'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(scanf, 1, 0)))"
+}
+
+void f38(const char *out, tdVaList args) // #f38
+{
+ scanf(out, args); // expected-warning@#f38 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f38'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:6-[[@LINE-3]]:6}:"__attribute__((format(scanf, 1, 0)))"
+}
+
+void f39(const char *out, tdVaList args) // #f39
+{
+ tdInt a;
+ printf(out, a); // expected-warning@#f39 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f39'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
+
+void f40(char *out, ... /* args */) // #f40
+{
+ va_list args;
+ char *ch;
+ vscanf(ch, args); // expected-no-warning@#f40
+ vprintf(out, args); // expected-no-warning@#f40
+}
+
+void f41(char *out, ... /* args */) // #f41
+{
+ va_list args;
+ char *ch;
+ vscanf("%s", ch); // expected-no-warning@#f41
+ vprintf(out, args); // expected-no-warning@#f41
+}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
new file mode 100644
index 0000000000000..4c5758d6c9d84
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -0,0 +1,174 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,beforeCxx2b -Wmissing-format-attribute %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2b -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
+
+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 *ch, size_t, const char *, va_list); // #vsnprintf
+
+void f1(const std::string &str, ... /* args */) // #f1
+{
+ va_list args;
+ vscanf(str.c_str(), args); // expected-no-warning@#f1
+}
+
+__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); // expected-no-warning@#f3
+}
+
+__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;
+ vprintf((const char *)str.c_str(), args); // expected-no-warning@#f5
+}
+
+__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;
+ vprintf((const char *) std::wstring(str).c_str(), args); // expected-no-warning@#f7
+}
+
+__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)))"
+ }
+
+ __attribute__((format(scanf, 2, 0)))
+ void fn2(const char *out, va_list args); // #S1_fn2
+
+ void fn3(const char *out, ... /* args */);
+
+ void fn4(this S1& expliciteThis, const char *out, va_list args) // #S1_fn4
+ {
+ expliciteThis.fn2(out, args); // beforeCxx2b-error@#S1_fn4 {{explicit object parameters are incompatible with C++ standards before C++2b}}
+ // expected-warning@#S1_fn4 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn4'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(scanf, 2, 0)))"
+ }
+};
+
+void S1::fn3(const char *out, ... /* args */) // #S1_fn3
+{
+ 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-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(scanf, 2, 3)))"
+}
+
+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)))"
+ }
+
+ void fn3(this U1&, const char *out) // #U1_fn3
+ {
+ va_list args;
+ printf(out, args); // beforeCxx2b-error@#U1_fn3 {{explicit object parameters are incompatible with C++ standards before C++2b}}
+ // expected-warning@#U1_fn3 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:10-[[@LINE-5]]:10}:"__attribute__((format(printf, 2, 0)))"
+ }
+};
+
+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)))"
+ }
+
+ void fn3(this const C1&, const char *out, va_list args) // #C1_fn3
+ {
+ scanf(out, args); // beforeCxx2b-error@#C1_fn3 {{explicit object parameters are incompatible with C++ standards before C++2b}}
+ // expected-warning@#C1_fn3 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn3'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:10-[[@LINE-4]]:10}:"__attribute__((format(scanf, 2, 0)))"
+ }
+
+ 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)))"
+ }
+
+ C1(const char *out, ... /* args */) // #C1_C1b
+ {
+ va_list args;
+ printf(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)))"
+ }
+
+ ~C1() // #d_C1
+ {
+ const char *out;
+ va_list args;
+ vprintf(out, args); // expected-no-warning@#d_C1
+ }
+};
+
+// TODO: implement for templates
+template <int N>
+void func(char (&str)[N], ... /* args */) // #func
+{
+ va_list args;
+ vprintf(str, args); // expected-no-warning@#func
+}
More information about the cfe-commits
mailing list