From 618397ac910c45bb17a34594de48476c0c7021d3 Mon Sep 17 00:00:00 2001 From: Vladimir Vuksanovic <109677816+vvuksanovic@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:04:12 +0100 Subject: [PATCH 1/2] [Sema] Suggest missing format attributes (#166738) Implements the `-Wmissing-format-attribute` diagnostic as a subgroup of `-Wformat-nonliteral`. It suggests adding format attributes to function declarations that call other format functions and pass the format string to them. This is an updated implementation of #105479. --------- Co-authored-by: Budimir Arandjelovic --- clang/docs/ReleaseNotes.rst | 4 + clang/include/clang/Basic/DiagnosticGroups.td | 4 +- .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/include/clang/Sema/Sema.h | 2 +- clang/lib/Sema/SemaChecking.cpp | 215 ++++++++++++++--- clang/lib/Sema/SemaDeclAttr.cpp | 4 +- clang/test/Sema/format-attr-missing-gnu.c | 55 +++++ clang/test/Sema/format-attr-missing.c | 228 ++++++++++++++++++ clang/test/Sema/format-attr-missing.cpp | 68 ++++++ clang/test/Sema/format-attr-missing.m | 68 ++++++ clang/test/Sema/format-strings-scanf.c | 5 +- clang/test/Sema/format-strings.c | 109 +++++++-- clang/test/SemaCXX/format-strings.cpp | 6 +- clang/test/SemaObjC/format-strings-objc.m | 5 +- 14 files changed, 717 insertions(+), 60 deletions(-) create mode 100644 clang/test/Sema/format-attr-missing-gnu.c create mode 100644 clang/test/Sema/format-attr-missing.c create mode 100644 clang/test/Sema/format-attr-missing.cpp create mode 100644 clang/test/Sema/format-attr-missing.m diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b8f2d3b7a96f2..508ce04b506e3 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -756,6 +756,10 @@ Improvements to Clang's diagnostics comparison operators when mixed with bitwise operators in enum value initializers. This can be locally disabled by explicitly casting the initializer value. +- Clang now detects potential missing format and format_matches attributes on function, + Objective-C method and block declarations when calling format functions. It is part + of the format-nonliteral diagnostic (#GH60718) + Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index b986f93200452..d9f1591367982 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -602,7 +602,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">; @@ -1175,6 +1174,7 @@ def FormatY2K : DiagGroup<"format-y2k">; def FormatPedantic : DiagGroup<"format-pedantic">; def FormatSignedness : DiagGroup<"format-signedness">; def FormatTypeConfusion : DiagGroup<"format-type-confusion">; +def MissingFormatAttribute : DiagGroup<"missing-format-attribute">; def FormatOverflowNonKprintf: DiagGroup<"format-overflow-non-kprintf">; def FormatOverflow: DiagGroup<"format-overflow", [FormatOverflowNonKprintf]>; @@ -1186,7 +1186,7 @@ def Format : DiagGroup<"format", FormatSecurity, FormatY2K, FormatInvalidSpecifier, FormatInsufficientArgs, FormatOverflow, FormatTruncation]>, DiagCategory<"Format String Issue">; -def FormatNonLiteral : DiagGroup<"format-nonliteral">; +def FormatNonLiteral : DiagGroup<"format-nonliteral", [MissingFormatAttribute]>; def Format2 : DiagGroup<"format=2", [FormatNonLiteral, FormatSecurity, FormatY2K]>; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 4e7a8d166b4fe..58a8a2eadbb95 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3490,6 +3490,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' 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/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 98ac143bf1dcb..5083b43abe45f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3433,7 +3433,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 f4acf22afce10..29b1234d15a4e 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -6249,13 +6249,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, 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: @@ -6277,10 +6280,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, 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: @@ -6312,10 +6316,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, 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; } @@ -6324,7 +6329,8 @@ static StringLiteralCheckType checkFormatStringExpr( StringLiteralCheckType Right = checkFormatStringExpr( S, ReferenceFormatString, C->getFalseExpr(), Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + UncoveredArg, Offset, CallerFormatParamIdx, + IgnoreStringsWithoutSpecifiers); return (CheckLeft && Left < Right) ? Left : Right; } @@ -6375,8 +6381,8 @@ static StringLiteralCheckType checkFormatStringExpr( } return checkFormatStringExpr( S, ReferenceFormatString, Init, Args, APK, format_idx, - firstDataArg, Type, CallType, - /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, Offset); + firstDataArg, Type, CallType, /*InFunctionCall=*/false, + CheckedVarArgs, UncoveredArg, Offset, CallerFormatParamIdx); } } @@ -6428,6 +6434,8 @@ static StringLiteralCheckType checkFormatStringExpr( // format arguments, in all cases. // if (const auto *PV = dyn_cast(VD)) { + if (CallerFormatParamIdx) + *CallerFormatParamIdx = PV->getFunctionScopeIndex(); if (const auto *D = dyn_cast(PV->getDeclContext())) { for (const auto *PVFormatMatches : D->specific_attrs()) { @@ -6453,7 +6461,7 @@ static StringLiteralCheckType checkFormatStringExpr( S, ReferenceFormatString, PVFormatMatches->getFormatString(), Args, APK, format_idx, firstDataArg, Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, - Offset, IgnoreStringsWithoutSpecifiers); + Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers); } } @@ -6508,7 +6516,7 @@ static StringLiteralCheckType checkFormatStringExpr( StringLiteralCheckType Result = checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, - Offset, IgnoreStringsWithoutSpecifiers); + Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers); if (IsFirst) { CommonResult = Result; IsFirst = false; @@ -6525,15 +6533,17 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers); + 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, 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: { @@ -6559,7 +6569,7 @@ static StringLiteralCheckType checkFormatStringExpr( return checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, - Offset, IgnoreStringsWithoutSpecifiers); + Offset, CallerFormatParamIdx, IgnoreStringsWithoutSpecifiers); } } @@ -6738,9 +6748,142 @@ bool Sema::CheckFormatString(const FormatMatchesAttr *Format, return false; } +static bool CheckMissingFormatAttribute( + Sema *S, ArrayRef Args, Sema::FormatArgumentPassingKind APK, + StringLiteral *ReferenceFormatString, unsigned FormatIdx, + unsigned FirstDataArg, FormatStringType FormatType, unsigned CallerParamIdx, + SourceLocation Loc) { + if (S->getDiagnostics().isIgnored(diag::warn_missing_format_attribute, Loc)) + return false; + + DeclContext *DC = S->CurContext; + if (!isa(DC) && !isa(DC) && !isa(DC)) + return false; + Decl *Caller = cast(DC)->getCanonicalDecl(); + + unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller); + + // 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() - FirstDataArg; + if (NumCalleeArgs == 0 || NumCallerParams < NumCalleeArgs) { + // There aren't enough arguments in the caller to pass to callee. + return false; + } + for (unsigned CalleeIdx = Args.size() - 1, CallerIdx = NumCallerParams - 1; + CalleeIdx >= FirstDataArg; --CalleeIdx, --CallerIdx) { + const auto *Arg = + dyn_cast(Args[CalleeIdx]->IgnoreParenCasts()); + if (!Arg) + return false; + const auto *Param = dyn_cast(Arg->getDecl()); + if (!Param || Param->getFunctionScopeIndex() != CallerIdx) + return false; + } + FirstArgumentIndex = + NumCallerParams + CallerArgumentIndexOffset - NumCalleeArgs; + break; + } + case Sema::FormatArgumentPassingKind::FAPK_VAList: + // Caller arguments are either variadic or a va_list. + FirstArgumentIndex = isFunctionOrMethodVariadic(Caller) + ? (NumCallerParams + CallerArgumentIndexOffset) + : 0; + break; + case Sema::FormatArgumentPassingKind::FAPK_Elsewhere: + // The callee has a format_matches attribute. We will emit that instead. + if (!ReferenceFormatString) + return false; + break; + } + + // Emit the diagnostic and fixit. + unsigned FormatStringIndex = CallerParamIdx + CallerArgumentIndexOffset; + StringRef FormatTypeName = S->GetFormatStringTypeName(FormatType); + NamedDecl *ND = dyn_cast(Caller); + do { + std::string Attr, Fixit; + llvm::raw_string_ostream AttrOS(Attr); + if (APK != Sema::FormatArgumentPassingKind::FAPK_Elsewhere) { + AttrOS << "format(" << FormatTypeName << ", " << FormatStringIndex << ", " + << FirstArgumentIndex << ")"; + } else { + AttrOS << "format_matches(" << FormatTypeName << ", " << FormatStringIndex + << ", \""; + AttrOS.write_escaped(ReferenceFormatString->getString()); + AttrOS << "\")"; + } + AttrOS.flush(); + auto DB = S->Diag(Loc, diag::warn_missing_format_attribute) << Attr; + if (ND) + DB << ND; + else + DB << "block"; + + // Blocks don't provide a correct end loc, so skip emitting a fixit. + if (isa(Caller)) + break; + + 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(); + } + 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); + + // Add implicit format or format_matches attribute. + 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)); + } + + { + auto DB = S->Diag(Caller->getLocation(), diag::note_entity_declared_at); + if (ND) + DB << ND; + else + DB << "block"; + } + return true; +} + bool Sema::CheckFormatArguments(ArrayRef Args, Sema::FormatArgumentPassingKind APK, - const StringLiteral *ReferenceFormatString, + StringLiteral *ReferenceFormatString, unsigned format_idx, unsigned firstDataArg, FormatStringType Type, VariadicCallType CallType, SourceLocation Loc, @@ -6770,11 +6913,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()) { @@ -6787,11 +6931,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 @@ -6801,6 +6940,16 @@ bool Sema::CheckFormatArguments(ArrayRef Args, SourceMgr.isInSystemMacro(FormatLoc)) return false; + if (CallerParamIdx && CheckMissingFormatAttribute( + this, Args, APK, ReferenceFormatString, format_idx, + firstDataArg, Type, *CallerParamIdx, Loc)) + return false; + + // 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 ad380af03748c..fdb0675055763 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3834,7 +3834,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; @@ -4093,7 +4093,7 @@ void Sema::copyFeatureAvailabilityCheck(Decl *Dst, NamedDecl *Src, } /// 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/format-attr-missing-gnu.c b/clang/test/Sema/format-attr-missing-gnu.c new file mode 100644 index 0000000000000..196b45cf58d48 --- /dev/null +++ b/clang/test/Sema/format-attr-missing-gnu.c @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -std=gnu11 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c++ -std=gnu++98 -Wmissing-format-attribute %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -std=gnu11 -Wmissing-format-attribute -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -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, 2))) +int printf(const char *, ...); + +__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 'format(printf, 1, 0)' attribute to the declaration of 'f1'}} + // expected-note@#f1 {{'f1' declared here}} +} + +void f2(void) { + void (^b1)(const char *, ...) = ^(const char *fmt, ...) { // #b1 + 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 block}} + // expected-note@#b1 {{block declared here}} + }; + + void (^b2)(const char *, va_list) = ^(const char *fmt, va_list args) { // #b2 + vprintf(fmt, args); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 0)' attribute to the declaration of block}}. + // expected-note@#b2 {{block declared here}} + }; + + void (^b3)(const char *, int x, float y) = ^(const char *fmt, int x, float y) { // #b3 + printf(fmt, x, y); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of block}}. + // expected-note@#b3 {{block declared here}} + }; + + void __attribute__((__format__(__printf__, 1, 2))) (^b4)(const char *, ...) = + ^(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))) { + va_list args; + vprintf(fmt, args); + }; + + void __attribute__((__format__(__printf__, 2, 3))) (^b5)(const char *, const char *, ...) = + ^(const char *not_fmt, const char *fmt, ...) __attribute__((__format__(__printf__, 2, 3))) { // #b5 + va_list args; + vprintf(fmt, args); + vprintf(not_fmt, args); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of block}} + // expected-note@#b5 {{block declared here}} + }; +} diff --git a/clang/test/Sema/format-attr-missing.c b/clang/test/Sema/format-attr-missing.c new file mode 100644 index 0000000000000..5133e595315d7 --- /dev/null +++ b/clang/test/Sema/format-attr-missing.c @@ -0,0 +1,228 @@ +// 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; + +[[gnu::format(printf, 1, 2)]] +int printf(const char *, ...); + +[[gnu::format(scanf, 1, 2)]] +int scanf(const char *, ...); + +[[gnu::format(printf, 1, 0)]] +int vprintf(const char *, va_list); + +[[gnu::format(scanf, 1, 0)]] +int vscanf(const char *, va_list); + +[[gnu::format(printf, 2, 0)]] +int vsprintf(char *, const char *, va_list); + +struct tm { unsigned i; }; +[[gnu::format(strftime, 3, 0)]] +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 +{ + 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 *fmt, va_list args) // #f2 +{ + 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 *fmt, ... /* args */) // #f3 +{ + 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 '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 *fmt, ... /* args */) // #f4 +{ + va_list args; + 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)]] " +[[gnu::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 '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 'format(printf, 2, 3)' 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, format string is not passed to format functions. +void f8(va_list args) +{ + const char * const ch = "format"; + vprintf(ch, args); + vprintf("test", args); + + 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+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 {{passing 'scanf' format string where 'printf' format string is expected}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::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 'format(printf, 1, 2)' attribute to the declaration of 'f11'}} + // expected-note@#f11 {{'f11' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::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 'format(printf, 1, 0)' attribute to the declaration of 'f12'}} + // expected-note@#f12 {{'f12' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 0)]] " +void f13(char *out, va_list args) // #f13 +{ + 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 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 '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 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 '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 f16(const char *a, ...) // #f16 +{ + va_list args; + const char *const b = a; + vprintf(b, args); // 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 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 'f17'}} + // expected-note@#f17 {{'f17' declared here}} +} + +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 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 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 'f20'}} + // expected-note@#f20 {{'f20' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(strfmon, 3, 4)]] " +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 'f21'}} + // expected-note@#f21 {{'f21' declared here}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"{{\[\[}}gnu::format(printf, 1, 2)]] " +void f22(const char *fmt, ... /* args */); // #f22 + +void f22(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 'f22'}} + // expected-note@#f22 {{'f22' declared here}} +} diff --git a/clang/test/Sema/format-attr-missing.cpp b/clang/test/Sema/format-attr-missing.cpp new file mode 100644 index 0000000000000..06b5ad1a68f3c --- /dev/null +++ b/clang/test/Sema/format-attr-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; + +[[gnu::format(printf, 1, 2)]] +int printf(const char *, ...); + +[[gnu::format(scanf, 1, 2)]] +int scanf(const char *, ...); + +[[gnu::format(printf, 1, 0)]] +int vprintf(const char *, va_list); + +[[gnu::format(scanf, 1, 0)]] +int vscanf(const char *, va_list); + +[[gnu::format(printf, 2, 0)]] +int vsprintf(char *, const char *, va_list); + +[[gnu::format(printf, 3, 0)]] +int vsnprintf(char *, size_t, const char *, va_list); + +struct S1 +{ + // 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; + 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}} + } + + [[gnu::format(printf, 2, 0)]] + void print(const char *out, va_list args); + + // 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; + 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 'format(printf, 2, 0)' attribute to the declaration of 'fn3'}} + // expected-note@#S1_fn3 {{'fn3' declared here}} + } + + // 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; + 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 'format(printf, 2, 0)' attribute to the declaration of 'fn5'}} + // expected-note@#S1_fn5 {{'fn5' declared here}} + } +}; + diff --git a/clang/test/Sema/format-attr-missing.m b/clang/test/Sema/format-attr-missing.m new file mode 100644 index 0000000000000..159b4bad47e98 --- /dev/null +++ b/clang/test/Sema/format-attr-missing.m @@ -0,0 +1,68 @@ +// 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 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(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}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 3))) " +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}} +} + +// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:1-[[@LINE+1]]:1}:"__attribute__((format(printf, 2, 0))) " +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}} +} + +__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 diff --git a/clang/test/Sema/format-strings-scanf.c b/clang/test/Sema/format-strings-scanf.c index eb5b8ec36bf7a..7c5b78b7e5e48 100644 --- a/clang/test/Sema/format-strings-scanf.c +++ b/clang/test/Sema/format-strings-scanf.c @@ -35,8 +35,9 @@ int vscanf(const char * restrict, va_list); int vfscanf(FILE * restrict, const char * restrict, va_list); int vsscanf(const char * restrict, const char * restrict, va_list); -void test(const char *s, int *i) { - scanf(s, i); // expected-warning{{format string is not a string literal}} +void test(const char *s, int *i) { // #test + scanf(s, i); // expected-warning{{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'test'}} + // expected-note@#test {{'test' declared here}} scanf("%0d", i); // expected-warning{{zero field width in scanf format string is unused}} scanf("%00d", i); // expected-warning{{zero field width in scanf format string is unused}} scanf("%d%[asdfasdfd", i, s); // expected-warning{{no closing ']' for '%[' in scanf format string}} diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c index dbe4e614d9d44..69a5f8ff23727 100644 --- a/clang/test/Sema/format-strings.c +++ b/clang/test/Sema/format-strings.c @@ -24,37 +24,115 @@ 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}} - vprintf(s,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal2( const char* s, ... ) { // #check_string_literal2 + va_list ap; + va_start(ap,s); + vprintf(s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'check_string_literal2'}} + // expected-note@#check_string_literal2 {{'check_string_literal2' declared here}} +} + +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}} - vfprintf(fp,s,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal4( FILE* fp, const char* s, ... ) { // #check_string_literal4 + va_list ap; + va_start(ap,s); + vfprintf(fp,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'check_string_literal4'}} + // expected-note@#check_string_literal4 {{'check_string_literal4' declared here}} +} + +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}} - vasprintf(&b,s,ap); // expected-warning {{format string is not a string literal}} +} + +void check_string_literal6( const char* s, ... ) { // #check_string_literal6 + char * b; + va_list ap; + va_start(ap,s); + vasprintf(&b,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 2)' attribute to the declaration of 'check_string_literal6'}} + // expected-note@#check_string_literal6 {{'check_string_literal6' declared here}} +} + +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}} - vsprintf(buf,s,ap); // expected-warning {{format string is not a string lit}} - vsnprintf(buf,2,s,ap); // expected-warning {{format string is not a string lit}} +} + +void check_string_literal11( const char* s, char *buf, ... ) { // #check_string_literal11 + va_list ap; + va_start(ap,buf); + vsprintf(buf,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'check_string_literal11'}} + // expected-note@#check_string_literal11 {{'check_string_literal11' declared here}} +} + +void check_string_literal12( const char* s, char *buf, ... ) { // #check_string_literal12 + va_list ap; + va_start(ap,buf); + vsnprintf(buf,2,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'check_string_literal12'}} + // expected-note@#check_string_literal12 {{'check_string_literal12' declared here}} +} + +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}} - __builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // expected-warning {{format string is not a string lit}} +} + +void check_string_literal14( FILE* fp, const char* s, char *buf, ... ) { // #check_string_literal14 + va_list ap; + va_start(ap,buf); + __builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(printf, 2, 4)' attribute to the declaration of 'check_string_literal14'}} + // expected-note@#check_string_literal14 {{'check_string_literal14' declared here}} +} + +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}} +} - vscanf(s, ap); // expected-warning {{format string is not a string literal}} +void check_string_literal16(const char* s, ... ) { // #check_string_literal16 + va_list ap; + va_start(ap,s); + vscanf(s, ap); // expected-warning {{diagnostic behavior may be improved by adding the 'format(scanf, 1, 2)' attribute to the declaration of 'check_string_literal16'}} + // expected-note@#check_string_literal16 {{'check_string_literal16' declared here}} +} +void check_string_literal17() { const char *const fmt = "%d"; // FIXME -- defined here printf(fmt, 1, 2); // expected-warning{{data argument not used}} @@ -74,7 +152,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); @@ -861,12 +939,13 @@ void test_block(void) { void __attribute__((__format__(__printf__, 2, 3))) (^printf_arg2)( const char *, const char *, ...) = - ^(const char *not_fmt, const char *fmt, ...) + ^(const char *not_fmt, const char *fmt, ...) // #printf_arg2 __attribute__((__format__(__printf__, 2, 3))) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); - vprintf(not_fmt, ap); // expected-warning{{format string is not a string literal}} + vprintf(not_fmt, ap); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of block}} + // expected-note@#printf_arg2 {{block declared here}} va_end(ap); }; diff --git a/clang/test/SemaCXX/format-strings.cpp b/clang/test/SemaCXX/format-strings.cpp index 48cf23999a94f..5890f56cfc952 100644 --- a/clang/test/SemaCXX/format-strings.cpp +++ b/clang/test/SemaCXX/format-strings.cpp @@ -33,7 +33,7 @@ class Foo { int scanf(const char *, ...) __attribute__((format(scanf, 2, 3))); int printf(const char *, ...) __attribute__((format(printf, 2, 3))); - int printf2(const char *, ...); + int printf2(const char *, ...); // #Foo_printf2 static const char *gettext_static(const char *fmt) __attribute__((format_arg(1))); static int printf_static(const char *fmt, ...) __attribute__((format(printf, 1, 2))); @@ -86,8 +86,8 @@ int Foo::printf(const char *fmt, ...) { int Foo::printf2(const char *fmt, ...) { va_list ap; va_start(ap,fmt); - vprintf(fmt, ap); // expected-warning{{format string is not a string literal}} - + vprintf(fmt, ap); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 2, 3)' attribute to the declaration of 'printf2'}} + // expected-note@#Foo_printf2 {{'printf2' declared here}} return 0; } diff --git a/clang/test/SemaObjC/format-strings-objc.m b/clang/test/SemaObjC/format-strings-objc.m index ec9c35c68f813..7f4485163a7a9 100644 --- a/clang/test/SemaObjC/format-strings-objc.m +++ b/clang/test/SemaObjC/format-strings-objc.m @@ -203,7 +203,7 @@ void test_toll_free_bridging(CFStringRef x, id y) { } @interface Bar -+ (void)log:(NSString *)fmt, ...; ++ (void)log:(NSString *)fmt, ...; // #log + (void)log2:(NSString *)fmt, ... __attribute__((format(NSString, 1, 2))); @end @@ -212,7 +212,8 @@ @implementation Bar + (void)log:(NSString *)fmt, ... { va_list ap; va_start(ap,fmt); - NSLogv(fmt, ap); // expected-warning{{format string is not a string literal}} + NSLogv(fmt, ap); // expected-warning{{diagnostic behavior may be improved by adding the 'format(NSString, 1, 2)' attribute to the declaration of 'log:'}} + // expected-note@#log {{'log:' declared here}} va_end(ap); } From f3c93666809e1ab81a5774f26b194d0710717333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= Date: Wed, 10 Dec 2025 18:34:07 +0100 Subject: [PATCH 2/2] Fix incompatibilities --- clang/lib/Sema/SemaChecking.cpp | 15 +++++++++++++-- .../BoundsSafety/Sema/check-format-arguments.c | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 29b1234d15a4e..8796005beee2e 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -6764,8 +6764,19 @@ static bool CheckMissingFormatAttribute( unsigned NumCallerParams = getFunctionOrMethodNumParams(Caller); // Find the offset to convert between attribute and parameter indexes. - unsigned CallerArgumentIndexOffset = - hasImplicitObjectParameter(Caller) ? 2 : 1; + + // Conflict note: this is equivalent to: + // + // hasImplicitObjectParameter(Caller) ? 2 : 1 + // + // hasImplicitObjectParameter needed to be dropped because it was + // introduced by another change that is too large to take in. + unsigned CallerArgumentIndexOffset = 1; + if (const auto *MethodDecl = dyn_cast(Caller)) { + if (MethodDecl->isImplicitObjectMemberFunction()) { + CallerArgumentIndexOffset = 2; + } + } unsigned FirstArgumentIndex = -1; switch (APK) { diff --git a/clang/test/BoundsSafety/Sema/check-format-arguments.c b/clang/test/BoundsSafety/Sema/check-format-arguments.c index 1937a6d7245f4..287c31fd5b090 100644 --- a/clang/test/BoundsSafety/Sema/check-format-arguments.c +++ b/clang/test/BoundsSafety/Sema/check-format-arguments.c @@ -9,7 +9,7 @@ void __printflike(1, 0) foo(const char *__null_terminated, va_list); -void __printflike(2, 3) bar(const char *__unsafe_indexable p1, const char *__unsafe_indexable p2, ...) { +void __printflike(2, 3) bar(const char *__unsafe_indexable p1, const char *__unsafe_indexable p2, ...) { // expected-note{{'bar' declared here}} va_list variadicArgs; va_start(variadicArgs, p2); @@ -18,7 +18,7 @@ void __printflike(2, 3) bar(const char *__unsafe_indexable p1, const char *__uns foo(__unsafe_forge_null_terminated(const char *, "Hello, %s!\n"), variadicArgs); foo(__unsafe_forge_null_terminated(const char *, 2), variadicArgs); // expected-warning{{format string is not a string literal}} - foo(__unsafe_forge_null_terminated(const char *, p1), variadicArgs); // expected-warning{{format string is not a string literal}} + foo(__unsafe_forge_null_terminated(const char *, p1), variadicArgs); // expected-warning{{diagnostic behavior may be improved by adding the 'format(printf, 1, 3)' attribute to the declaration of 'bar'}} va_end(variadicArgs); }