https://github.com/budimirarandjelovicsyrmia updated https://github.com/llvm/llvm-project/pull/70024
From 75ec541cdf717931afe70abc412cb1747f84e0d7 Mon Sep 17 00:00:00 2001 From: budimirarandjelovicsyrmia <budimir.arandjelo...@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/Sema.h | 4 + clang/lib/Sema/SemaChecking.cpp | 4 +- clang/lib/Sema/SemaDeclAttr.cpp | 119 ++++++- clang/test/Sema/attr-format-missing.c | 277 ++++++++++++++++ clang/test/Sema/attr-format-missing.cpp | 303 ++++++++++++++++++ 8 files changed, 709 insertions(+), 5 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 a85095e424b64..a07b223b48509 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -473,6 +473,9 @@ Improvements to Clang's diagnostics } }; +- Clang now diagnoses missing format attributes for non-template functions and + class/struct/union members. Fixes #GH70024 + Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 60f87da2a7387..5fa2f6e7db4fa 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 : 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 9a0bae9c216de..1fbe379d9af0b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1010,6 +1010,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 a80ac6dbc7613..5552a48456cde 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3725,6 +3725,10 @@ class Sema final : public SemaBase { bool DiagnoseSwiftName(Decl *D, StringRef Name, SourceLocation Loc, const ParsedAttr &AL, bool IsAsync); + 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 3179d542b1f92..fd5de633be318 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -8085,8 +8085,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 363ae93cb62df..5f5403d303a90 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -163,6 +163,13 @@ static bool isInstanceMethod(const Decl *D) { return false; } +static bool checkIfMethodHasImplicitObjectParameter(const Decl *D) { + if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(D)) + return MethodDecl->isInstance() && + !MethodDecl->hasCXXExplicitFunctionObjectParameter(); + return false; +} + static inline bool isNSStringType(QualType T, ASTContext &Ctx, bool AllowNSAttributedString = false) { const auto *PT = T->getAs<ObjCObjectPointerType>(); @@ -312,7 +319,7 @@ static bool checkFunctionOrMethodParameterIndex( // In C++ the implicit 'this' function parameter also counts. // Parameters are counted from one. bool HP = hasFunctionProto(D); - bool HasImplicitThisParam = isInstanceMethod(D); + bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D); bool IV = HP && isFunctionOrMethodVariadic(D); unsigned NumParams = (HP ? getFunctionOrMethodNumParams(D) : 0) + HasImplicitThisParam; @@ -4050,7 +4057,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; @@ -4164,7 +4171,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(); @@ -7159,6 +7166,112 @@ static void handleSwiftAsyncAttr(Sema &S, Decl *D, const ParsedAttr &AL) { checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr); } +// 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) { + const int FormatIndexOffseted = + Attr->getFormatIdx() - FormatArgumentIndexOffset; + if (FormatIndexOffseted < 0 || + (unsigned)FormatIndexOffseted >= Args.size()) + return false; + + const auto *FormatArgExpr = dyn_cast<DeclRefExpr>( + Args[FormatIndexOffseted]->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 FormatIndexOffseted = + Attr->getFormatIdx() - ParentFunctionFormatArgumentIndexOffset; + + if (FormatIndexOffseted < 0 || + (unsigned)FormatIndexOffseted >= NumOfParentFunctionParams) + return false; + + if (ParentFuncDecl->parameters()[FormatIndexOffseted] != 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 + llvm::Twine InsertionText( + llvm::Twine("__attribute__((format(") + AttrType->getName() + ", " + + llvm::Twine(StringIndex) + ", " + llvm::Twine(FirstToCheck) + ")))"); + SourceLocation ParentFuncLoc = ParentFuncDecl->getLocation(); + Diag(ParentFuncLoc, diag::warn_missing_format_attribute) + << AttrType << ParentFuncDecl + << FixItHint::CreateInsertion(ParentFuncLoc, InsertionText.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..751ece186e750 --- /dev/null +++ b/clang/test/Sema/attr-format-missing.c @@ -0,0 +1,277 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s + +typedef unsigned short char16_t; +typedef unsigned int char32_t; +typedef __WCHAR_TYPE__ wchar_t; +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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f4(char* out, ... /* args */) // #f4 +{ + va_list args; + vprintf("test", args); // no warning + + const char *ch; + vscanf(ch, args); // no warning +} + +void f5(va_list args) // #f5 +{ + char *ch; + vscanf(ch, args); // no warning +} + +void f6(char *out, va_list args) // #f6 +{ + char *ch; + vscanf(ch, args); // no warning + vprintf("test", args); // no warning +} + +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-FIXES: __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-FIXES: __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]); // no warning + vprintf(out, &args[0]); // no warning +} + +void f9(const char out[], ... /* args */) // #f9 +{ + va_list args; + char *ch; + vscanf(ch, args); // 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-FIXES: __attribute__((format(printf, 1, 2))) +} + +void f10(const wchar_t *out, ... /* args */) // #f10 +{ + va_list args; + vprintf(out, args); +#if __SIZEOF_WCHAR_T__ == 4 + // expected-warning@-2 {{incompatible pointer types passing 'const wchar_t *' (aka 'const int *') to parameter of type 'const char *'}} +#else + // expected-warning@-4 {{incompatible pointer types passing 'const wchar_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}} +#endif + // expected-note@#vprintf {{passing argument to parameter here}} + // expected-warning@#f10 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}} + // CHECK-FIXES: __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-FIXES: __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-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f11(const wchar_t *out, ... /* args */); // #f11 + +void f12(const char16_t *out, ... /* args */) // #f12 +{ + 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 *'}} + // expected-note@#vscanf {{passing argument to parameter here}} + // expected-warning@#f12 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f12'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f13(const char16_t *out, ... /* args */); // #f13 + +void f14(const char32_t *out, ... /* args */) // #f14 +{ + 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 *'}} + // expected-note@#vscanf {{passing argument to parameter here}} + // expected-warning@#f14 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}} +void f15(const char32_t *out, ... /* args */); // #f15 + +void f16(const unsigned char *out, ... /* args */) // #f16 +{ + 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}} + // expected-note@#vprintf {{passing argument to parameter here}} + // expected-warning@#f16 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((const char *) out, args); // no warning + // expected-warning@#f16 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vscanf((char *) out, args); // no warning + // expected-warning@#f16 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(printf, 1, 2))) +void f17(const unsigned char *out, ... /* args */) // #f17 +{ + 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}} + // expected-note@#vprintf {{passing argument to parameter here}} + vscanf((const char *) out, args); // expected-warning@#f17 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vprintf((const char *) out, args); // no warning + vscanf((char *) out, args); // expected-warning@#f17 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vprintf((char *) out, args); // no warning +} + +void f18(signed char *out, ... /* args */) // #f18 +{ + 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}} \ + // expected-note@#vscanf {{passing argument to parameter here}} \ + // expected-warning@#f18 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f18'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + 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-FIXES: __attribute__((format(scanf, 1, 2))) + vprintf((char *) out, args); // expected-warning@#f18 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) +} + +__attribute__((format(scanf, 1, 2))) +void f19(signed char *out, ... /* args */) // #f19 +{ + 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}} + // expected-note@#vprintf {{passing argument to parameter here}} + // expected-warning@#f19 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((const char *) out, args); // no warning + vprintf((const char *) out, args); // expected-warning@#f19 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((char *) out, args); // no warning + vprintf((char *) out, args); // expected-warning@#f19 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) +} + +__attribute__((format(printf, 1, 2))) +void f20(unsigned char out[], ... /* args */) // #f20 +{ + 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}} + // expected-note@#vprintf {{passing argument to parameter here}} + vscanf(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}} + // expected-note@#vscanf {{passing argument to parameter here}} + // expected-warning@#f20 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f20'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +void f21(char* out) // #f21 +{ + va_list args; + const char* ch; + vsprintf(out, ch, args); // no warning + vscanf(out, args); // expected-warning@#f21 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f21'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f22(const char *out, ... /* args */) // #f22 +{ + 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-FIXES: __attribute__((format(printf, 1, 0))) + printf(out, 1); // expected-warning@#f22 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f22'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 0))) +} + +__attribute__((format(printf, 1, 2))) +void f23(const char *out, ... /* args */) // #f23 +{ + int a; + printf(out, a); // no warning + printf(out, 1); // no warning +} + +void f24(char* ch, const char *out, ... /* args */) // #f24 +{ + va_list args; + printf(ch, args); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24}} + // CHECK-FIXES: __attribute__((format(printf, 1, 3))) + int a; + printf(out, a); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}} + // CHECK-FIXES: __attribute__((format(printf, 2, 0))) + printf(out, 1); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}} + // CHECK-FIXES: __attribute__((format(printf, 2, 0))) + printf(out, args); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}} + // CHECK-FIXES: __attribute__((format(printf, 2, 3))) +} + +typedef va_list tdVaList; +typedef int tdInt; + +void f25(const char *out, ... /* args */) // #f25 +{ + tdVaList args; + printf(out, args); // expected-warning@#f25 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f25'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + tdInt a; + scanf(out, a); // expected-warning@#f25 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f25'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f26(const char *out, tdVaList args) // #f26 +{ + scanf(out, args); // expected-warning@#f26 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f26'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) + tdInt a; + printf(out, a); // expected-warning@#f26 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f26'}} + // CHECK-FIXES: __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..2250f3d3b5af0 --- /dev/null +++ b/clang/test/Sema/attr-format-missing.cpp @@ -0,0 +1,303 @@ +// 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 + +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); // 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 */); // #f2 + +void f3(std::string_view str, ... /* args */) // #f3 +{ + 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 */); // #f4 + +void f5(const std::wstring &str, ... /* args */) // #f5 +{ + 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 */); // #f6 + +void f7(std::wstring_view str, ... /* args */) // #f7 +{ + 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 */); // #f8 + +void f9(const wchar_t *out, ... /* args */) // #f9 +{ + va_list args; + vprintf(out, args); // expected-error {{no matching function for call to 'vprintf'}} + // expected-note@#vprintf {{candidate function not viable: no known conversion from 'const wchar_t *' to 'const char *' for 1st argument}} + vscanf((const char *) out, args); // expected-warning@#f9 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f9'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vscanf((char *) out, args); // expected-warning@#f9 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f9'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f10(const wchar_t *out, ... /* args */); // #f10 + +void f11(const char16_t *out, ... /* args */) // #f11 +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} + // expected-note@#vscanf {{candidate function not viable: no known conversion from 'const char16_t *' to 'const char *' for 1st argument}} +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f12(const char16_t *out, ... /* args */); // #f12 + +void f13(const char32_t *out, ... /* args */) // #f13 +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} + // expected-note@#vscanf {{candidate function not viable: no known conversion from 'const char32_t *' to 'const char *' for 1st argument}} +} + +__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}} +void f14(const char32_t *out, ... /* args */); // #f14 + +void f15(const char *out, ... /* args */) // #f15 +{ + va_list args; + vscanf(out, args); // expected-warning@#f15 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(scanf, 1, 2))) +void f16(const char *out, ... /* args */) // #f16 +{ + va_list args; + vscanf(out, args); // no warning +} + +void f17(const unsigned char *out, ... /* args */) // #f17 +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} + // expected-note@#vscanf {{candidate function not viable: no known conversion from 'const unsigned char *' to 'const char *' for 1st argument}} +} + +__attribute__((format(scanf, 1, 2))) +void f18(const unsigned char *out, ... /* args */) // #f18 +{ + va_list args; + vprintf(out, args); // expected-error {{no matching function for call to 'vprintf'}} + // expected-note@#vprintf {{candidate function not viable: no known conversion from 'const unsigned char *' to 'const char *' for 1st argument}} +} + +void f19(const signed char *out, ... /* args */) // #f19 +{ + va_list args; + vprintf(out, args); // expected-error {{no matching function for call to 'vprintf'}} + // expected-note@#vprintf {{candidate function not viable: no known conversion from 'const signed char *' to 'const char *' for 1st argument}} +} + +__attribute__((format(scanf, 1, 2))) +void f20(const signed char *out, ... /* args */) // #f20 +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} + // expected-note@#vscanf {{candidate function not viable: no known conversion from 'const signed char *' to 'const char *' for 1st argument}} +} + +void f21(const char out[], ... /* args */) // #f21 +{ + va_list args; + vscanf(out, args); // expected-warning@#f21 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f21'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(scanf, 1, 0))) +void f22(const char out[], ... /* args */) // #f22 +{ + va_list args; + vscanf(out, args); // no warning +} + +void f23(const char *out) // #f23 +{ + va_list args; + vscanf(out, args); // expected-warning@#f23 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f23'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f24(const char *out, va_list args) // #f24 +{ + vprintf(out, args); // expected-warning@#f24 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 0))) +} + +typedef va_list tdVaList; +typedef int tdInt; +void f25(const char *out, ... /* args */) // #f25 +{ + tdVaList args; + printf(out, args); // expected-warning@#f25 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f25'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + tdInt a; + scanf(out, a); // expected-warning@#f25 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f25'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f26(const char *out, tdVaList args) // #f26 +{ + scanf(out, args); // expected-warning@#f26 {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f26'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) + tdInt a; + printf(out, a); // expected-warning@#f26 {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f26'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 0))) +} + +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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __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-FIXES: __attribute__((format(printf, 2, 3))) + } + + ~C1() + { + const char *out; + va_list args; + vprintf(out, args); // no warning + } +}; + +// TODO: implement for templates +template <int N> +void func(char (&str)[N], ... /* args */) +{ + va_list args; + vprintf(str, args); // no warning +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits