Skip to content

Commit 70f57d2

Browse files
[clang] Catch missing format attributes (#70024)
Enable flag -Wmissing-format-attribute to catch missing attributes. Fixes #60718
1 parent de90391 commit 70f57d2

File tree

9 files changed

+813
-3
lines changed

9 files changed

+813
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,9 @@ Improvements to Clang's diagnostics
714714

715715
- Clang now diagnoses integer constant expressions that are folded to a constant value as an extension in more circumstances. Fixes #GH59863
716716

717+
- Clang now diagnoses missing format attributes for non-template functions and
718+
class/struct/union members. Fixes #GH60718
719+
717720
Improvements to Clang's time-trace
718721
----------------------------------
719722

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,6 @@ def MainReturnType : DiagGroup<"main-return-type">;
525525
def MaxUnsignedZero : DiagGroup<"max-unsigned-zero">;
526526
def MissingBraces : DiagGroup<"missing-braces">;
527527
def MissingDeclarations: DiagGroup<"missing-declarations">;
528-
def : DiagGroup<"missing-format-attribute">;
529528
def MissingIncludeDirs : DiagGroup<"missing-include-dirs">;
530529
def MissingNoreturn : DiagGroup<"missing-noreturn">;
531530
def MultiChar : DiagGroup<"multichar">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,9 @@ def err_opencl_invalid_param : Error<
10311031
def err_opencl_invalid_return : Error<
10321032
"declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
10331033
def warn_enum_value_overflow : Warning<"overflow in enumeration value">;
1034+
def warn_missing_format_attribute : Warning<
1035+
"diagnostic behavior may be improved by adding the %0 format attribute to the declaration of %1">,
1036+
InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
10341037
def warn_pragma_options_align_reset_failed : Warning<
10351038
"#pragma options align=reset failed: %0">,
10361039
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
@@ -4594,6 +4594,10 @@ class Sema final : public SemaBase {
45944594

45954595
enum class RetainOwnershipKind { NS, CF, OS };
45964596

4597+
void DiagnoseMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
4598+
std::vector<FormatAttr *>
4599+
GetMissingFormatAttributes(Stmt *Body, const FunctionDecl *FDecl);
4600+
45974601
UuidAttr *mergeUuidAttr(Decl *D, const AttributeCommonInfo &CI,
45984602
StringRef UuidAsWritten, MSGuidDecl *GuidDecl);
45994603

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15934,6 +15934,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1593415934
}
1593515935
}
1593615936

15937+
DiagnoseMissingFormatAttributes(Body, FD);
15938+
1593715939
// We might not have found a prototype because we didn't wish to warn on
1593815940
// the lack of a missing prototype. Try again without the checks for
1593915941
// whether we want to warn on the missing prototype.

clang/lib/Sema/SemaDeclAttr.cpp

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

35093509
// In C++ the implicit 'this' function parameter also counts, and they are
35103510
// counted from one.
3511-
bool HasImplicitThisParam = isInstanceMethod(D);
3511+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
35123512
unsigned NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
35133513

35143514
IdentifierInfo *II = AL.getArgAsIdent(0)->Ident;
@@ -3621,7 +3621,7 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
36213621
return;
36223622
}
36233623

3624-
bool HasImplicitThisParam = isInstanceMethod(D);
3624+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
36253625
int32_t NumArgs = getFunctionOrMethodNumParams(D);
36263626

36273627
FunctionDecl *FD = D->getAsFunction();
@@ -5320,6 +5320,221 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
53205320
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
53215321
}
53225322

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

0 commit comments

Comments
 (0)