Skip to content

Commit 502404e

Browse files
[clang] Catch missing format attributes
1 parent b0338c3 commit 502404e

File tree

10 files changed

+649
-4
lines changed

10 files changed

+649
-4
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ Improvements to Clang's diagnostics
326326
- Now correctly diagnose a tentative definition of an array with static
327327
storage duration in pedantic mode in C. (#GH50661)
328328

329+
- Clang now diagnoses missing format attributes for non-template functions
330+
and class/struct/union members. (#GH60718)
331+
329332
Improvements to Clang's time-trace
330333
----------------------------------
331334

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,6 @@ def MainReturnType : DiagGroup<"main-return-type">;
550550
def MaxUnsignedZero : DiagGroup<"max-unsigned-zero">;
551551
def MissingBraces : DiagGroup<"missing-braces">;
552552
def MissingDeclarations: DiagGroup<"missing-declarations">;
553-
def : DiagGroup<"missing-format-attribute">;
554553
def MissingIncludeDirs : DiagGroup<"missing-include-dirs">;
555554
def MissingNoreturn : DiagGroup<"missing-noreturn">;
556555
def MultiChar : DiagGroup<"multichar">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,10 @@ def err_opencl_invalid_param : Error<
10991099
"declaring function parameter of type %0 is not allowed%select{; did you forget * ?|}1">;
11001100
def err_opencl_invalid_return : Error<
11011101
"declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
1102+
def warn_missing_format_attribute : Warning<
1103+
"diagnostic behavior may be improved by adding the %0 format attribute to the declaration of %1">,
1104+
InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
1105+
def note_format_function : Note<"%0 format function">;
11021106
def warn_pragma_options_align_reset_failed : Warning<
11031107
"#pragma options align=reset failed: %0">,
11041108
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4713,6 +4713,11 @@ class Sema final : public SemaBase {
47134713

47144714
enum class RetainOwnershipKind { NS, CF, OS };
47154715

4716+
void DetectMissingFormatAttributes(const FunctionDecl *Callee,
4717+
ArrayRef<const Expr *> Args,
4718+
SourceLocation Loc);
4719+
void EmitMissingFormatAttributesDiagnostic(const FunctionDecl *Caller);
4720+
47164721
UuidAttr *mergeUuidAttr(Decl *D, const AttributeCommonInfo &CI,
47174722
StringRef UuidAsWritten, MSGuidDecl *GuidDecl);
47184723

clang/lib/Sema/SemaChecking.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3460,8 +3460,10 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
34603460
}
34613461
}
34623462

3463-
if (FD)
3463+
if (FD) {
34643464
diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc);
3465+
DetectMissingFormatAttributes(FD, Args, Loc);
3466+
}
34653467
}
34663468

34673469
void Sema::CheckConstrainedAuto(const AutoType *AutoT, SourceLocation Loc) {

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16340,6 +16340,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1634016340
}
1634116341
}
1634216342

16343+
EmitMissingFormatAttributesDiagnostic(FD);
16344+
1634316345
// We might not have found a prototype because we didn't wish to warn on
1634416346
// the lack of a missing prototype. Try again without the checks for
1634516347
// whether we want to warn on the missing prototype.

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3899,7 +3899,7 @@ static bool handleFormatAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
38993899

39003900
// In C++ the implicit 'this' function parameter also counts, and they are
39013901
// counted from one.
3902-
bool HasImplicitThisParam = isInstanceMethod(D);
3902+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
39033903
Info->NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
39043904

39053905
Info->Identifier = AL.getArgAsIdent(0)->Ident;
@@ -4042,7 +4042,7 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
40424042
return;
40434043
}
40444044

4045-
bool HasImplicitThisParam = isInstanceMethod(D);
4045+
bool HasImplicitThisParam = checkIfMethodHasImplicitObjectParameter(D);
40464046
int32_t NumArgs = getFunctionOrMethodNumParams(D);
40474047

40484048
FunctionDecl *FD = D->getAsFunction();
@@ -5918,6 +5918,181 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
59185918
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
59195919
}
59205920

5921+
// Diagnosing missing format attributes is implemented in two steps:
5922+
// 1. Detect missing format attributes while checking function calls.
5923+
// 2. Emit diagnostic in part that processes function body.
5924+
// For this purpose it is created vector that stores information about format
5925+
// attributes. There are no two format attributes with same arguments in a
5926+
// vector. Vector could contains attributes that only store information about
5927+
// format type (format string and first to check argument are set to -1).
5928+
namespace {
5929+
std::vector<FormatAttr *> MissingAttributes;
5930+
} // end anonymous namespace
5931+
5932+
// This function is called only if function call is not inside template body.
5933+
// TODO: Add call for function calls inside template body.
5934+
// Detects and stores missing format attributes in a vector.
5935+
void Sema::DetectMissingFormatAttributes(const FunctionDecl *Callee,
5936+
ArrayRef<const Expr *> Args,
5937+
SourceLocation Loc) {
5938+
assert(Callee);
5939+
5940+
// If there is no caller, exit.
5941+
const FunctionDecl *Caller = getCurFunctionDecl();
5942+
if (!getCurFunctionDecl())
5943+
return;
5944+
5945+
// Check if callee function is a format function.
5946+
// If it is, check if caller function misses format attributes.
5947+
5948+
if (!Callee->hasAttr<FormatAttr>())
5949+
return;
5950+
5951+
// va_list is not intended to be passed to variadic function.
5952+
if (Callee->isVariadic())
5953+
return;
5954+
5955+
// Check if va_list is passed to callee function.
5956+
// If va_list is not passed, return.
5957+
bool hasVaList = false;
5958+
for (const auto *Param : Callee->parameters()) {
5959+
if (Param->getOriginalType().getCanonicalType() ==
5960+
getASTContext().getBuiltinVaListType().getCanonicalType()) {
5961+
hasVaList = true;
5962+
break;
5963+
}
5964+
}
5965+
if (!hasVaList)
5966+
return;
5967+
5968+
unsigned int FormatArgumentIndexOffset =
5969+
checkIfMethodHasImplicitObjectParameter(Callee) ? 2 : 1;
5970+
5971+
// If callee function is format function and format arguments are not
5972+
// relevant to emit diagnostic, save only information about format type
5973+
// (format index and first-to-check argument index are set to -1).
5974+
// Information about format type is later used to determine if there are
5975+
// more than one format type found.
5976+
5977+
unsigned int NumArgs = Args.size();
5978+
// Check if function has format attribute with forwarded format string.
5979+
IdentifierInfo *AttrType;
5980+
const ParmVarDecl *FormatStringArg;
5981+
if (!llvm::any_of(
5982+
Callee->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
5983+
AttrType = Attr->getType();
5984+
5985+
int OffsetFormatIndex =
5986+
Attr->getFormatIdx() - FormatArgumentIndexOffset;
5987+
if (OffsetFormatIndex < 0 || (unsigned)OffsetFormatIndex >= NumArgs)
5988+
return false;
5989+
5990+
if (const auto *FormatArgExpr = dyn_cast<DeclRefExpr>(
5991+
Args[OffsetFormatIndex]->IgnoreParenCasts()))
5992+
if (FormatStringArg = dyn_cast_or_null<ParmVarDecl>(
5993+
FormatArgExpr->getReferencedDeclOfCallee()))
5994+
return true;
5995+
return false;
5996+
})) {
5997+
MissingAttributes.push_back(
5998+
FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
5999+
return;
6000+
}
6001+
6002+
unsigned ArgumentIndexOffset =
6003+
checkIfMethodHasImplicitObjectParameter(Caller) ? 2 : 1;
6004+
6005+
unsigned NumOfCallerFunctionParams = Caller->getNumParams();
6006+
6007+
// Compare caller and callee function format attribute arguments (archetype
6008+
// and format string). If they don't match, caller misses format attribute.
6009+
if (llvm::any_of(
6010+
Caller->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
6011+
if (Attr->getType() != AttrType)
6012+
return false;
6013+
int OffsetFormatIndex = Attr->getFormatIdx() - ArgumentIndexOffset;
6014+
6015+
if (OffsetFormatIndex < 0 ||
6016+
(unsigned)OffsetFormatIndex >= NumOfCallerFunctionParams)
6017+
return false;
6018+
6019+
if (Caller->parameters()[OffsetFormatIndex] != FormatStringArg)
6020+
return false;
6021+
6022+
return true;
6023+
})) {
6024+
MissingAttributes.push_back(
6025+
FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
6026+
return;
6027+
}
6028+
6029+
// Get format string index
6030+
int FormatStringIndex =
6031+
FormatStringArg->getFunctionScopeIndex() + ArgumentIndexOffset;
6032+
6033+
// Get first argument index
6034+
int FirstToCheck = Caller->isVariadic()
6035+
? (NumOfCallerFunctionParams + ArgumentIndexOffset)
6036+
: 0;
6037+
6038+
// Do not add duplicate in a vector of missing format attributes.
6039+
if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
6040+
return Attr->getType() == AttrType &&
6041+
Attr->getFormatIdx() == FormatStringIndex &&
6042+
Attr->getFirstArg() == FirstToCheck;
6043+
}))
6044+
MissingAttributes.push_back(FormatAttr::CreateImplicit(
6045+
getASTContext(), AttrType, FormatStringIndex, FirstToCheck, Loc));
6046+
}
6047+
6048+
// This function is called only if caller function is not template.
6049+
// TODO: Add call for template functions.
6050+
// Emits missing format attribute diagnostics.
6051+
void Sema::EmitMissingFormatAttributesDiagnostic(const FunctionDecl *Caller) {
6052+
const clang::IdentifierInfo *AttrType = MissingAttributes[0]->getType();
6053+
for (unsigned i = 1; i < MissingAttributes.size(); ++i) {
6054+
if (AttrType != MissingAttributes[i]->getType()) {
6055+
// Clear vector of missing attributes because it could be used in
6056+
// diagnosing missing format attributes in another caller.
6057+
MissingAttributes.clear();
6058+
return;
6059+
}
6060+
}
6061+
6062+
for (const FormatAttr *FA : MissingAttributes) {
6063+
// If format index and first-to-check argument index are negative, it means
6064+
// that this attribute is only saved for multiple format types checking.
6065+
if (FA->getFormatIdx() < 0 || FA->getFirstArg() < 0)
6066+
continue;
6067+
6068+
// If caller function has format attributes and callee format attribute type
6069+
// mismatches caller attribute type, do not emit diagnostic.
6070+
if (Caller->hasAttr<FormatAttr>() &&
6071+
!llvm::any_of(Caller->specific_attrs<FormatAttr>(),
6072+
[FA](const FormatAttr *FunctionAttr) {
6073+
return FA->getType() == FunctionAttr->getType();
6074+
}))
6075+
continue;
6076+
6077+
// Emit diagnostic
6078+
SourceLocation Loc = Caller->getFirstDecl()->getLocation();
6079+
Diag(Loc, diag::warn_missing_format_attribute)
6080+
<< FA->getType() << Caller
6081+
<< FixItHint::CreateInsertion(Loc,
6082+
(llvm::Twine("__attribute__((format(") +
6083+
FA->getType()->getName() + ", " +
6084+
llvm::Twine(FA->getFormatIdx()) + ", " +
6085+
llvm::Twine(FA->getFirstArg()) + ")))")
6086+
.str());
6087+
Diag(FA->getLocation(), diag::note_format_function) << FA->getType();
6088+
}
6089+
6090+
// Clear vector of missing attributes after emitting diagnostics for caller
6091+
// function because it could be used in diagnosing missing format attributes
6092+
// in another caller.
6093+
MissingAttributes.clear();
6094+
}
6095+
59216096
//===----------------------------------------------------------------------===//
59226097
// Microsoft specific attribute handlers.
59236098
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)