Skip to content

Commit d04f97b

Browse files
[clang] Catch missing format attributes
1 parent 78a98c7 commit d04f97b

File tree

9 files changed

+654
-3
lines changed

9 files changed

+654
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ Improvements to Clang's diagnostics
451451

452452
- Clang now diagnoses ``[[deprecated]]`` attribute usage on local variables (#GH90073).
453453

454+
- Clang now diagnoses missing format attributes for non-template functions and class/struct/union members. (#GH60718)
455+
454456
Improvements to Clang's time-trace
455457
----------------------------------
456458

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,6 @@ def MainReturnType : DiagGroup<"main-return-type">;
530530
def MaxUnsignedZero : DiagGroup<"max-unsigned-zero">;
531531
def MissingBraces : DiagGroup<"missing-braces">;
532532
def MissingDeclarations: DiagGroup<"missing-declarations">;
533-
def : DiagGroup<"missing-format-attribute">;
534533
def MissingIncludeDirs : DiagGroup<"missing-include-dirs">;
535534
def MissingNoreturn : DiagGroup<"missing-noreturn">;
536535
def MultiChar : DiagGroup<"multichar">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,10 @@ def err_opencl_invalid_param : Error<
10551055
"declaring function parameter of type %0 is not allowed%select{; did you forget * ?|}1">;
10561056
def err_opencl_invalid_return : Error<
10571057
"declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
1058+
def warn_missing_format_attribute : Warning<
1059+
"diagnostic behavior may be improved by adding the %0 format attribute to the declaration of %1">,
1060+
InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
1061+
def note_format_function : Note<"%0 format function">;
10581062
def warn_pragma_options_align_reset_failed : Warning<
10591063
"#pragma options align=reset failed: %0">,
10601064
InGroup<IgnoredPragmas>;

clang/include/clang/Sema/Attr.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ inline bool isInstanceMethod(const Decl *D) {
123123
return false;
124124
}
125125

126+
inline bool checkIfMethodHasImplicitObjectParameter(const Decl *D) {
127+
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(D))
128+
return MethodDecl->isInstance() &&
129+
!MethodDecl->hasCXXExplicitFunctionObjectParameter();
130+
return false;
131+
}
132+
126133
/// Diagnose mutually exclusive attributes when present on a given
127134
/// declaration. Returns true if diagnosed.
128135
template <typename AttrTy>

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4576,6 +4576,8 @@ class Sema final : public SemaBase {
45764576

45774577
enum class RetainOwnershipKind { NS, CF, OS };
45784578

4579+
void DiagnoseMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
4580+
45794581
UuidAttr *mergeUuidAttr(Decl *D, const AttributeCommonInfo &CI,
45804582
StringRef UuidAsWritten, MSGuidDecl *GuidDecl);
45814583

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16049,6 +16049,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1604916049
}
1605016050
}
1605116051

16052+
DiagnoseMissingFormatAttributes(Body, FD);
16053+
1605216054
// We might not have found a prototype because we didn't wish to warn on
1605316055
// the lack of a missing prototype. Try again without the checks for
1605416056
// whether we want to warn on the missing prototype.

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 220 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3624,7 +3624,7 @@ static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
36243624

36253625
// In C++ the implicit 'this' function parameter also counts, and they are
36263626
// counted from one.
3627-
bool HasImplicitThisParam = isInstanceMethod(D);
3627+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
36283628
unsigned NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
36293629

36303630
IdentifierInfo *II = AL.getArgAsIdent(0)->Ident;
@@ -3737,7 +3737,7 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
37373737
return;
37383738
}
37393739

3740-
bool HasImplicitThisParam = isInstanceMethod(D);
3740+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
37413741
int32_t NumArgs = getFunctionOrMethodNumParams(D);
37423742

37433743
FunctionDecl *FD = D->getAsFunction();
@@ -5421,6 +5421,224 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
54215421
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
54225422
}
54235423

5424+
// Returns vector of format attributes. There are no two attributes with same
5425+
// arguments in returning vector. There can be attributes that effectively only
5426+
// store information about format type.
5427+
static std::vector<FormatAttr *>
5428+
GetMissingFormatAttributes(Sema &S, Stmt *Body, const FunctionDecl *FDecl) {
5429+
unsigned int ArgumentIndexOffset =
5430+
checkIfMethodHasImplicitObjectParameter(FDecl) ? 2 : 1;
5431+
5432+
std::vector<FormatAttr *> MissingAttributes;
5433+
5434+
// Iterate over body statements.
5435+
for (auto *Child : Body->children()) {
5436+
// If child statement is compound statement, recursively get missing
5437+
// attributes.
5438+
if (dyn_cast_or_null<CompoundStmt>(Child)) {
5439+
std::vector<FormatAttr *> CompoundStmtMissingAttributes =
5440+
GetMissingFormatAttributes(S, Child, FDecl);
5441+
5442+
// If there are already missing attributes with same arguments, do not add
5443+
// duplicates.
5444+
for (FormatAttr *FA : CompoundStmtMissingAttributes) {
5445+
if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
5446+
return FA->getType() == Attr->getType() &&
5447+
FA->getFormatIdx() == Attr->getFormatIdx() &&
5448+
FA->getFirstArg() == Attr->getFirstArg();
5449+
}))
5450+
MissingAttributes.push_back(FA);
5451+
}
5452+
5453+
continue;
5454+
}
5455+
5456+
ValueStmt *VS = dyn_cast<ValueStmt>(Child);
5457+
if (!VS)
5458+
continue;
5459+
CallExpr *TheCall = dyn_cast_or_null<CallExpr>(VS->getExprStmt());
5460+
if (!TheCall)
5461+
continue;
5462+
5463+
const FunctionDecl *CalleeFunction =
5464+
dyn_cast_or_null<FunctionDecl>(TheCall->getCalleeDecl());
5465+
if (!CalleeFunction || !CalleeFunction->hasAttr<FormatAttr>())
5466+
continue;
5467+
5468+
// va_list is not intended to be passed to variadic function.
5469+
if (CalleeFunction->isVariadic())
5470+
continue;
5471+
5472+
Expr **Args = TheCall->getArgs();
5473+
unsigned int NumArgs = TheCall->getNumArgs();
5474+
5475+
// Check if va_list is passed to callee function.
5476+
// If va_list is not passed, continue to the next iteration.
5477+
bool hasVaList = false;
5478+
for (const auto *Param : CalleeFunction->parameters()) {
5479+
if (Param->getOriginalType().getCanonicalType() ==
5480+
S.getASTContext().getBuiltinVaListType().getCanonicalType()) {
5481+
hasVaList = true;
5482+
break;
5483+
}
5484+
}
5485+
if (!hasVaList)
5486+
continue;
5487+
5488+
// If callee expression is function, check if it is format function.
5489+
// If it is, check if caller function misses format attributes.
5490+
5491+
unsigned int CalleeFunctionFormatArgumentIndexOffset =
5492+
checkIfMethodHasImplicitObjectParameter(CalleeFunction) ? 2 : 1;
5493+
5494+
// If callee function is format function and format arguments are not
5495+
// relevant to emit diagnostic, save only information about format type
5496+
// (format index and first-to-check argument index are set to -1).
5497+
// Information about format type is later used to determine if there are
5498+
// more than one format type found.
5499+
5500+
// Check if function has format attribute with forwarded format string.
5501+
IdentifierInfo *AttrType;
5502+
const ParmVarDecl *FormatArg;
5503+
if (!llvm::any_of(CalleeFunction->specific_attrs<FormatAttr>(),
5504+
[&](const FormatAttr *Attr) {
5505+
AttrType = Attr->getType();
5506+
5507+
int OffsetFormatIndex =
5508+
Attr->getFormatIdx() -
5509+
CalleeFunctionFormatArgumentIndexOffset;
5510+
if (OffsetFormatIndex < 0 ||
5511+
(unsigned)OffsetFormatIndex >= NumArgs)
5512+
return false;
5513+
5514+
if (const auto *FormatArgExpr = dyn_cast<DeclRefExpr>(
5515+
Args[OffsetFormatIndex]->IgnoreParenCasts()))
5516+
if (FormatArg = dyn_cast_or_null<ParmVarDecl>(
5517+
FormatArgExpr->getReferencedDeclOfCallee()))
5518+
return true;
5519+
return false;
5520+
})) {
5521+
MissingAttributes.push_back(
5522+
FormatAttr::CreateImplicit(S.getASTContext(), AttrType, -1, -1));
5523+
continue;
5524+
}
5525+
5526+
// If format string argument is caller function parameter, get string index.
5527+
// Otherwise, save only attribute type and go to next iteration.
5528+
int StringIndex = [=]() -> int {
5529+
for (const ParmVarDecl *Param : FDecl->parameters()) {
5530+
if (Param == FormatArg)
5531+
return Param->getFunctionScopeIndex() + ArgumentIndexOffset;
5532+
}
5533+
return 0;
5534+
}();
5535+
5536+
if (StringIndex == 0) {
5537+
MissingAttributes.push_back(
5538+
FormatAttr::CreateImplicit(S.getASTContext(), AttrType, -1, -1));
5539+
continue;
5540+
}
5541+
5542+
unsigned NumOfCallerFunctionParams = FDecl->getNumParams();
5543+
5544+
// Compare caller and callee function format attribute arguments (archetype
5545+
// and format string).
5546+
if (llvm::any_of(
5547+
FDecl->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
5548+
if (Attr->getType() != AttrType)
5549+
return false;
5550+
int OffsetFormatIndex =
5551+
Attr->getFormatIdx() - ArgumentIndexOffset;
5552+
5553+
if (OffsetFormatIndex < 0 ||
5554+
(unsigned)OffsetFormatIndex >= NumOfCallerFunctionParams)
5555+
return false;
5556+
5557+
if (FDecl->parameters()[OffsetFormatIndex] != FormatArg)
5558+
return false;
5559+
5560+
return true;
5561+
})) {
5562+
MissingAttributes.push_back(
5563+
FormatAttr::CreateImplicit(S.getASTContext(), AttrType, -1, -1));
5564+
continue;
5565+
}
5566+
5567+
// Get first argument index
5568+
int FirstToCheck = FDecl->isVariadic()
5569+
? (NumOfCallerFunctionParams + ArgumentIndexOffset)
5570+
: 0;
5571+
5572+
// If there are already attributes which arguments matches arguments
5573+
// detected in this iteration, do not add new attribute as it would be
5574+
// duplicate.
5575+
if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
5576+
return Attr->getType() == AttrType &&
5577+
Attr->getFormatIdx() == StringIndex &&
5578+
Attr->getFirstArg() == FirstToCheck;
5579+
}))
5580+
MissingAttributes.push_back(
5581+
FormatAttr::CreateImplicit(S.getASTContext(), AttrType, StringIndex,
5582+
FirstToCheck, TheCall->getBeginLoc()));
5583+
}
5584+
5585+
return MissingAttributes;
5586+
}
5587+
5588+
// This function is called only if function call is not inside template body.
5589+
// TODO: Add call for function calls inside template body.
5590+
// Emit warnings if caller function misses format attributes.
5591+
void Sema::DiagnoseMissingFormatAttributes(Stmt *Body,
5592+
const FunctionDecl *FDecl) {
5593+
assert(FDecl);
5594+
5595+
// If there are no function body, exit.
5596+
if (!Body)
5597+
return;
5598+
5599+
// Get missing format attributes
5600+
std::vector<FormatAttr *> MissingFormatAttributes =
5601+
GetMissingFormatAttributes(*this, Body, FDecl);
5602+
if (MissingFormatAttributes.empty())
5603+
return;
5604+
5605+
// Check if there are more than one format type found. In that case do not
5606+
// emit diagnostic.
5607+
const clang::IdentifierInfo *AttrType = MissingFormatAttributes[0]->getType();
5608+
for (unsigned i = 1; i < MissingFormatAttributes.size(); ++i) {
5609+
if (AttrType != MissingFormatAttributes[i]->getType())
5610+
return;
5611+
}
5612+
5613+
for (const FormatAttr *FA : MissingFormatAttributes) {
5614+
// If format index and first-to-check argument index are negative, it means
5615+
// that this attribute is only saved for multiple format types checking.
5616+
if (FA->getFormatIdx() < 0 || FA->getFirstArg() < 0)
5617+
continue;
5618+
5619+
// If caller function has format attributes and callee format attribute type
5620+
// mismatches caller attribute type, do not emit diagnostic.
5621+
if (FDecl->hasAttr<FormatAttr>() &&
5622+
!llvm::any_of(FDecl->specific_attrs<FormatAttr>(),
5623+
[FA](const FormatAttr *FunctionAttr) {
5624+
return FA->getType() == FunctionAttr->getType();
5625+
}))
5626+
continue;
5627+
5628+
// Emit diagnostic
5629+
SourceLocation Loc = FDecl->getFirstDecl()->getLocation();
5630+
Diag(Loc, diag::warn_missing_format_attribute)
5631+
<< FA->getType() << FDecl
5632+
<< FixItHint::CreateInsertion(Loc,
5633+
(llvm::Twine("__attribute__((format(") +
5634+
FA->getType()->getName() + ", " +
5635+
llvm::Twine(FA->getFormatIdx()) + ", " +
5636+
llvm::Twine(FA->getFirstArg()) + ")))")
5637+
.str());
5638+
Diag(FA->getLocation(), diag::note_format_function) << FA->getType();
5639+
}
5640+
}
5641+
54245642
//===----------------------------------------------------------------------===//
54255643
// Microsoft specific attribute handlers.
54265644
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)