https://github.com/budimirarandjelovicsyrmia updated https://github.com/llvm/llvm-project/pull/70024
From c8d3fbdf7fc494aaf5c3ac1e2ca9545d8bc4e0bc Mon Sep 17 00:00:00 2001 From: budimirarandjelovicsyrmia <budimir.arandjelo...@syrmia.com> Date: Fri, 13 Oct 2023 14:45:15 +0200 Subject: [PATCH] [clang] Catch missing format attributes --- clang/docs/ReleaseNotes.rst | 4 +- clang/include/clang/Basic/DiagnosticGroups.td | 1 - .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/include/clang/Sema/Sema.h | 4 + clang/lib/Sema/SemaChecking.cpp | 4 +- clang/lib/Sema/SemaDeclAttr.cpp | 90 ++++++++ clang/test/Sema/attr-format-missing.c | 213 ++++++++++++++++++ clang/test/Sema/attr-format-missing.cpp | 132 +++++++++++ 8 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 clang/test/Sema/attr-format-missing.c create mode 100644 clang/test/Sema/attr-format-missing.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 9cc2a72f4c864d..1ca055f65f2115 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -65,7 +65,9 @@ Improvements to Clang's diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - We now generate a diagnostic for signed integer overflow due to unary minus in a non-constant expression context. This fixes - `Issue 31643 <https://github.com/llvm/llvm-project/issues/31643>`_ + `Issue 31643 <https://github.com/llvm/llvm-project/issues/31643>`_. +- We now generate a diagnostic for missing format attributes + `Issue 60718 <https://github.com/llvm/llvm-project/issues/60718>`_. Non-comprehensive list of changes in this release ------------------------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 17fdcffa2d4274..114cbbc2e82b85 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -482,7 +482,6 @@ def MainReturnType : DiagGroup<"main-return-type">; def MaxUnsignedZero : DiagGroup<"max-unsigned-zero">; def MissingBraces : DiagGroup<"missing-braces">; def MissingDeclarations: DiagGroup<"missing-declarations">; -def : DiagGroup<"missing-format-attribute">; def : DiagGroup<"missing-include-dirs">; def MissingNoreturn : DiagGroup<"missing-noreturn">; def MultiChar : DiagGroup<"multichar">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6d6f474f6dcdab..964166b70c2aee 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -936,6 +936,9 @@ def err_opencl_invalid_param : Error< def err_opencl_invalid_return : Error< "declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">; def warn_enum_value_overflow : Warning<"overflow in enumeration value">; +def warn_missing_format_attribute : Warning< + "diagnostic behavior may be improved by adding the %0 format attribute to the declaration of %1">, + InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore; def warn_pragma_options_align_reset_failed : Warning< "#pragma options align=reset failed: %0">, InGroup<IgnoredPragmas>; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 741c2503127af7..064506e7096033 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10615,6 +10615,10 @@ class Sema final { ChangedStateAtExit }; + void DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl, + ArrayRef<const Expr *> Args, + SourceLocation Loc); + void DiagnoseNonDefaultPragmaAlignPack(PragmaAlignPackDiagnoseKind Kind, SourceLocation IncludeLoc); void DiagnoseUnterminatedPragmaAlignPack(); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 4602284309491c..d3ac6cb519c56e 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -6014,8 +6014,10 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, } } - if (FD) + if (FD) { diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc); + DiagnoseMissingFormatAttributes(FD, Args, Range.getBegin()); + } } /// CheckConstructorCall - Check a constructor call for correctness and safety diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 1a0bfb3d91bcc8..e2dfdd806f7c8b 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6849,6 +6849,96 @@ static void handleSwiftAsyncAttr(Sema &S, Decl *D, const ParsedAttr &AL) { checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr); } +// 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); + + // 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) { + if (Attr->getFormatIdx() < 0 || + Args.size() < (unsigned)Attr->getFormatIdx()) + return false; + + const DeclRefExpr *FormatArgExpr = dyn_cast_or_null<DeclRefExpr>( + Args[Attr->getFormatIdx() - 1]->IgnoreParenCasts()); + if (!FormatArgExpr) + return false; + + FormatArg = dyn_cast_or_null<ParmVarDecl>( + FormatArgExpr->getReferencedDeclOfCallee()); + if (!FormatArg) + return false; + + AttrType = Attr->getType(); + return true; + })) + return; + + const FunctionDecl *ParentFuncDecl = getCurFunctionDecl(); + if (!ParentFuncDecl) + 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() + 1; + + 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; + if (Attr->getFormatIdx() < 1 || + (unsigned)Attr->getFormatIdx() > NumOfParentFunctionParams) + return false; + if (ParentFuncDecl->parameters()[Attr->getFormatIdx() - 1] != + 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 DeclRefExpr *FirstToCheckArg = dyn_cast_or_null<DeclRefExpr>( + Args[Args.size() - 1]->IgnoreParenCasts()); + if (!FirstToCheckArg) + return 0; + + if (FirstToCheckArg->getType().getAsString() != "va_list") + return 0; + return NumOfParentFunctionParams + 1; + }(); + + // Emit warning + std::string InsertionText; + llvm::raw_string_ostream OS(InsertionText); + OS << "__attribute__((format(" << AttrType->getName() << ", " << StringIndex + << ", " << FirstToCheck << ")))"; + SourceLocation ParentFuncLoc = ParentFuncDecl->getLocation(); + Diag(ParentFuncLoc, diag::warn_missing_format_attribute) + << AttrType << ParentFuncDecl + << FixItHint::CreateInsertion(ParentFuncLoc, InsertionText); +} + //===----------------------------------------------------------------------===// // Microsoft specific attribute handlers. //===----------------------------------------------------------------------===// diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c new file mode 100644 index 00000000000000..1f304a0cff7974 --- /dev/null +++ b/clang/test/Sema/attr-format-missing.c @@ -0,0 +1,213 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s + +#include <stdarg.h> +#include <stdio.h> +#include <uchar.h> +#include <wchar.h> + +__attribute__((__format__ (__scanf__, 1, 4))) +void f1(char *out, const size_t len, const char *format, ... /* args */) +{ + va_list args; + vsnprintf(out, len, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}} + // CHECK-FIXES: __attribute__((format(printf, 3, 4))) +} + +__attribute__((__format__ (__printf__, 1, 4))) +void f2(char *out, const size_t len, const char *format, ... /* args */) +{ + va_list args; + vsnprintf(out, len, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f2'}} + // CHECK-FIXES: __attribute__((format(printf, 3, 4))) +} + +void f3(char *out, va_list args) +{ + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 0))) + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f3'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f4(char* out, ... /* args */) +{ + va_list args; + vprintf("test", args); // no warning + + const char *ch; + vscanf(ch, args); // no warning +} + +void f5(va_list args) +{ + char *ch; + vscanf(ch, args); // no warning +} + +void f6(char *out, va_list args) +{ + char *ch; + vscanf(ch, args); // no warning + vprintf("test", args); // no warning +} + +void f7(const char *out, ... /* args */) +{ + va_list args; + + vscanf(out, &args[0]); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f7'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vprintf(out, &args[0]); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f7'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) +} + +void f8(const char out[], ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f8'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + char *ch; + vprintf(ch, args); // no warning +} + +void f9(const wchar_t *out, ... /* args */) +{ + va_list args; + vprintf(out, args); // expected-warning {{incompatible pointer types passing 'const wchar_t *' (aka 'const int *') to parameter of type 'const char *'}} + // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f9'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((const char *) out, args); // no warning + // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f9'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vscanf((char *) out, args); // no warning + // expected-warning {{diagnostic behavior may be improved by adding the 'printf' 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 */); + +void f11(const char16_t *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-warning {{incompatible pointer types passing 'const char16_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}} + // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f11'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f12(const char16_t *out, ... /* args */); + +void f13(const char32_t *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-warning {{incompatible pointer types passing 'const char32_t *' (aka 'const unsigned int *') to parameter of type 'const char *'}} + // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}} +void f14(const char32_t *out, ... /* args */); + +void f15(const unsigned char *out, ... /* args */) +{ + va_list args; + vprintf(out, args); // expected-warning {{passing 'const unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}} + // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f15'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((const char *) out, args); // no warning + // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vscanf((char *) out, args); // no warning + // expected-warning {{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(printf, 1, 2))) +void f16(const unsigned char *out, ... /* args */) +{ + va_list args; + vprintf(out, args); // expected-warning {{passing 'const unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}} + vscanf((const char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vprintf((const char *) out, args); // no warning + vscanf((char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vprintf((char *) out, args); // no warning +} + +void f17(signed char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-warning {{passing 'signed char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}} + // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) + vscanf((const char *) out, args); // expected-warning {{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); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) +} + +__attribute__((format(scanf, 1, 2))) +void f18(signed char *out, ... /* args */) +{ + va_list args; + vprintf(out, args); // expected-warning {{passing 'signed char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}} + // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((const char *) out, args); // no warning + vprintf((const char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 2))) + vscanf((char *) out, args); // no warning + vprintf((char *) out, args); // expected-warning {{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(printf, 1, 2))) +void f19(unsigned char out[], ... /* args */) +{ + va_list args; + vprintf(out, args); // expected-warning {{passing 'unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}} + 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-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f19'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 2))) +} + +void f20(char* out) { + va_list args; + const char* ch; + vsprintf(out, ch, args); // no warning + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f20'}} + // CHECK-FIXES: __attribute__((format(scanf, 1, 0))) +} + +void f21(const char *out, ...) +{ + int a; + printf(out, a); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f21'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 0))) + printf(out, 1); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f21'}} + // CHECK-FIXES: __attribute__((format(printf, 1, 0))) +} + +__attribute__((format(printf, 1, 2))) +void f22(const char *out, ...) +{ + int a; + printf(out, a); // no warning + printf(out, 1); // no warning +} + +void f23(char* ch, const char *out, ...) +{ + va_list args; + printf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f23}} + // CHECK-FIXES: __attribute__((format(printf, 1, 3))) + int a; + printf(out, a); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f23'}} + // CHECK-FIXES: __attribute__((format(printf, 2, 0))) + printf(out, 1); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f23'}} + // CHECK-FIXES: __attribute__((format(printf, 2, 0))) + printf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f23'}} + // CHECK-FIXES: __attribute__((format(printf, 2, 3))) +} diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp new file mode 100644 index 00000000000000..a934d68cfaa907 --- /dev/null +++ b/clang/test/Sema/attr-format-missing.cpp @@ -0,0 +1,132 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s + +#include <iostream> +#include <cstdarg> + +void f1(const std::string &str, ... /* args */) +{ + va_list args; + vscanf(str.c_str(), args); // no warning + vprintf(str.c_str(), args); // no warning +} + +__attribute__((format(printf, 1, 2))) // expected-error: {{format argument not a string type}} +void f2(const std::string &str, ... /* args */); + +void f3(std::string_view str, ... /* args */) +{ + va_list args; + vscanf(std::string(str).c_str(), args); // no warning + vprintf(std::string(str).c_str(), args); // no warning +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f4(std::string_view str, ... /* args */); + +void f5(const std::wstring &str, ... /* args */) +{ + va_list args; + vscanf((const char *)str.c_str(), args); // no warning + vprintf((const char *)str.c_str(), args); // no warning +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f6(const std::wstring &str, ... /* args */); + +void f7(std::wstring_view str, ... /* args */) +{ + va_list args; + vscanf((const char *) std::wstring(str).c_str(), args); // no warning + vprintf((const char *) std::wstring(str).c_str(), args); // no warning +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f8(std::wstring_view str, ... /* args */); + +void f9(const wchar_t *out, ... /* args */) +{ + va_list args; + vprintf(out, args); // expected-error {{no matching function for call to 'vprintf'}} + vscanf((const char *) out, args); // no warning + vscanf((char *) out, args); // no warning +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f10(const wchar_t *out, ... /* args */); + +void f11(const char16_t *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} +} + +__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}} +void f12(const char16_t *out, ... /* args */); + +void f13(const char32_t *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} +} + +__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}} +void f14(const char32_t *out, ... /* args */); + +void f15(const char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} +} + +__attribute__((format(scanf, 1, 2))) +void f16(const char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // no warning +} + +void f17(const unsigned char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} +} + +__attribute__((format(scanf, 1, 2))) +void f18(const unsigned char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} +} + +void f19(const signed char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} +} + +__attribute__((format(scanf, 1, 2))) +void f20(const signed char *out, ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-error {{no matching function for call to 'vscanf'}} +} + +void f21(const char out[], ... /* args */) +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f21'}} +} + +__attribute__((format(scanf, 1, 2))) +void f22(const char out[], ... /* args */) +{ + va_list args; + vscanf(out, args); // no warning +} + +template <int N> +void func(char (&str)[N], ...) +{ + va_list args; + vprintf(str, args); // no warning +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits