[clang] [clang] Catch missing format attributes (PR #70024)
Budimir Aranđelović via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 24 01:39:23 PDT 2024
https://github.com/budimirarandjelovicsyrmia updated https://github.com/llvm/llvm-project/pull/70024
>From 97cc6f1e97bb27422df8fa0ecce2f4ad1dfcacff 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 | 4 +
clang/lib/Sema/SemaChecking.cpp | 4 +-
clang/lib/Sema/SemaDeclAttr.cpp | 112 ++++++-
clang/test/Sema/attr-format-missing.c | 304 ++++++++++++++++++
clang/test/Sema/attr-format-missing.cpp | 178 ++++++++++
9 files changed, 612 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 9c8f8c4a4fbaf..31e31ab4ac262 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -612,6 +612,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 9b37d4bd3205b..4e075eb7c062a 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 25a87078a5709..f92aa7be8effb 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 e43e5f465361d..93906437c6626 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3775,6 +3775,10 @@ class Sema final : public SemaBase {
enum class RetainOwnershipKind { NS, CF, OS };
+ void DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl,
+ ArrayRef<const Expr *> Args,
+ SourceLocation Loc);
+
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 87988519e7691..63697fab64ee1 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -4119,8 +4119,10 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
}
}
- if (FD)
+ if (FD) {
diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc);
+ DiagnoseMissingFormatAttributes(FD, Args, Range.getBegin());
+ }
}
void Sema::CheckConstrainedAuto(const AutoType *AutoT, SourceLocation Loc) {
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index ce6b5b1ff6f93..588c33accccae 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3533,7 +3533,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;
@@ -3646,7 +3646,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();
@@ -5347,6 +5347,114 @@ 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.
+// Check if parent function misses format attribute. If misses, emit warning.
+void Sema::DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl,
+ ArrayRef<const Expr *> Args,
+ SourceLocation Loc) {
+ assert(FDecl);
+
+ const FunctionDecl *ParentFuncDecl = getCurFunctionDecl();
+ if (!ParentFuncDecl)
+ return;
+
+ // If function is a member of struct/union/class and has implicit object
+ // parameter, format attribute argument indexing starts from 2. Otherwise, it
+ // starts from 1.
+ unsigned int FormatArgumentIndexOffset =
+ checkIfMethodHasImplicitObjectParameter(FDecl) ? 2 : 1;
+ unsigned int ParentFunctionFormatArgumentIndexOffset =
+ checkIfMethodHasImplicitObjectParameter(ParentFuncDecl) ? 2 : 1;
+
+ // Check if function has format attribute with forwarded format string.
+ IdentifierInfo *AttrType;
+ const ParmVarDecl *FormatArg;
+ if (!llvm::any_of(FDecl->specific_attrs<FormatAttr>(),
+ [&](const FormatAttr *Attr) {
+ int OffsetFormatIndex =
+ Attr->getFormatIdx() - FormatArgumentIndexOffset;
+ if (OffsetFormatIndex < 0 ||
+ (unsigned)OffsetFormatIndex >= Args.size())
+ 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;
+
+ AttrType = Attr->getType();
+ return true;
+ }))
+ return;
+
+ // Check if format string argument is parent function parameter.
+ unsigned int StringIndex = 0;
+ if (!llvm::any_of(ParentFuncDecl->parameters(),
+ [&](const ParmVarDecl *Param) {
+ StringIndex = Param->getFunctionScopeIndex() +
+ ParentFunctionFormatArgumentIndexOffset;
+
+ return Param == FormatArg;
+ }))
+ return;
+
+ unsigned NumOfParentFunctionParams = ParentFuncDecl->getNumParams();
+
+ // Compare parent and calling function format attribute arguments (archetype
+ // and format string).
+ if (llvm::any_of(
+ ParentFuncDecl->specific_attrs<FormatAttr>(),
+ [&](const FormatAttr *Attr) {
+ if (Attr->getType() != AttrType)
+ return false;
+ int OffsetFormatIndex =
+ Attr->getFormatIdx() - ParentFunctionFormatArgumentIndexOffset;
+
+ if (OffsetFormatIndex < 0 ||
+ (unsigned)OffsetFormatIndex >= NumOfParentFunctionParams)
+ return false;
+
+ if (ParentFuncDecl->parameters()[OffsetFormatIndex] != FormatArg)
+ return false;
+
+ return true;
+ }))
+ return;
+
+ // If parent function is variadic, check if last argument of child function is
+ // va_list.
+ unsigned FirstToCheck = [&]() -> unsigned {
+ if (!ParentFuncDecl->isVariadic())
+ return 0;
+ const auto *FirstToCheckArg =
+ dyn_cast<DeclRefExpr>(Args[Args.size() - 1]->IgnoreParenCasts());
+ if (!FirstToCheckArg)
+ return 0;
+
+ if (FirstToCheckArg->getType().getCanonicalType() !=
+ Context.getBuiltinVaListType().getCanonicalType())
+ return 0;
+ return NumOfParentFunctionParams + ParentFunctionFormatArgumentIndexOffset;
+ }();
+
+ // Emit warning
+ SourceLocation ParentFuncLoc = ParentFuncDecl->getLocation();
+ Diag(ParentFuncLoc, diag::warn_missing_format_attribute)
+ << AttrType << ParentFuncDecl
+ << FixItHint::CreateInsertion(ParentFuncLoc,
+ (llvm::Twine("__attribute__((format(") +
+ AttrType->getName() + ", " +
+ llvm::Twine(StringIndex) + ", " +
+ llvm::Twine(FirstToCheck) + ")))")
+ .str());
+}
+
//===----------------------------------------------------------------------===//
// 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..136ae0739791e
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.c
@@ -0,0 +1,304 @@
+// 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-warning@#f1 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 3, 4)))"
+}
+
+__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)))"
+ vscanf(out, args); // expected-warning@#f3 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f3'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:6-[[@LINE-5]]:6}:"__attribute__((format(scanf, 1, 0)))"
+}
+
+void f4(char* out, ... /* args */) // #f4
+{
+ va_list args;
+ vprintf("test", args); // expected-no-warning
+
+ const char *ch;
+ vscanf(ch, args); // expected-no-warning
+}
+
+void f5(va_list args) // #f5
+{
+ char *ch;
+ vscanf(ch, args); // expected-no-warning
+}
+
+void f6(char *out, va_list args) // #f6
+{
+ char *ch;
+ vscanf(ch, args); // expected-no-warning
+ vprintf("test", args); // expected-no-warning
+ 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)))"
+ vprintf(out, &args[0]); // expected-warning@#f7 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f7'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:6-[[@LINE-7]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
+
+__attribute__((format(scanf, 1, 0)))
+__attribute__((format(printf, 1, 2)))
+void f8(const char *out, ... /* args */) // #f8
+{
+ va_list args;
+
+ vscanf(out, &args[0]); // expected-no-warning
+ vprintf(out, &args[0]); // expected-no-warning
+}
+
+void f9(const char out[], ... /* args */) // #f9
+{
+ va_list args;
+ char *ch;
+ vscanf(ch, args); // expected-no-warning
+ vscanf(out, args); // expected-warning@#f9 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f9'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ 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-8]]:6-[[@LINE-8]]:6}:"__attribute__((format(printf, 1, 2)))"
+}
+
+void f10(const wchar_t *out, ... /* args */) // #f10
+{
+ va_list args;
+ vprintf(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@#vprintf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f10 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}}
+ // cpp_diagnostics-error at -8 {{no matching function for call to 'vprintf'}}
+ // cpp_diagnostics-note@#vprintf {{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(printf, 1, 2)))"
+ vscanf((const char *) out, args); // expected-warning@#f10 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-15]]:6-[[@LINE-15]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ vscanf((char *) out, args); // expected-warning@#f10 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-17]]:6-[[@LINE-17]]:6}:"__attribute__((format(scanf, 1, 2)))"
+
+}
+
+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)))"
+}
+
+void f12(const char16_t *out, ... /* args */) // #f12
+{
+ 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@#f12 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f12'}}
+ // 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 f13(const char32_t *out, ... /* args */) // #f13
+{
+ 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@#f13 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}}
+ // 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 f14(const unsigned char *out, ... /* args */) // #f14
+{
+ 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}}
+ // c_diagnostics-warning@#f14 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f14'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 2)))"
+ // cpp_diagnostics-error at -4 {{no matching function for call to 'vprintf'}}
+ // cpp_diagnostics-note@#vprintf {{candidate function not viable: no known conversion from 'const unsigned char *' to 'const char *' for 1st argument}}
+ vscanf((const char *) 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-10]]:6-[[@LINE-10]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ vscanf((char *) 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-12]]:6-[[@LINE-12]]:6}:"__attribute__((format(scanf, 1, 2)))"
+}
+
+__attribute__((format(printf, 1, 2)))
+void f15(const unsigned char *out, ... /* args */) // #f15
+{
+ 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@#vprintf {{candidate function not viable: no known conversion from 'const unsigned char *' to 'const char *' for 1st argument}}
+ vscanf((const char *) out, args); // expected-warning@#f15 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-8]]:6-[[@LINE-8]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ vprintf((const char *) out, args); // expected-no-warning
+ vscanf((char *) out, args); // expected-warning@#f15 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-11]]:6-[[@LINE-11]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ vprintf((char *) out, args); // expected-no-warning
+}
+
+void f16(signed char *out, ... /* args */) // #f16
+{
+ 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@#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 'signed char *' to 'const char *' for 1st argument}}
+ vscanf((const char *) out, args); // expected-warning@#f16 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-10]]:6-[[@LINE-10]]:6}:"__attribute__((format(scanf, 1, 2)))"
+ vprintf((char *) out, args); // expected-warning@#f16 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-12]]:6-[[@LINE-12]]:6}:"__attribute__((format(printf, 1, 2)))"
+}
+
+__attribute__((format(scanf, 1, 2)))
+void f17(signed char *out, ... /* args */) // #f17
+{
+ va_list args;
+ vprintf(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@#vprintf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f17 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 2)))"
+ // cpp_diagnostics-error at -4 {{no matching function for call to 'vprintf'}}
+ // cpp_diagnostics-note@#vprintf {{candidate function not viable: no known conversion from 'signed char *' to 'const char *' for 1st argument}}
+ vscanf((const char *) out, args); // expected-no-warning
+ vprintf((const char *) out, args); // expected-warning@#f17 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-11]]:6-[[@LINE-11]]:6}:"__attribute__((format(printf, 1, 2)))"
+ vscanf((char *) out, args); // expected-no-warning
+ vprintf((char *) out, args); // expected-warning@#f17 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-14]]:6-[[@LINE-14]]:6}:"__attribute__((format(printf, 1, 2)))"
+}
+
+__attribute__((format(printf, 1, 2)))
+void f18(unsigned char out[], ... /* args */) // #f18
+{
+ va_list args;
+ vprintf(out, args); // c_diagnostics-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}}
+ // c_diagnostics-note@#vprintf {{passing argument to parameter here}}
+ // cpp_diagnostics-error at -2 {{no matching function for call to 'vprintf'}}
+ // cpp_diagnostics-note@#vprintf {{candidate function not viable: no known conversion from 'unsigned char *' to 'const char *' for 1st argument}}
+ vscanf(out, args); // c_diagnostics-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}}
+ // c_diagnostics-note@#vscanf {{passing argument to parameter here}}
+ // c_diagnostics-warning@#f18 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f18'}}
+ // C-CHECK: fix-it:"{{.*}}":{[[@LINE-10]]:6-[[@LINE-10]]: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 'unsigned char *' to 'const char *' for 1st argument}}
+}
+
+void f19(char* out) // #f19
+{
+ va_list args;
+ const char* ch;
+ vsprintf(out, ch, args); // expected-no-warning
+ vscanf(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-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(scanf, 1, 0)))"
+}
+
+void f20(const char *out, ... /* args */) // #f20
+{
+ int a;
+ printf(out, a); // expected-warning@#f20 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f20'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:6-[[@LINE-4]]:6}:"__attribute__((format(printf, 1, 0)))"
+ printf(out, 1); // expected-warning@#f20 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f20'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
+
+__attribute__((format(printf, 1, 2)))
+void f21(const char *out, ... /* args */) // #f21
+{
+ int a;
+ printf(out, a); // expected-no-warning
+ printf(out, 1); // expected-no-warning
+}
+
+void f22(char* ch, const char *out, ... /* args */) // #f22
+{
+ va_list args;
+ printf(ch, 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, 3)))"
+ int a;
+ printf(out, a); // expected-warning@#f22 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f22'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:6-[[@LINE-7]]:6}:"__attribute__((format(printf, 2, 0)))"
+ printf(out, 1); // expected-warning@#f22 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f22'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-9]]:6-[[@LINE-9]]:6}:"__attribute__((format(printf, 2, 0)))"
+ printf(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-11]]:6-[[@LINE-11]]:6}:"__attribute__((format(printf, 2, 3)))"
+}
+
+typedef va_list tdVaList;
+typedef int tdInt;
+
+void f23(const char *out, ... /* args */) // #f23
+{
+ tdVaList args;
+ printf(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)))"
+ tdInt a;
+ scanf(out, a); // expected-warning@#f23 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f23'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-7]]:6-[[@LINE-7]]:6}:"__attribute__((format(scanf, 1, 0)))"
+}
+
+void f24(const char *out, tdVaList args) // #f24
+{
+ scanf(out, args); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f24'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:6-[[@LINE-3]]:6}:"__attribute__((format(scanf, 1, 0)))"
+ tdInt a;
+ printf(out, a); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}}
+ // CHECK: fix-it:"{{.*}}":{[[@LINE-6]]:6-[[@LINE-6]]:6}:"__attribute__((format(printf, 1, 0)))"
+}
diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp
new file mode 100644
index 0000000000000..ba66c29fbb9bf
--- /dev/null
+++ b/clang/test/Sema/attr-format-missing.cpp
@@ -0,0 +1,178 @@
+// 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
+ vprintf(str.c_str(), args); // expected-no-warning
+}
+
+__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
+ vprintf(std::string(str).c_str(), args); // expected-no-warning
+}
+
+__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;
+ vscanf((const char *)str.c_str(), args); // expected-no-warning
+ vprintf((const char *)str.c_str(), args); // expected-no-warning
+}
+
+__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;
+ vscanf((const char *) std::wstring(str).c_str(), args); // expected-no-warning
+ vprintf((const char *) std::wstring(str).c_str(), args); // expected-no-warning
+}
+
+__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()
+ {
+ const char *out;
+ va_list args;
+ vprintf(out, args); // expected-no-warning
+ }
+};
+
+// TODO: implement for templates
+template <int N>
+void func(char (&str)[N], ... /* args */)
+{
+ va_list args;
+ vprintf(str, args); // expected-no-warning
+}
More information about the cfe-commits
mailing list