From aebddd9138b5aedd941739dcf5f8bf08357ad0b7 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Fri, 31 Oct 2025 02:10:05 -0700 Subject: [PATCH 1/7] [Sema] Suggest missing format attributes Implements the `-Wmissing-format-attribute` diagnostic. It suggests adding format attributes to function declarations that call other format functions and pass the format string and arguments to them. Co-authored-by: Budimir Arandjelovic --- clang/docs/ReleaseNotes.rst | 2 + clang/include/clang/Basic/DiagnosticGroups.td | 1 - .../clang/Basic/DiagnosticSemaKinds.td | 5 + clang/lib/Sema/SemaChecking.cpp | 125 ++++++++-- clang/lib/Sema/SemaDeclAttr.cpp | 4 +- clang/test/Sema/attr-format-missing.c | 217 ++++++++++++++++++ clang/test/Sema/attr-format-missing.cpp | 68 ++++++ 7 files changed, 397 insertions(+), 25 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 32f669f8d70d8..be384b7db72a3 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -401,6 +401,8 @@ Improvements to Clang's diagnostics or continue (#GH166013) - Clang now emits a diagnostic in case `vector_size` or `ext_vector_type` attributes are used with a negative size (#GH165463). +- Clang now detects potential missing format attributes on function declarations + when calling format functions. (#GH60718) Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 1e0321de3f4b6..7b534d416fdd9 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -607,7 +607,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 fa509536bf021..78c5caf79eab7 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3478,6 +3478,11 @@ def err_format_attribute_result_not : Error<"function does not return %0">; def err_format_attribute_implicit_this_format_string : Error< "format attribute cannot specify the implicit this argument as the format " "string">; +def warn_missing_format_attribute + : Warning<"diagnostic behavior may be improved by adding the '%0' format " + "attribute to the declaration of %1">, + InGroup>, + DefaultIgnore; def err_callback_attribute_no_callee : Error< "'callback' attribute specifies no callback callee">; def err_callback_attribute_invalid_callee : Error< diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index ad2c2e4a97bb9..502e9fd099144 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -6517,7 +6517,8 @@ static StringLiteralCheckType checkFormatStringExpr( unsigned format_idx, unsigned firstDataArg, FormatStringType Type, VariadicCallType CallType, bool InFunctionCall, llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg, - llvm::APSInt Offset, bool IgnoreStringsWithoutSpecifiers = false) { + llvm::APSInt Offset, std::optional *CallerParamIdx = nullptr, + bool IgnoreStringsWithoutSpecifiers = false) { if (S.isConstantEvaluatedContext()) return SLCT_NotALiteral; tryAgain: @@ -6542,7 +6543,7 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg, Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); } return SLCT_NotALiteral; case Stmt::BinaryConditionalOperatorClass: @@ -6577,7 +6578,7 @@ static StringLiteralCheckType checkFormatStringExpr( Left = checkFormatStringExpr( S, ReferenceFormatString, C->getTrueExpr(), Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); if (Left == SLCT_NotALiteral || !CheckRight) { return Left; } @@ -6586,7 +6587,7 @@ static StringLiteralCheckType checkFormatStringExpr( StringLiteralCheckType Right = checkFormatStringExpr( S, ReferenceFormatString, C->getFalseExpr(), Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); return (CheckLeft && Left < Right) ? Left : Right; } @@ -6635,10 +6636,11 @@ static StringLiteralCheckType checkFormatStringExpr( if (InitList->isStringLiteralInit()) Init = InitList->getInit(0)->IgnoreParenImpCasts(); } - return checkFormatStringExpr( - S, ReferenceFormatString, Init, Args, APK, format_idx, - firstDataArg, Type, CallType, - /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, Offset); + return checkFormatStringExpr(S, ReferenceFormatString, Init, Args, + APK, format_idx, firstDataArg, Type, + CallType, + /*InFunctionCall*/ false, CheckedVarArgs, + UncoveredArg, Offset, CallerParamIdx); } } @@ -6690,6 +6692,8 @@ static StringLiteralCheckType checkFormatStringExpr( // format arguments, in all cases. // if (const auto *PV = dyn_cast(VD)) { + if (CallerParamIdx) + *CallerParamIdx = PV->getFunctionScopeIndex(); if (const auto *D = dyn_cast(PV->getDeclContext())) { for (const auto *PVFormatMatches : D->specific_attrs()) { @@ -6715,7 +6719,7 @@ static StringLiteralCheckType checkFormatStringExpr( S, ReferenceFormatString, PVFormatMatches->getFormatString(), Args, APK, format_idx, firstDataArg, Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, - Offset, IgnoreStringsWithoutSpecifiers); + Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); } } @@ -6770,7 +6774,7 @@ static StringLiteralCheckType checkFormatStringExpr( StringLiteralCheckType Result = checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, - Offset, IgnoreStringsWithoutSpecifiers); + Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); if (IsFirst) { CommonResult = Result; IsFirst = false; @@ -6784,10 +6788,11 @@ static StringLiteralCheckType checkFormatStringExpr( if (BuiltinID == Builtin::BI__builtin___CFStringMakeConstantString || BuiltinID == Builtin::BI__builtin___NSStringMakeConstantString) { const Expr *Arg = CE->getArg(0); - return checkFormatStringExpr( - S, ReferenceFormatString, Arg, Args, APK, format_idx, - firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + return checkFormatStringExpr(S, ReferenceFormatString, Arg, Args, APK, + format_idx, firstDataArg, Type, CallType, + InFunctionCall, CheckedVarArgs, + UncoveredArg, Offset, CallerParamIdx, + IgnoreStringsWithoutSpecifiers); } } } @@ -6795,7 +6800,7 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg, Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); return SLCT_NotALiteral; } case Stmt::ObjCMessageExprClass: { @@ -6821,7 +6826,7 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, - Offset, IgnoreStringsWithoutSpecifiers); + Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); } } @@ -7001,6 +7006,77 @@ bool Sema::CheckFormatString(const FormatMatchesAttr *Format, return false; } +static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, + unsigned FormatIdx, unsigned FirstArg, + ArrayRef Args, + Sema::FormatArgumentPassingKind APK, + unsigned CallerParamIdx, + SourceLocation Loc) { + const FunctionDecl *Caller = S->getCurFunctionDecl(); + if (!Caller) + return; + + // Find the offset to convert between attribute and parameter indexes. + unsigned CallerArgumentIndexOffset = + hasImplicitObjectParameter(Caller) ? 2 : 1; + + unsigned FirstArgumentIndex = -1; + switch (APK) { + case Sema::FormatArgumentPassingKind::FAPK_Fixed: + case Sema::FormatArgumentPassingKind::FAPK_Variadic: { + // As an extension, clang allows the format attribute on non-variadic + // functions. + // Caller must have fixed arguments to pass them to a fixed or variadic + // function. Try to match caller and callee arguments. If successful, then + // emit a diag with the caller idx, otherwise we can't determine the callee + // arguments. + unsigned NumCalleeArgs = Args.size() - FirstArg; + if (NumCalleeArgs == 0 || Caller->getNumParams() < NumCalleeArgs) { + // There aren't enough arugments in the caller to pass to callee. + return; + } + for (unsigned CalleeIdx = Args.size() - 1, + CallerIdx = Caller->getNumParams() - 1; + CalleeIdx >= FirstArg; --CalleeIdx, --CallerIdx) { + const auto *Arg = + dyn_cast(Args[CalleeIdx]->IgnoreParenCasts()); + if (!Arg) + return; + const auto *Param = dyn_cast(Arg->getDecl()); + if (!Param || Param->getFunctionScopeIndex() != CallerIdx) + return; + } + FirstArgumentIndex = + Caller->getNumParams() + CallerArgumentIndexOffset - NumCalleeArgs; + break; + } + case Sema::FormatArgumentPassingKind::FAPK_VAList: + // Caller arguments are either variadic or a va_list. + FirstArgumentIndex = + Caller->isVariadic() + ? (Caller->getNumParams() + CallerArgumentIndexOffset) + : 0; + break; + case Sema::FormatArgumentPassingKind::FAPK_Elsewhere: + // Args are not passed to the callee. + return; + } + + // Emit the diagnostic and fixit. + unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; + StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); + S->Diag(Loc, diag::warn_missing_format_attribute) + << FormatTypeName << Caller + << FixItHint::CreateInsertion(Caller->getFirstDecl()->getLocation(), + (llvm::Twine("__attribute__((format(") + + FormatTypeName + ", " + + llvm::Twine(FormatStringIndex) + ", " + + llvm::Twine(FirstArgumentIndex) + ")))") + .str()); + S->Diag(Caller->getFirstDecl()->getLocation(), diag::note_callee_decl) + << Caller; +} + bool Sema::CheckFormatArguments(ArrayRef Args, Sema::FormatArgumentPassingKind APK, const StringLiteral *ReferenceFormatString, @@ -7030,11 +7106,12 @@ bool Sema::CheckFormatArguments(ArrayRef Args, // ObjC string uses the same format specifiers as C string, so we can use // the same format string checking logic for both ObjC and C strings. UncoveredArgHandler UncoveredArg; + std::optional CallerParamIdx; StringLiteralCheckType CT = checkFormatStringExpr( *this, ReferenceFormatString, OrigFormatExpr, Args, APK, format_idx, firstDataArg, Type, CallType, /*IsFunctionCall*/ true, CheckedVarArgs, UncoveredArg, - /*no string offset*/ llvm::APSInt(64, false) = 0); + /*no string offset*/ llvm::APSInt(64, false) = 0, &CallerParamIdx); // Generate a diagnostic where an uncovered argument is detected. if (UncoveredArg.hasUncoveredArg()) { @@ -7047,11 +7124,6 @@ bool Sema::CheckFormatArguments(ArrayRef Args, // Literal format string found, check done! return CT == SLCT_CheckedLiteral; - // Strftime is particular as it always uses a single 'time' argument, - // so it is safe to pass a non-literal string. - if (Type == FormatStringType::Strftime) - return false; - // Do not emit diag when the string param is a macro expansion and the // format is either NSString or CFString. This is a hack to prevent // diag when using the NSLocalizedString and CFCopyLocalizedString macros @@ -7061,6 +7133,15 @@ bool Sema::CheckFormatArguments(ArrayRef Args, SourceMgr.isInSystemMacro(FormatLoc)) return false; + if (CallerParamIdx) + CheckMissingFormatAttributes(this, Type, format_idx, firstDataArg, Args, + APK, *CallerParamIdx, Loc); + + // Strftime is particular as it always uses a single 'time' argument, + // so it is safe to pass a non-literal string. + if (Type == FormatStringType::Strftime) + return false; + // If there are no arguments specified, warn with -Wformat-security, otherwise // warn only with -Wformat-nonliteral. if (Args.size() == firstDataArg) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index a9e7b44ac9d73..9849e125889cf 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3572,7 +3572,7 @@ static void handleEnumExtensibilityAttr(Sema &S, Decl *D, } /// Handle __attribute__((format_arg((idx)))) attribute based on -/// http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html +/// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html static void handleFormatArgAttr(Sema &S, Decl *D, const ParsedAttr &AL) { const Expr *IdxExpr = AL.getArgAsExpr(0); ParamIdx Idx; @@ -3771,7 +3771,7 @@ struct FormatAttrCommon { }; /// Handle __attribute__((format(type,idx,firstarg))) attributes based on -/// http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html +/// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html static bool handleFormatAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL, FormatAttrCommon *Info) { // Checks the first two arguments of the attribute; this is shared between diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c new file mode 100644 index 0000000000000..7e5131153f661 --- /dev/null +++ b/clang/test/Sema/attr-format-missing.c @@ -0,0 +1,217 @@ +// 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 + +typedef unsigned long size_t; +typedef long ssize_t; +typedef __builtin_va_list va_list; + +__attribute__((format(printf, 1, 2))) +int printf(const char *, ...); + +__attribute__((format(scanf, 1, 2))) +int scanf(const char *, ...); + +__attribute__((format(printf, 1, 0))) +int vprintf(const char *, va_list); + +__attribute__((format(scanf, 1, 0))) +int vscanf(const char *, va_list); + +__attribute__((format(printf, 2, 0))) +int vsprintf(char *, const char *, va_list); + +struct tm { unsigned i; }; +__attribute__((format(strftime, 3, 0))) +size_t strftime(char *, size_t, const char *, const struct tm *); + +__attribute__((format(strfmon, 3, 4))) +ssize_t strfmon(char *, size_t, const char *, ...); + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))" +void f1(char *out, va_list args) // #f1 +{ + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}} + // expected-note@#f1 {{'f1' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 0)))" +void f2(const char *out, va_list args) // #f2 +{ + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}} + // expected-note@#f2 {{'f2' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +void f3(const char *out, ... /* args */) // #f3 +{ + va_list args; + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}} + // expected-note@#f3 {{'f3' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))" +void f4(const char *out, ... /* args */) // #f4 +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f4'}} + // expected-note@#f4 {{'f4' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 2, 3)))" +__attribute__((format(printf, 1, 3))) +void f5(char *out, const char *format, ... /* args */) // #f5 +{ + va_list args; + vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f5'}} + // expected-note@#f5 {{'f5' declared here}} +} + +__attribute__((format(scanf, 1, 3))) +void f6(char *out, const char *format, ... /* args */) // #f6 +{ + va_list args; + vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f6'}} + // expected-note@#f6 {{'f6' declared here}} +} + +// Ok, out is not passed to print functions. +void f7(char* out, ... /* args */) +{ + va_list args; + + const char *ch = "format"; + vprintf(ch, args); + vprintf("test", args); +} + +// Ok, out is not passed to print functions. +void f8(char *out, va_list args) +{ + const char *ch = "format"; + vprintf(ch, args); + vprintf("test", args); +} + +// Ok, out is not passed to scan functions. +void f9(va_list args) +{ + const char *ch = "format"; + vscanf(ch, args); + vscanf("test", args); +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +void f10(const char *out, ... /* args */) // #f10 +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}} + // expected-note@#f10 {{'f10' declared here}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}} + // expected-note@#f10 {{'f10' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +void f11(const char out[], ... /* args */) // #f11 +{ + va_list args; + char ch[10] = "format"; + vprintf(ch, args); + vsprintf(ch, out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f11'}} + // expected-note@#f11 {{'f11' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))" +void f12(char* out) // #f12 +{ + va_list args; + const char *ch = "format"; + vsprintf(out, ch, args); + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f12'}} + // expected-note@#f12 {{'f12' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +void f13(char *out, ... /* args */) // #f13 +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}} + // expected-note@#f13 {{'f13' declared here}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f13'}} + // expected-note@#f13 {{'f13' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 0)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))" +void f14(char *out, va_list args) // #f14 +{ + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}} + // expected-note@#f14 {{'f14' declared here}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f14'}} + // expected-note@#f14 {{'f14' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))" +void f15(char *out, ... /* args */) // #f15 +{ + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} + // expected-note@#f15 {{'f15' declared here}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} + // expected-note@#f15 {{'f15' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 1, 3)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 2, 3)))" +void f16(char *ch, const char *out, ... /* args */) // #f16 +{ + va_list args; + vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}} + // expected-note@#f16 {{'f16' declared here}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}} + // expected-note@#f16 {{'f16' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +void f17(const char *a, ...) // #f17 +{ + va_list ap; + const char *const b = a; + vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}} + // expected-note@#f17 {{'f17' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18 +{ + printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}} + // expected-note@#f18 {{'f18' declared here}} +} + +void f19(char *fmt, unsigned x, unsigned y, unsigned z) // #f19 +{ + // Arguments are not passed in the same order. + printf(fmt, x, z, y); +} + +void f20(char *out, ... /* args */) +{ + printf(out, 1); // No warning, arguments are not passed to printf. +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strftime, 3, 0)))" +void f21(char *out, const size_t len, const char *format) // #f21 +{ + struct tm tm_arg; + tm_arg.i = 0; + strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'strftime' format attribute to the declaration of 'f21'}} + // expected-note@#f21 {{'f21' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strfmon, 3, 4)))" +void f22(char *out, const size_t len, const char *format, int x, int y) // #f22 +{ + strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'strfmon' format attribute to the declaration of 'f22'}} + // expected-note@#f22 {{'f22' declared here}} +} diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp new file mode 100644 index 0000000000000..90d667531ce0a --- /dev/null +++ b/clang/test/Sema/attr-format-missing.cpp @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -Wmissing-format-attribute -fdiagnostics-parseable-fixits -std=c++23 %s 2>&1 | FileCheck %s + +typedef __SIZE_TYPE__ size_t; +typedef __builtin_va_list va_list; + +__attribute__((__format__(__printf__, 1, 2))) +int printf(const char *, ...); + +__attribute__((__format__(__scanf__, 1, 2))) +int scanf(const char *, ...); + +__attribute__((__format__(__printf__, 1, 0))) +int vprintf(const char *, va_list); + +__attribute__((__format__(__scanf__, 1, 0))) +int vscanf(const char *, va_list); + +__attribute__((__format__(__printf__, 2, 0))) +int vsprintf(char *, const char *, va_list); + +__attribute__((__format__(__printf__, 3, 0))) +int vsnprintf(char *, size_t, const char *, va_list); + +struct S1 +{ + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(scanf, 2, 3)))" + void fn1(const char *out, ... /* args */) // #S1_fn1 + { + va_list args; + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn1'}} + // expected-note@#S1_fn1 {{'fn1' declared here}} + } + + __attribute__((format(printf, 2, 0))) + void print(const char *out, va_list args); + + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))" + void fn2(const char *out, ... /* args */) // #S1_fn2 + { + va_list args; + print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn2'}} + // expected-note@#S1_fn2 {{'fn2' declared here}} + } + + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))" + void fn3(const char *out, va_list args) // #S1_fn3 + { + print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}} + // expected-note@#S1_fn3 {{'fn3' declared here}} + } + + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))" + void fn4(this S1& self, const char *out, ... /* args */) // #S1_fn4 + { + va_list args; + self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn4'}} + // expected-note@#S1_fn4 {{'fn4' declared here}} + } + + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))" + void fn5(this S1& self, const char *out, va_list args) // #S1_fn5 + { + self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn5'}} + // expected-note@#S1_fn5 {{'fn5' declared here}} + } +}; + From c62ec642a3aafd82ae4ec1da0ea46d1a4ca43c60 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Fri, 7 Nov 2025 03:13:04 -0800 Subject: [PATCH 2/7] Switch to portable attribute syntax --- .../clang/Basic/DiagnosticSemaKinds.td | 9 ++- clang/lib/Sema/SemaChecking.cpp | 28 ++++++--- clang/test/Sema/attr-format-missing-gnu.c | 20 ++++++ .../Sema/attr-format-missing-unsupported.c | 17 +++++ clang/test/Sema/attr-format-missing.c | 62 +++++++++---------- clang/test/Sema/attr-format-missing.cpp | 24 +++---- 6 files changed, 102 insertions(+), 58 deletions(-) create mode 100644 clang/test/Sema/attr-format-missing-gnu.c create mode 100644 clang/test/Sema/attr-format-missing-unsupported.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 78c5caf79eab7..37d7e9591d32b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3478,11 +3478,10 @@ def err_format_attribute_result_not : Error<"function does not return %0">; def err_format_attribute_implicit_this_format_string : Error< "format attribute cannot specify the implicit this argument as the format " "string">; -def warn_missing_format_attribute - : Warning<"diagnostic behavior may be improved by adding the '%0' format " - "attribute to the declaration of %1">, - InGroup>, - DefaultIgnore; +def warn_missing_format_attribute : Warning< + "diagnostic behavior may be improved by adding the '%0' format " + "attribute to the declaration of %1">, + InGroup>, DefaultIgnore; def err_callback_attribute_no_callee : Error< "'callback' attribute specifies no callback callee">; def err_callback_attribute_invalid_callee : Error< diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 502e9fd099144..bb11b6077e03f 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -6636,11 +6636,10 @@ static StringLiteralCheckType checkFormatStringExpr( if (InitList->isStringLiteralInit()) Init = InitList->getInit(0)->IgnoreParenImpCasts(); } - return checkFormatStringExpr(S, ReferenceFormatString, Init, Args, - APK, format_idx, firstDataArg, Type, - CallType, - /*InFunctionCall*/ false, CheckedVarArgs, - UncoveredArg, Offset, CallerParamIdx); + return checkFormatStringExpr( + S, ReferenceFormatString, Init, Args, APK, format_idx, + firstDataArg, Type, CallType, /*InFunctionCall=*/false, + CheckedVarArgs, UncoveredArg, Offset, CallerParamIdx); } } @@ -7065,13 +7064,21 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, // Emit the diagnostic and fixit. unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); + StringRef AttrPrefix, AttrSuffix; + if (S->getLangOpts().C23 || S->getLangOpts().CPlusPlus11) { + AttrPrefix = "[[gnu::format("; + AttrSuffix = ")]] "; + } else { + AttrPrefix = "__attribute__((format("; + AttrSuffix = "))) "; + } S->Diag(Loc, diag::warn_missing_format_attribute) << FormatTypeName << Caller - << FixItHint::CreateInsertion(Caller->getFirstDecl()->getLocation(), - (llvm::Twine("__attribute__((format(") + - FormatTypeName + ", " + + << FixItHint::CreateInsertion(Caller->getFirstDecl()->getBeginLoc(), + (AttrPrefix + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) + ", " + - llvm::Twine(FirstArgumentIndex) + ")))") + llvm::Twine(FirstArgumentIndex) + + AttrSuffix) .str()); S->Diag(Caller->getFirstDecl()->getLocation(), diag::note_callee_decl) << Caller; @@ -7133,7 +7140,8 @@ bool Sema::CheckFormatArguments(ArrayRef Args, SourceMgr.isInSystemMacro(FormatLoc)) return false; - if (CallerParamIdx) + const LangOptions &LO = getLangOpts(); + if (CallerParamIdx && (LO.GNUMode || LO.C23 || LO.CPlusPlus11)) CheckMissingFormatAttributes(this, Type, format_idx, firstDataArg, Args, APK, *CallerParamIdx, Loc); diff --git a/clang/test/Sema/attr-format-missing-gnu.c b/clang/test/Sema/attr-format-missing-gnu.c new file mode 100644 index 0000000000000..4ba7d64107d3a --- /dev/null +++ b/clang/test/Sema/attr-format-missing-gnu.c @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=gnu11 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=gnu++98 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -std=gnu11 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -fsyntax-only -x c++ -std=gnu++98 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +typedef unsigned long size_t; +typedef long ssize_t; +typedef __builtin_va_list va_list; + +__attribute__((format(printf, 1, 0))) +int vprintf(const char *, va_list); + +// Test that attribute fixit is specified using the GNU extension format when -std=gnuXY or -std=gnu++XY. + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 1, 0))) " +void f1(char *out, va_list args) // #f1 +{ + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}} + // expected-note@#f1 {{'f1' declared here}} +} diff --git a/clang/test/Sema/attr-format-missing-unsupported.c b/clang/test/Sema/attr-format-missing-unsupported.c new file mode 100644 index 0000000000000..2694ce1b9bb49 --- /dev/null +++ b/clang/test/Sema/attr-format-missing-unsupported.c @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c11 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=c++98 -Wmissing-format-attribute %s + +typedef unsigned long size_t; +typedef long ssize_t; +typedef __builtin_va_list va_list; + +__attribute__((format(printf, 1, 0))) +int vprintf(const char *, va_list); + +// Test that diagnostic is disabled when the standard doesn't support a portable attribute syntax. +// expected-no-diagnostics + +void f1(char *out, va_list args) // #f1 +{ + vprintf(out, args); +} diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c index 7e5131153f661..071054dfa1e6d 100644 --- a/clang/test/Sema/attr-format-missing.c +++ b/clang/test/Sema/attr-format-missing.c @@ -1,47 +1,47 @@ -// 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 -verify -std=c23 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -std=c23 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s typedef unsigned long size_t; typedef long ssize_t; typedef __builtin_va_list va_list; -__attribute__((format(printf, 1, 2))) +[[gnu::format(printf, 1, 2)]] int printf(const char *, ...); -__attribute__((format(scanf, 1, 2))) +[[gnu::format(scanf, 1, 2)]] int scanf(const char *, ...); -__attribute__((format(printf, 1, 0))) +[[gnu::format(printf, 1, 0)]] int vprintf(const char *, va_list); -__attribute__((format(scanf, 1, 0))) +[[gnu::format(scanf, 1, 0)]] int vscanf(const char *, va_list); -__attribute__((format(printf, 2, 0))) +[[gnu::format(printf, 2, 0)]] int vsprintf(char *, const char *, va_list); struct tm { unsigned i; }; -__attribute__((format(strftime, 3, 0))) +[[gnu::format(strftime, 3, 0)]] size_t strftime(char *, size_t, const char *, const struct tm *); -__attribute__((format(strfmon, 3, 4))) +[[gnu::format(strfmon, 3, 4)]] ssize_t strfmon(char *, size_t, const char *, ...); -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " void f1(char *out, va_list args) // #f1 { vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}} // expected-note@#f1 {{'f1' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 0)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] " void f2(const char *out, va_list args) // #f2 { vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}} // expected-note@#f2 {{'f2' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f3(const char *out, ... /* args */) // #f3 { va_list args; @@ -49,7 +49,7 @@ void f3(const char *out, ... /* args */) // #f3 // expected-note@#f3 {{'f3' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " void f4(const char *out, ... /* args */) // #f4 { va_list args; @@ -57,8 +57,8 @@ void f4(const char *out, ... /* args */) // #f4 // expected-note@#f4 {{'f4' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 2, 3)))" -__attribute__((format(printf, 1, 3))) +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] " +[[gnu::format(printf, 1, 3)]] void f5(char *out, const char *format, ... /* args */) // #f5 { va_list args; @@ -66,7 +66,7 @@ void f5(char *out, const char *format, ... /* args */) // #f5 // expected-note@#f5 {{'f5' declared here}} } -__attribute__((format(scanf, 1, 3))) +[[gnu::format(scanf, 1, 3)]] void f6(char *out, const char *format, ... /* args */) // #f6 { va_list args; @@ -100,8 +100,8 @@ void f9(va_list args) vscanf("test", args); } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))" -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f10(const char *out, ... /* args */) // #f10 { va_list args; @@ -111,7 +111,7 @@ void f10(const char *out, ... /* args */) // #f10 // expected-note@#f10 {{'f10' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f11(const char out[], ... /* args */) // #f11 { va_list args; @@ -121,7 +121,7 @@ void f11(const char out[], ... /* args */) // #f11 // expected-note@#f11 {{'f11' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " void f12(char* out) // #f12 { va_list args; @@ -131,8 +131,8 @@ void f12(char* out) // #f12 // expected-note@#f12 {{'f12' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 2)))" -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f13(char *out, ... /* args */) // #f13 { va_list args; @@ -142,8 +142,8 @@ void f13(char *out, ... /* args */) // #f13 // expected-note@#f13 {{'f13' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(scanf, 1, 0)))" -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 0)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] " +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " void f14(char *out, va_list args) // #f14 { vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}} @@ -152,7 +152,7 @@ void f14(char *out, va_list args) // #f14 // expected-note@#f14 {{'f14' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(scanf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " void f15(char *out, ... /* args */) // #f15 { va_list args; @@ -162,8 +162,8 @@ void f15(char *out, ... /* args */) // #f15 // expected-note@#f15 {{'f15' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:6-[[@LINE+2]]:6}:"__attribute__((format(printf, 1, 3)))" -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 2, 3)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 1, 3)]] " +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] " void f16(char *ch, const char *out, ... /* args */) // #f16 { va_list args; @@ -173,7 +173,7 @@ void f16(char *ch, const char *out, ... /* args */) // #f16 // expected-note@#f16 {{'f16' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f17(const char *a, ...) // #f17 { va_list ap; @@ -182,7 +182,7 @@ void f17(const char *a, ...) // #f17 // expected-note@#f17 {{'f17' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(printf, 1, 2)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18 { printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}} @@ -200,7 +200,7 @@ void f20(char *out, ... /* args */) printf(out, 1); // No warning, arguments are not passed to printf. } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strftime, 3, 0)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strftime, 3, 0)]] " void f21(char *out, const size_t len, const char *format) // #f21 { struct tm tm_arg; @@ -209,7 +209,7 @@ void f21(char *out, const size_t len, const char *format) // #f21 // expected-note@#f21 {{'f21' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:6-[[@LINE+1]]:6}:"__attribute__((format(strfmon, 3, 4)))" +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] " void f22(char *out, const size_t len, const char *format, int x, int y) // #f22 { strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'strfmon' format attribute to the declaration of 'f22'}} diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp index 90d667531ce0a..23f643bb80058 100644 --- a/clang/test/Sema/attr-format-missing.cpp +++ b/clang/test/Sema/attr-format-missing.cpp @@ -4,27 +4,27 @@ typedef __SIZE_TYPE__ size_t; typedef __builtin_va_list va_list; -__attribute__((__format__(__printf__, 1, 2))) +[[gnu::format(printf, 1, 2)]] int printf(const char *, ...); -__attribute__((__format__(__scanf__, 1, 2))) +[[gnu::format(scanf, 1, 2)]] int scanf(const char *, ...); -__attribute__((__format__(__printf__, 1, 0))) +[[gnu::format(printf, 1, 0)]] int vprintf(const char *, va_list); -__attribute__((__format__(__scanf__, 1, 0))) +[[gnu::format(scanf, 1, 0)]] int vscanf(const char *, va_list); -__attribute__((__format__(__printf__, 2, 0))) +[[gnu::format(printf, 2, 0)]] int vsprintf(char *, const char *, va_list); -__attribute__((__format__(__printf__, 3, 0))) +[[gnu::format(printf, 3, 0)]] int vsnprintf(char *, size_t, const char *, va_list); struct S1 { - // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(scanf, 2, 3)))" + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(scanf, 2, 3)]] " void fn1(const char *out, ... /* args */) // #S1_fn1 { va_list args; @@ -32,10 +32,10 @@ struct S1 // expected-note@#S1_fn1 {{'fn1' declared here}} } - __attribute__((format(printf, 2, 0))) + [[gnu::format(printf, 2, 0)]] void print(const char *out, va_list args); - // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))" + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 3)]] " void fn2(const char *out, ... /* args */) // #S1_fn2 { va_list args; @@ -43,14 +43,14 @@ struct S1 // expected-note@#S1_fn2 {{'fn2' declared here}} } - // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))" + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] " void fn3(const char *out, va_list args) // #S1_fn3 { print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}} // expected-note@#S1_fn3 {{'fn3' declared here}} } - // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 3)))" + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 3)]] " void fn4(this S1& self, const char *out, ... /* args */) // #S1_fn4 { va_list args; @@ -58,7 +58,7 @@ struct S1 // expected-note@#S1_fn4 {{'fn4' declared here}} } - // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:8-[[@LINE+1]]:8}:"__attribute__((format(printf, 2, 0)))" + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] " void fn5(this S1& self, const char *out, va_list args) // #S1_fn5 { self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn5'}} From a0a4ca6383a88215b8edc9d2814ee0694a28f406 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Mon, 17 Nov 2025 00:52:50 -0800 Subject: [PATCH 3/7] Address review comments - Check now works or Objective-C - Add attribute arguments to diagnostic message - Rename CallerParamIdx to CallerFormatParamIdx - Fix typo --- .../clang/Basic/DiagnosticSemaKinds.td | 4 +- clang/lib/Sema/SemaChecking.cpp | 118 +++++++++--------- clang/test/Sema/attr-format-missing-gnu.c | 2 +- clang/test/Sema/attr-format-missing.c | 57 ++++----- clang/test/Sema/attr-format-missing.cpp | 10 +- clang/test/Sema/attr-format-missing.m | 31 +++++ 6 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 clang/test/Sema/attr-format-missing.m diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 37d7e9591d32b..c42ee4ddb4e65 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3479,8 +3479,8 @@ def err_format_attribute_implicit_this_format_string : Error< "format attribute cannot specify the implicit this argument as the format " "string">; def warn_missing_format_attribute : Warning< - "diagnostic behavior may be improved by adding the '%0' format " - "attribute to the declaration of %1">, + "diagnostic behavior may be improved by adding the '%0' attribute to the " + "declaration of %1">, InGroup>, DefaultIgnore; def err_callback_attribute_no_callee : Error< "'callback' attribute specifies no callback callee">; diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index bb11b6077e03f..b78aec68bec62 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -17,6 +17,7 @@ #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/AttrIterator.h" +#include "clang/AST/Attrs.inc" #include "clang/AST/CharUnits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" @@ -6511,14 +6512,16 @@ static const Expr *maybeConstEvalStringLiteral(ASTContext &Context, // If this function returns false on the arguments to a function expecting a // format string, we will usually need to emit a warning. // True string literals are then checked by CheckFormatString. -static StringLiteralCheckType checkFormatStringExpr( - Sema &S, const StringLiteral *ReferenceFormatString, const Expr *E, - ArrayRef Args, Sema::FormatArgumentPassingKind APK, - unsigned format_idx, unsigned firstDataArg, FormatStringType Type, - VariadicCallType CallType, bool InFunctionCall, - llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg, - llvm::APSInt Offset, std::optional *CallerParamIdx = nullptr, - bool IgnoreStringsWithoutSpecifiers = false) { +static StringLiteralCheckType +checkFormatStringExpr(Sema &S, const StringLiteral *ReferenceFormatString, + const Expr *E, ArrayRef Args, + Sema::FormatArgumentPassingKind APK, unsigned format_idx, + unsigned firstDataArg, FormatStringType Type, + VariadicCallType CallType, bool InFunctionCall, + llvm::SmallBitVector &CheckedVarArgs, + UncoveredArgHandler &UncoveredArg, llvm::APSInt Offset, + std::optional *CallerFormatParamIdx = nullptr, + bool IgnoreStringsWithoutSpecifiers = false) { if (S.isConstantEvaluatedContext()) return SLCT_NotALiteral; tryAgain: @@ -6540,10 +6543,11 @@ static StringLiteralCheckType checkFormatStringExpr( case Stmt::InitListExprClass: // Handle expressions like {"foobar"}. if (const clang::Expr *SLE = maybeConstEvalStringLiteral(S.Context, E)) { - return checkFormatStringExpr( - S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg, - Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, - UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + return checkFormatStringExpr(S, ReferenceFormatString, SLE, Args, APK, + format_idx, firstDataArg, Type, CallType, + /*InFunctionCall*/ false, CheckedVarArgs, + UncoveredArg, Offset, CallerFormatParamIdx, + IgnoreStringsWithoutSpecifiers); } return SLCT_NotALiteral; case Stmt::BinaryConditionalOperatorClass: @@ -6575,10 +6579,11 @@ static StringLiteralCheckType checkFormatStringExpr( if (!CheckLeft) Left = SLCT_UncheckedLiteral; else { - Left = checkFormatStringExpr( - S, ReferenceFormatString, C->getTrueExpr(), Args, APK, format_idx, - firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + Left = checkFormatStringExpr(S, ReferenceFormatString, C->getTrueExpr(), + Args, APK, format_idx, firstDataArg, Type, + CallType, InFunctionCall, CheckedVarArgs, + UncoveredArg, Offset, CallerFormatParamIdx, + IgnoreStringsWithoutSpecifiers); if (Left == SLCT_NotALiteral || !CheckRight) { return Left; } @@ -6587,7 +6592,8 @@ static StringLiteralCheckType checkFormatStringExpr( StringLiteralCheckType Right = checkFormatStringExpr( S, ReferenceFormatString, C->getFalseExpr(), Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + UncoveredArg, Offset, CallerFormatParamIdx, + IgnoreStringsWithoutSpecifiers); return (CheckLeft && Left < Right) ? Left : Right; } @@ -6639,7 +6645,7 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, Init, Args, APK, format_idx, firstDataArg, Type, CallType, /*InFunctionCall=*/false, - CheckedVarArgs, UncoveredArg, Offset, CallerParamIdx); + CheckedVarArgs, UncoveredArg, Offset, CallerFormatParamIdx); } } @@ -6691,8 +6697,8 @@ static StringLiteralCheckType checkFormatStringExpr( // format arguments, in all cases. // if (const auto *PV = dyn_cast(VD)) { - if (CallerParamIdx) - *CallerParamIdx = PV->getFunctionScopeIndex(); + if (CallerFormatParamIdx) + *CallerFormatParamIdx = PV->getFunctionScopeIndex(); if (const auto *D = dyn_cast(PV->getDeclContext())) { for (const auto *PVFormatMatches : D->specific_attrs()) { @@ -6718,7 +6724,7 @@ static StringLiteralCheckType checkFormatStringExpr( S, ReferenceFormatString, PVFormatMatches->getFormatString(), Args, APK, format_idx, firstDataArg, Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, - Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers); } } @@ -6773,7 +6779,7 @@ static StringLiteralCheckType checkFormatStringExpr( StringLiteralCheckType Result = checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, - Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers); if (IsFirst) { CommonResult = Result; IsFirst = false; @@ -6787,19 +6793,20 @@ static StringLiteralCheckType checkFormatStringExpr( if (BuiltinID == Builtin::BI__builtin___CFStringMakeConstantString || BuiltinID == Builtin::BI__builtin___NSStringMakeConstantString) { const Expr *Arg = CE->getArg(0); - return checkFormatStringExpr(S, ReferenceFormatString, Arg, Args, APK, - format_idx, firstDataArg, Type, CallType, - InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, CallerParamIdx, - IgnoreStringsWithoutSpecifiers); + return checkFormatStringExpr( + S, ReferenceFormatString, Arg, Args, APK, format_idx, + firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, + UncoveredArg, Offset, CallerFormatParamIdx, + IgnoreStringsWithoutSpecifiers); } } } if (const Expr *SLE = maybeConstEvalStringLiteral(S.Context, E)) - return checkFormatStringExpr( - S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg, - Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, - UncoveredArg, Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + return checkFormatStringExpr(S, ReferenceFormatString, SLE, Args, APK, + format_idx, firstDataArg, Type, CallType, + /*InFunctionCall*/ false, CheckedVarArgs, + UncoveredArg, Offset, CallerFormatParamIdx, + IgnoreStringsWithoutSpecifiers); return SLCT_NotALiteral; } case Stmt::ObjCMessageExprClass: { @@ -6825,7 +6832,7 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, - Offset, CallerParamIdx, IgnoreStringsWithoutSpecifiers); + Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers); } } @@ -7011,10 +7018,12 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, Sema::FormatArgumentPassingKind APK, unsigned CallerParamIdx, SourceLocation Loc) { - const FunctionDecl *Caller = S->getCurFunctionDecl(); + NamedDecl *Caller = S->getCurFunctionOrMethodDecl(); if (!Caller) return; + unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller); + // Find the offset to convert between attribute and parameter indexes. unsigned CallerArgumentIndexOffset = hasImplicitObjectParameter(Caller) ? 2 : 1; @@ -7030,12 +7039,11 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, // emit a diag with the caller idx, otherwise we can't determine the callee // arguments. unsigned NumCalleeArgs = Args.size() - FirstArg; - if (NumCalleeArgs == 0 || Caller->getNumParams() < NumCalleeArgs) { - // There aren't enough arugments in the caller to pass to callee. + if (NumCalleeArgs == 0 || NumCallerParams < NumCalleeArgs) { + // There aren't enough arguments in the caller to pass to callee. return; } - for (unsigned CalleeIdx = Args.size() - 1, - CallerIdx = Caller->getNumParams() - 1; + for (unsigned CalleeIdx = Args.size() - 1, CallerIdx = NumCallerParams - 1; CalleeIdx >= FirstArg; --CalleeIdx, --CallerIdx) { const auto *Arg = dyn_cast(Args[CalleeIdx]->IgnoreParenCasts()); @@ -7046,15 +7054,14 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, return; } FirstArgumentIndex = - Caller->getNumParams() + CallerArgumentIndexOffset - NumCalleeArgs; + NumCallerParams + CallerArgumentIndexOffset - NumCalleeArgs; break; } case Sema::FormatArgumentPassingKind::FAPK_VAList: // Caller arguments are either variadic or a va_list. - FirstArgumentIndex = - Caller->isVariadic() - ? (Caller->getNumParams() + CallerArgumentIndexOffset) - : 0; + FirstArgumentIndex = isFunctionOrMethodVariadic(Caller) + ? (NumCallerParams + CallerArgumentIndexOffset) + : 0; break; case Sema::FormatArgumentPassingKind::FAPK_Elsewhere: // Args are not passed to the callee. @@ -7063,25 +7070,24 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, // Emit the diagnostic and fixit. unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; - StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); StringRef AttrPrefix, AttrSuffix; if (S->getLangOpts().C23 || S->getLangOpts().CPlusPlus11) { - AttrPrefix = "[[gnu::format("; - AttrSuffix = ")]] "; + AttrPrefix = "[[gnu::"; + AttrSuffix = "]] "; } else { - AttrPrefix = "__attribute__((format("; - AttrSuffix = "))) "; + AttrPrefix = "__attribute__(("; + AttrSuffix = ")) "; } + StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); + std::string Attr = + ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) + + ", " + llvm::Twine(FirstArgumentIndex) + ")") + .str(); S->Diag(Loc, diag::warn_missing_format_attribute) - << FormatTypeName << Caller - << FixItHint::CreateInsertion(Caller->getFirstDecl()->getBeginLoc(), - (AttrPrefix + FormatTypeName + ", " + - llvm::Twine(FormatStringIndex) + ", " + - llvm::Twine(FirstArgumentIndex) + - AttrSuffix) - .str()); - S->Diag(Caller->getFirstDecl()->getLocation(), diag::note_callee_decl) - << Caller; + << Attr << Caller + << FixItHint::CreateInsertion(Caller->getBeginLoc(), + (AttrPrefix + Attr + AttrSuffix).str()); + S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller; } bool Sema::CheckFormatArguments(ArrayRef Args, diff --git a/clang/test/Sema/attr-format-missing-gnu.c b/clang/test/Sema/attr-format-missing-gnu.c index 4ba7d64107d3a..83f20b8b85a7b 100644 --- a/clang/test/Sema/attr-format-missing-gnu.c +++ b/clang/test/Sema/attr-format-missing-gnu.c @@ -15,6 +15,6 @@ int vprintf(const char *, va_list); // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 1, 0))) " void f1(char *out, va_list args) // #f1 { - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f1'}} // expected-note@#f1 {{'f1' declared here}} } diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c index 071054dfa1e6d..5d4eeac18773b 100644 --- a/clang/test/Sema/attr-format-missing.c +++ b/clang/test/Sema/attr-format-missing.c @@ -28,33 +28,33 @@ size_t strftime(char *, size_t, const char *, const struct tm *); ssize_t strfmon(char *, size_t, const char *, ...); // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " -void f1(char *out, va_list args) // #f1 +void f1(const char *fmt, va_list args) // #f1 { - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}} + vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f1'}} // expected-note@#f1 {{'f1' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] " -void f2(const char *out, va_list args) // #f2 +void f2(const char *fmt, va_list args) // #f2 { - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f2'}} - // expected-note@#f2 {{'f2' declared here}} + vscanf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 0)' attribute to the declaration of 'f2'}} + // expected-note@#f2 {{'f2' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " -void f3(const char *out, ... /* args */) // #f3 +void f3(const char *fmt, ... /* args */) // #f3 { va_list args; - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}} + vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f3'}} // expected-note@#f3 {{'f3' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " -void f4(const char *out, ... /* args */) // #f4 +void f4(const char *fmt, ... /* args */) // #f4 { va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f4'}} - // expected-note@#f4 {{'f4' declared here}} + vscanf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f4'}} + // expected-note@#f4 {{'f4' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] " @@ -62,15 +62,16 @@ void f4(const char *out, ... /* args */) // #f4 void f5(char *out, const char *format, ... /* args */) // #f5 { va_list args; - vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f5'}} + vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f5'}} // expected-note@#f5 {{'f5' declared here}} } +// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] " [[gnu::format(scanf, 1, 3)]] void f6(char *out, const char *format, ... /* args */) // #f6 { va_list args; - vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f6'}} + vsprintf(out, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f6'}} // expected-note@#f6 {{'f6' declared here}} } @@ -105,9 +106,9 @@ void f9(va_list args) void f10(const char *out, ... /* args */) // #f10 { va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f10'}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f10'}} // expected-note@#f10 {{'f10' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f10'}} // expected-note@#f10 {{'f10' declared here}} } @@ -117,7 +118,7 @@ void f11(const char out[], ... /* args */) // #f11 va_list args; char ch[10] = "format"; vprintf(ch, args); - vsprintf(ch, out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f11'}} + vsprintf(ch, out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f11'}} // expected-note@#f11 {{'f11' declared here}} } @@ -127,7 +128,7 @@ void f12(char* out) // #f12 va_list args; const char *ch = "format"; vsprintf(out, ch, args); - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f12'}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f12'}} // expected-note@#f12 {{'f12' declared here}} } @@ -136,9 +137,9 @@ void f12(char* out) // #f12 void f13(char *out, ... /* args */) // #f13 { va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f13'}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f13'}} // expected-note@#f13 {{'f13' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f13'}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f13'}} // expected-note@#f13 {{'f13' declared here}} } @@ -146,9 +147,9 @@ void f13(char *out, ... /* args */) // #f13 // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " void f14(char *out, va_list args) // #f14 { - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 0)' attribute to the declaration of 'f14'}} // expected-note@#f14 {{'f14' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f14'}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f14'}} // expected-note@#f14 {{'f14' declared here}} } @@ -156,9 +157,9 @@ void f14(char *out, va_list args) // #f14 void f15(char *out, ... /* args */) // #f15 { va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}} // expected-note@#f15 {{'f15' declared here}} - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f15'}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}} // expected-note@#f15 {{'f15' declared here}} } @@ -167,9 +168,9 @@ void f15(char *out, ... /* args */) // #f15 void f16(char *ch, const char *out, ... /* args */) // #f16 { va_list args; - vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}} + vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'f16'}} // expected-note@#f16 {{'f16' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f16'}} // expected-note@#f16 {{'f16' declared here}} } @@ -178,14 +179,14 @@ void f17(const char *a, ...) // #f17 { va_list ap; const char *const b = a; - vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f17'}} + vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f17'}} // expected-note@#f17 {{'f17' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18 { - printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f18'}} + printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f18'}} // expected-note@#f18 {{'f18' declared here}} } @@ -205,13 +206,13 @@ void f21(char *out, const size_t len, const char *format) // #f21 { struct tm tm_arg; tm_arg.i = 0; - strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'strftime' format attribute to the declaration of 'f21'}} + strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strftime, 3, 0)' attribute to the declaration of 'f21'}} // expected-note@#f21 {{'f21' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] " void f22(char *out, const size_t len, const char *format, int x, int y) // #f22 { - strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'strfmon' format attribute to the declaration of 'f22'}} + strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f22'}} // expected-note@#f22 {{'f22' declared here}} } diff --git a/clang/test/Sema/attr-format-missing.cpp b/clang/test/Sema/attr-format-missing.cpp index 23f643bb80058..06b5ad1a68f3c 100644 --- a/clang/test/Sema/attr-format-missing.cpp +++ b/clang/test/Sema/attr-format-missing.cpp @@ -28,7 +28,7 @@ struct S1 void fn1(const char *out, ... /* args */) // #S1_fn1 { va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'fn1'}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 2, 3)' attribute to the declaration of 'fn1'}} // expected-note@#S1_fn1 {{'fn1' declared here}} } @@ -39,14 +39,14 @@ struct S1 void fn2(const char *out, ... /* args */) // #S1_fn2 { va_list args; - print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn2'}} + print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'fn2'}} // expected-note@#S1_fn2 {{'fn2' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] " void fn3(const char *out, va_list args) // #S1_fn3 { - print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn3'}} + print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'fn3'}} // expected-note@#S1_fn3 {{'fn3' declared here}} } @@ -54,14 +54,14 @@ struct S1 void fn4(this S1& self, const char *out, ... /* args */) // #S1_fn4 { va_list args; - self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn4'}} + self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'fn4'}} // expected-note@#S1_fn4 {{'fn4' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:3-[[@LINE+1]]:3}:"{{\[\[}}gnu::format(printf, 2, 0)]] " void fn5(this S1& self, const char *out, va_list args) // #S1_fn5 { - self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'fn5'}} + self.print(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'fn5'}} // expected-note@#S1_fn5 {{'fn5' declared here}} } }; diff --git a/clang/test/Sema/attr-format-missing.m b/clang/test/Sema/attr-format-missing.m new file mode 100644 index 0000000000000..3896bcef4634a --- /dev/null +++ b/clang/test/Sema/attr-format-missing.m @@ -0,0 +1,31 @@ +// 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 + +#include + +@interface Print +-(void)printf:(const char *)fmt, ... __attribute__((format(printf, 1, 2))); +-(void)vprintf:(const char *)fmt list:(va_list)ap __attribute__((format(printf, 1, 0))); +@end + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) " +void f1(Print *p, const char *fmt, int x) // #f1 +{ + [p printf:fmt, x]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f1'}} + // expected-note@#f1 {{'f1' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) " +void f2(Print *p, const char *fmt, ...) // #f2 +{ + va_list ap; + [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f2'}} + // expected-note@#f2 {{'f2' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 0))) " +void f3(Print *p, const char *fmt, va_list ap) // #f3 +{ + [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'f3'}} + // expected-note@#f3 {{'f3' declared here}} +} From ba5e3d8cee04ba236495c8e440688533f93f928d Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Mon, 17 Nov 2025 00:55:32 -0800 Subject: [PATCH 4/7] Emit fixit only when supported --- clang/lib/Sema/SemaChecking.cpp | 37 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index b78aec68bec62..5dbac022d7c4d 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -7070,23 +7070,28 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, // Emit the diagnostic and fixit. unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; - StringRef AttrPrefix, AttrSuffix; - if (S->getLangOpts().C23 || S->getLangOpts().CPlusPlus11) { - AttrPrefix = "[[gnu::"; - AttrSuffix = "]] "; - } else { - AttrPrefix = "__attribute__(("; - AttrSuffix = ")) "; - } StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); - std::string Attr = - ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) + - ", " + llvm::Twine(FirstArgumentIndex) + ")") - .str(); - S->Diag(Loc, diag::warn_missing_format_attribute) - << Attr << Caller - << FixItHint::CreateInsertion(Caller->getBeginLoc(), - (AttrPrefix + Attr + AttrSuffix).str()); + { + std::string Attr = + ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) + + ", " + llvm::Twine(FirstArgumentIndex) + ")") + .str(); + auto DB = S->Diag(Loc, diag::warn_missing_format_attribute) + << Attr << Caller; + const LangOptions &LO = S->getLangOpts(); + StringRef AttrPrefix, AttrSuffix; + if (LO.C23 || LO.CPlusPlus11) { + AttrPrefix = "[[gnu::"; + AttrSuffix = "]] "; + } else if (LO.ObjC || LO.GNUMode) { + AttrPrefix = "__attribute__(("; + AttrSuffix = ")) "; + } + if (!AttrPrefix.empty()) { + DB << FixItHint::CreateInsertion(Caller->getBeginLoc(), + (AttrPrefix + Attr + AttrSuffix).str()); + } + } S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller; } From c1b001a8e4fb5ba5842e6daf438d94d43abfcaa8 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Tue, 18 Nov 2025 02:01:28 -0800 Subject: [PATCH 5/7] Fix ObjC fixit location --- clang/lib/Sema/SemaChecking.cpp | 49 +++++++++++++--------- clang/test/Sema/attr-format-missing.c | 10 +++++ clang/test/Sema/attr-format-missing.m | 59 ++++++++++++++++++++++----- 3 files changed, 88 insertions(+), 30 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 5dbac022d7c4d..1c756e65fa9b7 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -7021,6 +7021,7 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, NamedDecl *Caller = S->getCurFunctionOrMethodDecl(); if (!Caller) return; + Caller = dyn_cast(Caller->getCanonicalDecl()); unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller); @@ -7069,29 +7070,39 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, } // Emit the diagnostic and fixit. - unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; - StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); - { - std::string Attr = - ("format(" + FormatTypeName + ", " + llvm::Twine(FormatStringIndex) + - ", " + llvm::Twine(FirstArgumentIndex) + ")") - .str(); + do { + unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; + StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); + std::string Attr, Fixit; + llvm::raw_string_ostream(Attr) + << "format(" << FormatTypeName << ", " << FormatStringIndex << ", " + << FirstArgumentIndex << ")"; auto DB = S->Diag(Loc, diag::warn_missing_format_attribute) << Attr << Caller; - const LangOptions &LO = S->getLangOpts(); - StringRef AttrPrefix, AttrSuffix; - if (LO.C23 || LO.CPlusPlus11) { - AttrPrefix = "[[gnu::"; - AttrSuffix = "]] "; - } else if (LO.ObjC || LO.GNUMode) { - AttrPrefix = "__attribute__(("; - AttrSuffix = ")) "; + + SourceLocation SL; + llvm::raw_string_ostream IS(Fixit); + // The attribute goes at the start of the declaration in C/C++ functions + // and methods, but after the declaration for Objective-C methods. + if (isa(Caller)) { + IS << ' '; + SL = Caller->getEndLoc(); } - if (!AttrPrefix.empty()) { - DB << FixItHint::CreateInsertion(Caller->getBeginLoc(), - (AttrPrefix + Attr + AttrSuffix).str()); + const LangOptions &LO = S->getLangOpts(); + if (LO.C23 || LO.CPlusPlus11) + IS << "[[gnu::" << Attr << "]]"; + else if (LO.ObjC || LO.GNUMode) + IS << "__attribute__((" << Attr << "))"; + else + break; + if (!isa(Caller)) { + IS << ' '; + SL = Caller->getBeginLoc(); } - } + IS.flush(); + + DB << FixItHint::CreateInsertion(SL, Fixit); + } while (false); S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller; } diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c index 5d4eeac18773b..10a8b2e193447 100644 --- a/clang/test/Sema/attr-format-missing.c +++ b/clang/test/Sema/attr-format-missing.c @@ -216,3 +216,13 @@ void f22(char *out, const size_t len, const char *format, int x, int y) // #f22 strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f22'}} // expected-note@#f22 {{'f22' declared here}} } + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " +void f23(const char *fmt, ... /* args */); // #f23 + +void f23(const char *fmt, ... /* args */) +{ + va_list args; + vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f23'}} + // expected-note@#f23 {{'f23' declared here}} +} diff --git a/clang/test/Sema/attr-format-missing.m b/clang/test/Sema/attr-format-missing.m index 3896bcef4634a..159b4bad47e98 100644 --- a/clang/test/Sema/attr-format-missing.m +++ b/clang/test/Sema/attr-format-missing.m @@ -3,29 +3,66 @@ #include -@interface Print +@interface PrintCallee -(void)printf:(const char *)fmt, ... __attribute__((format(printf, 1, 2))); -(void)vprintf:(const char *)fmt list:(va_list)ap __attribute__((format(printf, 1, 0))); @end // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) " -void f1(Print *p, const char *fmt, int x) // #f1 +void f1(PrintCallee *p, const char *fmt, int x) // #f1 { - [p printf:fmt, x]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f1'}} - // expected-note@#f1 {{'f1' declared here}} + [p printf:fmt, x]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f1'}} + // expected-note@#f1 {{'f1' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) " -void f2(Print *p, const char *fmt, ...) // #f2 +void f2(PrintCallee *p, const char *fmt, ...) // #f2 { - va_list ap; - [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f2'}} - // expected-note@#f2 {{'f2' declared here}} + va_list ap; + [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f2'}} + // expected-note@#f2 {{'f2' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 0))) " -void f3(Print *p, const char *fmt, va_list ap) // #f3 +void f3(PrintCallee *p, const char *fmt, va_list ap) // #f3 { - [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'f3'}} - // expected-note@#f3 {{'f3' declared here}} + [p vprintf:fmt list:ap]; // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 0)' attribute to the declaration of 'f3'}} + // expected-note@#f3 {{'f3' declared here}} } + +__attribute__((format(printf, 1, 2))) +int printf(const char *, ...); +__attribute__((format(printf, 1, 0))) +int vprintf(const char *, va_list ap); + +__attribute__((objc_root_class)) +@interface PrintCaller +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:33-[[@LINE+1]]:33}:" __attribute__((format(printf, 1, 2)))" +-(void)f4:(const char *)fmt, ...; // #f4 + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:45-[[@LINE+1]]:45}:" __attribute__((format(printf, 1, 0)))" +-(void)f5:(const char *)fmt list:(va_list)ap; // #f5 + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:37-[[@LINE+1]]:37}:" __attribute__((format(printf, 1, 2)))" +-(void)f6:(const char *)fmt x:(int)x; // #f6 +@end + +@implementation PrintCaller +-(void)f4:(const char *)fmt, ... { + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f4:'}} + // expected-note@#f4 {{'f4:' declared here}} + va_end(ap); +} + +-(void)f5:(const char *)fmt list:(va_list)ap { + vprintf(fmt, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f5:list:'}} + // expected-note@#f5 {{'f5:list:' declared here}} +} + +-(void)f6:(const char *)fmt x:(int)x { + printf(fmt, x); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f6:x:'}} + // expected-note@#f6 {{'f6:x:' declared here}} +} +@end From 948c282a11daa9b32ed76234c5b32ffedc3eca43 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Tue, 18 Nov 2025 06:22:38 -0800 Subject: [PATCH 6/7] Suggest format_matches when applicable --- clang/lib/Sema/SemaChecking.cpp | 54 +++++++++++++++++++-------- clang/test/Sema/attr-format-missing.c | 11 ++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 1c756e65fa9b7..93b0d32d0621c 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -41,6 +41,7 @@ #include "clang/AST/TypeLoc.h" #include "clang/AST/UnresolvedSet.h" #include "clang/Basic/AddressSpaces.h" +#include "clang/Basic/CharInfo.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/IdentifierTable.h" @@ -7012,12 +7013,24 @@ bool Sema::CheckFormatString(const FormatMatchesAttr *Format, return false; } -static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, - unsigned FormatIdx, unsigned FirstArg, - ArrayRef Args, - Sema::FormatArgumentPassingKind APK, - unsigned CallerParamIdx, - SourceLocation Loc) { +std::string escapeFormatString(StringRef Input) { + std::string Result; + llvm::raw_string_ostream OS(Result); + for (char C : Input) { + StringRef Escaped = escapeCStyle(C); + if (Escaped.empty()) + OS << C; + else + OS << Escaped; + } + return Result; +} + +static void CheckMissingFormatAttributes( + Sema *S, ArrayRef Args, Sema::FormatArgumentPassingKind APK, + const StringLiteral *ReferenceFormatString, unsigned FormatIdx, + unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx, + SourceLocation Loc) { NamedDecl *Caller = S->getCurFunctionOrMethodDecl(); if (!Caller) return; @@ -7039,13 +7052,13 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, // function. Try to match caller and callee arguments. If successful, then // emit a diag with the caller idx, otherwise we can't determine the callee // arguments. - unsigned NumCalleeArgs = Args.size() - FirstArg; + unsigned NumCalleeArgs = Args.size() - FirstDataArg; if (NumCalleeArgs == 0 || NumCallerParams < NumCalleeArgs) { // There aren't enough arguments in the caller to pass to callee. return; } for (unsigned CalleeIdx = Args.size() - 1, CallerIdx = NumCallerParams - 1; - CalleeIdx >= FirstArg; --CalleeIdx, --CallerIdx) { + CalleeIdx >= FirstDataArg; --CalleeIdx, --CallerIdx) { const auto *Arg = dyn_cast(Args[CalleeIdx]->IgnoreParenCasts()); if (!Arg) @@ -7065,8 +7078,10 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, : 0; break; case Sema::FormatArgumentPassingKind::FAPK_Elsewhere: - // Args are not passed to the callee. - return; + // The callee has a format_matches attribute. We will emit that instead. + if (!ReferenceFormatString) + return; + break; } // Emit the diagnostic and fixit. @@ -7074,12 +7089,18 @@ static void CheckMissingFormatAttributes(Sema *S, FormatStringType FormatType, unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); std::string Attr, Fixit; - llvm::raw_string_ostream(Attr) - << "format(" << FormatTypeName << ", " << FormatStringIndex << ", " - << FirstArgumentIndex << ")"; + if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) + llvm::raw_string_ostream(Attr) + << "format(" << FormatTypeName << ", " << FormatStringIndex << ", " + << FirstArgumentIndex << ")"; + else + llvm::raw_string_ostream(Attr) + << "format_matches(" << FormatTypeName << ", " << FormatStringIndex + << ", \"" << escapeFormatString(ReferenceFormatString->getString()) + << "\")"; auto DB = S->Diag(Loc, diag::warn_missing_format_attribute) << Attr << Caller; - + SourceLocation SL; llvm::raw_string_ostream IS(Fixit); // The attribute goes at the start of the declaration in C/C++ functions @@ -7164,8 +7185,9 @@ bool Sema::CheckFormatArguments(ArrayRef Args, const LangOptions &LO = getLangOpts(); if (CallerParamIdx && (LO.GNUMode || LO.C23 || LO.CPlusPlus11)) - CheckMissingFormatAttributes(this, Type, format_idx, firstDataArg, Args, - APK, *CallerParamIdx, Loc); + CheckMissingFormatAttributes(this, Args, APK, ReferenceFormatString, + format_idx, firstDataArg, Type, + *CallerParamIdx, Loc); // Strftime is particular as it always uses a single 'time' argument, // so it is safe to pass a non-literal string. diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c index 10a8b2e193447..63d30940fd637 100644 --- a/clang/test/Sema/attr-format-missing.c +++ b/clang/test/Sema/attr-format-missing.c @@ -27,6 +27,9 @@ size_t strftime(char *, size_t, const char *, const struct tm *); [[gnu::format(strfmon, 3, 4)]] ssize_t strfmon(char *, size_t, const char *, ...); +[[gnu::format_matches(printf, 1, "%d %f \"'")]] +int custom_print(const char *, va_list); + // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " void f1(const char *fmt, va_list args) // #f1 { @@ -226,3 +229,11 @@ void f23(const char *fmt, ... /* args */) vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f23'}} // expected-note@#f23 {{'f23' declared here}} } + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format_matches(printf, 1, \"%d %f \\\"'\")]] " +void f24(const char *fmt, ...) // #f24 +{ + va_list args; + custom_print(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format_matches(printf, 1, "%d %f \"'")' attribute to the declaration of 'f24'}} + // expected-note@#f24 {{'f24' declared here}} +} From 4fd960b18b93039665a33a0e279f37a948af5cf7 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic Date: Wed, 19 Nov 2025 00:49:08 -0800 Subject: [PATCH 7/7] Add implicit attribute --- clang/include/clang/Sema/Sema.h | 2 +- clang/lib/Sema/SemaChecking.cpp | 18 +++- clang/test/Sema/attr-format-missing.c | 119 ++++++++++++-------------- clang/test/Sema/format-strings.c | 83 ++++++++++++++++-- 4 files changed, 146 insertions(+), 76 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index eb8d7d1112016..4d45511894887 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3019,7 +3019,7 @@ class Sema final : public SemaBase { llvm::SmallBitVector &CheckedVarArgs); bool CheckFormatArguments(ArrayRef Args, FormatArgumentPassingKind FAPK, - const StringLiteral *ReferenceFormatString, + StringLiteral *ReferenceFormatString, unsigned format_idx, unsigned firstDataArg, FormatStringType Type, VariadicCallType CallType, SourceLocation Loc, SourceRange range, diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 93b0d32d0621c..c14011ed38ccd 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -7028,7 +7028,7 @@ std::string escapeFormatString(StringRef Input) { static void CheckMissingFormatAttributes( Sema *S, ArrayRef Args, Sema::FormatArgumentPassingKind APK, - const StringLiteral *ReferenceFormatString, unsigned FormatIdx, + StringLiteral *ReferenceFormatString, unsigned FormatIdx, unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx, SourceLocation Loc) { NamedDecl *Caller = S->getCurFunctionOrMethodDecl(); @@ -7085,9 +7085,9 @@ static void CheckMissingFormatAttributes( } // Emit the diagnostic and fixit. + unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; + StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); do { - unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; - StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); std::string Attr, Fixit; if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) llvm::raw_string_ostream(Attr) @@ -7125,11 +7125,21 @@ static void CheckMissingFormatAttributes( DB << FixItHint::CreateInsertion(SL, Fixit); } while (false); S->Diag(Caller->getLocation(), diag::note_entity_declared_at) << Caller; + + if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) { + Caller->addAttr(FormatAttr::CreateImplicit( + S->getASTContext(), &S->getASTContext().Idents.get(FormatTypeName), + FormatStringIndex, FirstArgumentIndex)); + } else { + Caller->addAttr(FormatMatchesAttr::CreateImplicit( + S->getASTContext(), &S->getASTContext().Idents.get(FormatTypeName), + FormatStringIndex, ReferenceFormatString)); + } } bool Sema::CheckFormatArguments(ArrayRef Args, Sema::FormatArgumentPassingKind APK, - const StringLiteral *ReferenceFormatString, + StringLiteral *ReferenceFormatString, unsigned format_idx, unsigned firstDataArg, FormatStringType Type, VariadicCallType CallType, SourceLocation Loc, diff --git a/clang/test/Sema/attr-format-missing.c b/clang/test/Sema/attr-format-missing.c index 63d30940fd637..8cfc2b2d07dab 100644 --- a/clang/test/Sema/attr-format-missing.c +++ b/clang/test/Sema/attr-format-missing.c @@ -88,31 +88,42 @@ void f7(char* out, ... /* args */) vprintf("test", args); } -// Ok, out is not passed to print functions. -void f8(char *out, va_list args) +// Ok, format string is not passed to format functions. +void f8(va_list args) { - const char *ch = "format"; + const char * const ch = "format"; vprintf(ch, args); vprintf("test", args); -} -// Ok, out is not passed to scan functions. -void f9(va_list args) -{ - const char *ch = "format"; vscanf(ch, args); vscanf("test", args); + + char out[10]; + + struct tm tm_arg; + tm_arg.i = 0; + strftime(out, sizeof(out), ch, &tm_arg); + strftime(out, sizeof(out), "test", &tm_arg); + + strfmon(out, sizeof(out), ch); + strfmon(out, sizeof(out), "test"); } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format_matches(printf, 1, \"%d %f \\\"'\")]] " +void f9(const char *fmt, ...) // #f9 +{ + va_list args; + custom_print(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format_matches(printf, 1, "%d %f \"'")' attribute to the declaration of 'f9'}} + // expected-note@#f9 {{'f9' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " void f10(const char *out, ... /* args */) // #f10 { va_list args; vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f10'}} // expected-note@#f10 {{'f10' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f10'}} - // expected-note@#f10 {{'f10' declared here}} + vprintf(out, args); // expected-warning {{passing 'scanf' format string where 'printf' format string is expected}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " @@ -135,105 +146,83 @@ void f12(char* out) // #f12 // expected-note@#f12 {{'f12' declared here}} } -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " -void f13(char *out, ... /* args */) // #f13 -{ - va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f13'}} - // expected-note@#f13 {{'f13' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f13'}} - // expected-note@#f13 {{'f13' declared here}} -} - -// CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(scanf, 1, 0)]] " // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " -void f14(char *out, va_list args) // #f14 +void f13(char *out, va_list args) // #f13 { - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 0)' attribute to the declaration of 'f14'}} - // expected-note@#f14 {{'f14' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f14'}} - // expected-note@#f14 {{'f14' declared here}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of 'f13'}} + // expected-note@#f13 {{'f13' declared here}} + vscanf(out, args); // expected-warning {{passing 'printf' format string where 'scanf' format string is expected}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(scanf, 1, 2)]] " -void f15(char *out, ... /* args */) // #f15 +void f14(char *out, ... /* args */) // #f14 { va_list args; - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}} - // expected-note@#f15 {{'f15' declared here}} - vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f15'}} - // expected-note@#f15 {{'f15' declared here}} + vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'f14'}} + // expected-note@#f14 {{'f14' declared here}} + vscanf(out, args); } // CHECK: fix-it:"{{.*}}":{[[@LINE+2]]:1-[[@LINE+2]]:1}:"{{\[\[}}gnu::format(printf, 1, 3)]] " // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 2, 3)]] " -void f16(char *ch, const char *out, ... /* args */) // #f16 +void f15(char *ch, const char *out, ... /* args */) // #f15 { va_list args; - vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'f16'}} - // expected-note@#f16 {{'f16' declared here}} - vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f16'}} - // expected-note@#f16 {{'f16' declared here}} + vprintf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'f15'}} + // expected-note@#f15 {{'f15' declared here}} + vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'f15'}} + // expected-note@#f15 {{'f15' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " -void f17(const char *a, ...) // #f17 +void f16(const char *a, ...) // #f16 { va_list ap; const char *const b = a; - vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f17'}} - // expected-note@#f17 {{'f17' declared here}} + vprintf(b, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f16'}} + // expected-note@#f16 {{'f16' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " -void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18 +void f17(char *fmt, unsigned x, unsigned y, unsigned z) // #f17 { - printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f18'}} - // expected-note@#f18 {{'f18' declared here}} + printf(fmt, x, y, z); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f17'}} + // expected-note@#f17 {{'f17' declared here}} } -void f19(char *fmt, unsigned x, unsigned y, unsigned z) // #f19 +void f18(char *fmt, unsigned x, unsigned y, unsigned z) // #f18 { // Arguments are not passed in the same order. printf(fmt, x, z, y); } -void f20(char *out, ... /* args */) +void f19(char *out, ... /* args */) { printf(out, 1); // No warning, arguments are not passed to printf. } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strftime, 3, 0)]] " -void f21(char *out, const size_t len, const char *format) // #f21 +void f20(char *out, const size_t len, const char *format) // #f20 { struct tm tm_arg; tm_arg.i = 0; - strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strftime, 3, 0)' attribute to the declaration of 'f21'}} - // expected-note@#f21 {{'f21' declared here}} + strftime(out, len, format, &tm_arg); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strftime, 3, 0)' attribute to the declaration of 'f20'}} + // expected-note@#f20 {{'f20' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] " -void f22(char *out, const size_t len, const char *format, int x, int y) // #f22 +void f21(char *out, const size_t len, const char *format, int x, int y) // #f21 { - strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f22'}} - // expected-note@#f22 {{'f22' declared here}} + strfmon(out, len, format, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(strfmon, 3, 4)' attribute to the declaration of 'f21'}} + // expected-note@#f21 {{'f21' declared here}} } // CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " -void f23(const char *fmt, ... /* args */); // #f23 - -void f23(const char *fmt, ... /* args */) -{ - va_list args; - vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f23'}} - // expected-note@#f23 {{'f23' declared here}} -} +void f22(const char *fmt, ... /* args */); // #f22 -// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format_matches(printf, 1, \"%d %f \\\"'\")]] " -void f24(const char *fmt, ...) // #f24 +void f22(const char *fmt, ... /* args */) { va_list args; - custom_print(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format_matches(printf, 1, "%d %f \"'")' attribute to the declaration of 'f24'}} - // expected-note@#f24 {{'f24' declared here}} + vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'f22'}} + // expected-note@#f22 {{'f22' declared here}} } diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c index 103dd8ab5a85c..b46fc2c008b3a 100644 --- a/clang/test/Sema/format-strings.c +++ b/clang/test/Sema/format-strings.c @@ -24,37 +24,108 @@ int vscanf(const char *restrict format, va_list arg); char * global_fmt; -void check_string_literal( FILE* fp, const char* s, char *buf, ... ) { - - char * b; +void check_string_literal1( const char* s, ... ) { va_list ap; - va_start(ap,buf); - + va_start(ap,s); printf(s); // expected-warning {{format string is not a string literal}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal2( const char* s, ... ) { + va_list ap; + va_start(ap,s); vprintf(s,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal3( FILE* fp, const char* s, ... ) { + va_list ap; + va_start(ap,s); fprintf(fp,s); // expected-warning {{format string is not a string literal}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal4( FILE* fp, const char* s, ... ) { + va_list ap; + va_start(ap,s); vfprintf(fp,s,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal5( const char* s, ... ) { + char * b; + va_list ap; + va_start(ap,s); asprintf(&b,s); // expected-warning {{format string is not a string lit}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal6( const char* s, ... ) { + char * b; + va_list ap; + va_start(ap,s); vasprintf(&b,s,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal7( const char* s, char *buf ) { sprintf(buf,s); // expected-warning {{format string is not a string literal}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal8( const char* s, char *buf ) { snprintf(buf,2,s); // expected-warning {{format string is not a string lit}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal9( const char* s, char *buf, ... ) { + va_list ap; + va_start(ap,buf); __builtin___sprintf_chk(buf,0,-1,s); // expected-warning {{format string is not a string literal}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal10( const char* s, char *buf, ... ) { + va_list ap; + va_start(ap,buf); __builtin___snprintf_chk(buf,2,0,-1,s); // expected-warning {{format string is not a string lit}} // expected-note@-1{{treat the string as an argument to avoid this}} +} + +void check_string_literal11( const char* s, char *buf, ... ) { + va_list ap; + va_start(ap,buf); vsprintf(buf,s,ap); // expected-warning {{format string is not a string lit}} +} + +void check_string_literal12( const char* s, char *buf, ... ) { + va_list ap; + va_start(ap,buf); vsnprintf(buf,2,s,ap); // expected-warning {{format string is not a string lit}} +} + +void check_string_literal13( char *buf, ... ) { + va_list ap; + va_start(ap,buf); vsnprintf(buf,2,global_fmt,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal14( FILE* fp, const char* s, char *buf, ... ) { + va_list ap; + va_start(ap,buf); __builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // expected-warning {{format string is not a string lit}} +} + +void check_string_literal15( FILE* fp, const char* s, char *buf, ... ) { + va_list ap; + va_start(ap,buf); __builtin___vsnprintf_chk(buf,2,0,-1,global_fmt,ap); // expected-warning {{format string is not a string literal}} +} +void check_string_literal16(const char* s, ... ) { + va_list ap; + va_start(ap,s); vscanf(s, ap); // expected-warning {{format string is not a string literal}} +} +void check_string_literal17() { const char *const fmt = "%d"; // FIXME -- defined here printf(fmt, 1, 2); // expected-warning{{data argument not used}} @@ -74,7 +145,7 @@ def" // warn only if the format string argument is a parameter that is not itself // declared as a format string with compatible format. __attribute__((__format__ (__printf__, 2, 4))) -void check_string_literal2( FILE* fp, const char* s, char *buf, ... ) { +void check_string_literal18( FILE* fp, const char* s, char *buf, ... ) { char * b; va_list ap; va_start(ap,buf);