Skip to content

Commit 11aef7c

Browse files
[clang] Catch missing format attributes
1 parent 2e9cbb6 commit 11aef7c

File tree

9 files changed

+659
-3
lines changed

9 files changed

+659
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ Improvements to Clang's diagnostics
254254
compilation speed with modules. This warning is disabled by default and it needs
255255
to be explicitly enabled or by ``-Weverything``.
256256

257+
- Clang now diagnoses missing format attributes for non-template functions and
258+
class/struct/union members. Fixes #GH60718
259+
257260
Improvements to Clang's time-trace
258261
----------------------------------
259262

clang/include/clang/Basic/DiagnosticGroups.td

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

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,9 @@ def err_opencl_invalid_param : Error<
10401040
def err_opencl_invalid_return : Error<
10411041
"declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
10421042
def warn_enum_value_overflow : Warning<"overflow in enumeration value">;
1043+
def warn_missing_format_attribute : Warning<
1044+
"diagnostic behavior may be improved by adding the %0 format attribute to the declaration of %1">,
1045+
InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
10431046
def warn_pragma_options_align_reset_failed : Warning<
10441047
"#pragma options align=reset failed: %0">,
10451048
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4619,6 +4619,10 @@ class Sema final : public SemaBase {
46194619

46204620
enum class RetainOwnershipKind { NS, CF, OS };
46214621

4622+
void DiagnoseMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
4623+
std::vector<FormatAttr *>
4624+
GetMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
4625+
46224626
UuidAttr *mergeUuidAttr(Decl *D, const AttributeCommonInfo &CI,
46234627
StringRef UuidAsWritten, MSGuidDecl *GuidDecl);
46244628

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16004,6 +16004,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1600416004
}
1600516005
}
1600616006

16007+
DiagnoseMissingFormatAttributes(Body, FD);
16008+
1600716009
// We might not have found a prototype because we didn't wish to warn on
1600816010
// the lack of a missing prototype. Try again without the checks for
1600916011
// whether we want to warn on the missing prototype.

clang/lib/Sema/SemaDeclAttr.cpp

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

35203520
// In C++ the implicit 'this' function parameter also counts, and they are
35213521
// counted from one.
3522-
bool HasImplicitThisParam = isInstanceMethod(D);
3522+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
35233523
unsigned NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
35243524

35253525
IdentifierInfo *II = AL.getArgAsIdent(0)->Ident;
@@ -3632,7 +3632,7 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
36323632
return;
36333633
}
36343634

3635-
bool HasImplicitThisParam = isInstanceMethod(D);
3635+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
36363636
int32_t NumArgs = getFunctionOrMethodNumParams(D);
36373637

36383638
FunctionDecl *FD = D->getAsFunction();
@@ -5335,6 +5335,217 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
53355335
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
53365336
}
53375337

5338+
// This function is called only if function call is not inside template body.
5339+
// TODO: Add call for function calls inside template body.
5340+
// Emit warnings if parent function misses format attributes.
5341+
void Sema::DiagnoseMissingFormatAttributes(Stmt *Body,
5342+
const FunctionDecl *FDecl) {
5343+
assert(FDecl);
5344+
5345+
// If there are no function body, exit.
5346+
if (!Body)
5347+
return;
5348+
5349+
// Get missing format attributes
5350+
std::vector<FormatAttr *> MissingFormatAttributes =
5351+
GetMissingFormatAttributes(Body, FDecl);
5352+
if (MissingFormatAttributes.empty())
5353+
return;
5354+
5355+
// Check if there are more than one format type found. In that case do not
5356+
// emit diagnostic.
5357+
const clang::IdentifierInfo *AttrType = MissingFormatAttributes[0]->getType();
5358+
if (llvm::any_of(MissingFormatAttributes, [&](const FormatAttr *Attr) {
5359+
return AttrType != Attr->getType();
5360+
}))
5361+
return;
5362+
5363+
for (const FormatAttr *FA : MissingFormatAttributes) {
5364+
// If format index and first-to-check argument index are negative, it means
5365+
// that this attribute is only saved for multiple format types checking.
5366+
if (FA->getFormatIdx() < 0 || FA->getFirstArg() < 0)
5367+
continue;
5368+
5369+
// Emit diagnostic
5370+
SourceLocation Loc = FDecl->getLocation();
5371+
Diag(Loc, diag::warn_missing_format_attribute)
5372+
<< FA->getType() << FDecl
5373+
<< FixItHint::CreateInsertion(Loc,
5374+
(llvm::Twine("__attribute__((format(") +
5375+
FA->getType()->getName() + ", " +
5376+
llvm::Twine(FA->getFormatIdx()) + ", " +
5377+
llvm::Twine(FA->getFirstArg()) + ")))")
5378+
.str());
5379+
}
5380+
}
5381+
5382+
// Returns vector of format attributes. There are no two attributes with same
5383+
// arguments in returning vector. There can be attributes that effectivelly only
5384+
// store information about format type.
5385+
std::vector<FormatAttr *>
5386+
Sema::GetMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl) {
5387+
unsigned int FunctionFormatArgumentIndexOffset =
5388+
checkIfMethodHasImplicitObjectParameter(FDecl) ? 2 : 1;
5389+
5390+
std::vector<FormatAttr *> MissingAttributes;
5391+
5392+
// Iterate over body statements.
5393+
for (auto *Child : Body->children()) {
5394+
// If child statement is compound statement, recursively get missing
5395+
// attributes.
5396+
if (dyn_cast_or_null<CompoundStmt>(Child)) {
5397+
std::vector<FormatAttr *> CompoundStmtMissingAttributes =
5398+
GetMissingFormatAttributes(Child, FDecl);
5399+
5400+
// If there are already missing attributes with same arguments, do not add
5401+
// duplicates.
5402+
for (FormatAttr *FA : CompoundStmtMissingAttributes) {
5403+
if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
5404+
return FA->getType() == Attr->getType() &&
5405+
FA->getFormatIdx() == Attr->getFormatIdx() &&
5406+
FA->getFirstArg() == Attr->getFirstArg();
5407+
}))
5408+
MissingAttributes.push_back(FA);
5409+
}
5410+
5411+
continue;
5412+
}
5413+
5414+
ValueStmt *VS = dyn_cast_or_null<ValueStmt>(Child);
5415+
if (!VS)
5416+
continue;
5417+
Expr *TheExpr = VS->getExprStmt();
5418+
if (!TheExpr)
5419+
continue;
5420+
CallExpr *TheCall = dyn_cast_or_null<CallExpr>(TheExpr);
5421+
if (!TheCall)
5422+
continue;
5423+
const FunctionDecl *ChildFunction =
5424+
dyn_cast_or_null<FunctionDecl>(TheCall->getCalleeDecl());
5425+
if (!ChildFunction || !ChildFunction->hasAttr<FormatAttr>())
5426+
continue;
5427+
5428+
Expr **Args = TheCall->getArgs();
5429+
unsigned int NumArgs = TheCall->getNumArgs();
5430+
5431+
// If child expression is function, check if it is format function.
5432+
// If it is, check if parent function misses format attributes.
5433+
5434+
unsigned int ChildFunctionFormatArgumentIndexOffset =
5435+
checkIfMethodHasImplicitObjectParameter(ChildFunction) ? 2 : 1;
5436+
5437+
// If child function is format function and format arguments are not
5438+
// relevant to emit diagnostic, save only information about format type
5439+
// (format index and first-to-check argument index are set to -1).
5440+
// Information about format type is later used to determine if there are
5441+
// more than one format type found.
5442+
5443+
// Check if function has format attribute with forwarded format string.
5444+
IdentifierInfo *AttrType;
5445+
const ParmVarDecl *FormatArg;
5446+
if (!llvm::any_of(ChildFunction->specific_attrs<FormatAttr>(),
5447+
[&](const FormatAttr *Attr) {
5448+
AttrType = Attr->getType();
5449+
5450+
int OffsetFormatIndex =
5451+
Attr->getFormatIdx() -
5452+
ChildFunctionFormatArgumentIndexOffset;
5453+
if (OffsetFormatIndex < 0 ||
5454+
(unsigned)OffsetFormatIndex >= NumArgs)
5455+
return false;
5456+
5457+
const auto *FormatArgExpr = dyn_cast<DeclRefExpr>(
5458+
Args[OffsetFormatIndex]->IgnoreParenCasts());
5459+
if (!FormatArgExpr)
5460+
return false;
5461+
5462+
FormatArg = dyn_cast_or_null<ParmVarDecl>(
5463+
FormatArgExpr->getReferencedDeclOfCallee());
5464+
if (!FormatArg)
5465+
return false;
5466+
5467+
return true;
5468+
})) {
5469+
MissingAttributes.push_back(
5470+
FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
5471+
continue;
5472+
}
5473+
5474+
// Do not add in a vector format attributes whose type is different than
5475+
// parent function attribute type.
5476+
if (llvm::any_of(FDecl->specific_attrs<FormatAttr>(),
5477+
[&](const FormatAttr *FunctionAttr) {
5478+
return AttrType != FunctionAttr->getType();
5479+
}))
5480+
continue;
5481+
5482+
// Check if format string argument is parent function parameter.
5483+
int StringIndex = 0;
5484+
if (!llvm::any_of(FDecl->parameters(), [&](const ParmVarDecl *Param) {
5485+
if (Param != FormatArg)
5486+
return false;
5487+
5488+
StringIndex = Param->getFunctionScopeIndex() +
5489+
FunctionFormatArgumentIndexOffset;
5490+
5491+
return true;
5492+
})) {
5493+
MissingAttributes.push_back(
5494+
FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
5495+
continue;
5496+
}
5497+
5498+
unsigned NumOfParentFunctionParams = FDecl->getNumParams();
5499+
5500+
// Compare parent and calling function format attribute arguments (archetype
5501+
// and format string).
5502+
if (llvm::any_of(
5503+
FDecl->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
5504+
if (Attr->getType() != AttrType)
5505+
return false;
5506+
int OffsetFormatIndex =
5507+
Attr->getFormatIdx() - FunctionFormatArgumentIndexOffset;
5508+
5509+
if (OffsetFormatIndex < 0 ||
5510+
(unsigned)OffsetFormatIndex >= NumOfParentFunctionParams)
5511+
return false;
5512+
5513+
if (FDecl->parameters()[OffsetFormatIndex] != FormatArg)
5514+
return false;
5515+
5516+
return true;
5517+
})) {
5518+
MissingAttributes.push_back(
5519+
FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
5520+
continue;
5521+
}
5522+
5523+
// Get first argument index
5524+
int FirstToCheck = [&]() -> unsigned {
5525+
if (!FDecl->isVariadic())
5526+
return 0;
5527+
const auto *FirstToCheckArg = Args[NumArgs - 1]->IgnoreParenCasts();
5528+
if (FirstToCheckArg->getType().getCanonicalType() !=
5529+
Context.getBuiltinVaListType().getCanonicalType())
5530+
return 0;
5531+
return NumOfParentFunctionParams + FunctionFormatArgumentIndexOffset;
5532+
}();
5533+
5534+
// If there are already attributes which arguments matches arguments
5535+
// detected in this iteration, do not add new attribute as it would be
5536+
// duplicate.
5537+
if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
5538+
return Attr->getType() == AttrType &&
5539+
Attr->getFormatIdx() == StringIndex &&
5540+
Attr->getFirstArg() == FirstToCheck;
5541+
}))
5542+
MissingAttributes.push_back(FormatAttr::CreateImplicit(
5543+
getASTContext(), AttrType, StringIndex, FirstToCheck));
5544+
}
5545+
5546+
return MissingAttributes;
5547+
}
5548+
53385549
//===----------------------------------------------------------------------===//
53395550
// Microsoft specific attribute handlers.
53405551
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)