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