Skip to content

Commit ca3bfcd

Browse files
[clang] Catch missing format attributes
1 parent b505ef5 commit ca3bfcd

File tree

8 files changed

+550
-3
lines changed

8 files changed

+550
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ Improvements to Clang's diagnostics
6565
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6666
- We now generate a diagnostic for signed integer overflow due to unary minus
6767
in a non-constant expression context. This fixes
68-
`Issue 31643 <https://github.com/llvm/llvm-project/issues/31643>`_
68+
`Issue 31643 <https://github.com/llvm/llvm-project/issues/31643>`_.
69+
- We now generate a diagnostic for missing format attributes
70+
`Issue 60718 <https://github.com/llvm/llvm-project/issues/60718>`_.
6971

7072
Non-comprehensive list of changes in this release
7173
-------------------------------------------------

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,6 @@ def MainReturnType : DiagGroup<"main-return-type">;
482482
def MaxUnsignedZero : DiagGroup<"max-unsigned-zero">;
483483
def MissingBraces : DiagGroup<"missing-braces">;
484484
def MissingDeclarations: DiagGroup<"missing-declarations">;
485-
def : DiagGroup<"missing-format-attribute">;
486485
def : DiagGroup<"missing-include-dirs">;
487486
def MissingNoreturn : DiagGroup<"missing-noreturn">;
488487
def MultiChar : DiagGroup<"multichar">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,9 @@ def err_opencl_invalid_param : Error<
936936
def err_opencl_invalid_return : Error<
937937
"declaring function return value of type %0 is not allowed %select{; did you forget * ?|}1">;
938938
def warn_enum_value_overflow : Warning<"overflow in enumeration value">;
939+
def warn_missing_format_attribute : Warning<
940+
"diagnostic behavior may be improved by adding the %0 format attribute to the declaration of %1">,
941+
InGroup<DiagGroup<"missing-format-attribute">>, DefaultIgnore;
939942
def warn_pragma_options_align_reset_failed : Warning<
940943
"#pragma options align=reset failed: %0">,
941944
InGroup<IgnoredPragmas>;

clang/include/clang/Sema/Sema.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10615,6 +10615,10 @@ class Sema final {
1061510615
ChangedStateAtExit
1061610616
};
1061710617

10618+
void DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl,
10619+
ArrayRef<const Expr *> Args,
10620+
SourceLocation Loc);
10621+
1061810622
void DiagnoseNonDefaultPragmaAlignPack(PragmaAlignPackDiagnoseKind Kind,
1061910623
SourceLocation IncludeLoc);
1062010624
void DiagnoseUnterminatedPragmaAlignPack();

clang/lib/Sema/SemaChecking.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6014,8 +6014,10 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
60146014
}
60156015
}
60166016

6017-
if (FD)
6017+
if (FD) {
60186018
diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc);
6019+
DiagnoseMissingFormatAttributes(FD, Args, Range.getBegin());
6020+
}
60196021
}
60206022

60216023
/// CheckConstructorCall - Check a constructor call for correctness and safety

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6849,6 +6849,109 @@ static void handleSwiftAsyncAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
68496849
checkSwiftAsyncErrorBlock(S, D, ErrorAttr, AsyncAttr);
68506850
}
68516851

6852+
// Check if parent function misses format attribute. If misses, emit warning.
6853+
void Sema::DiagnoseMissingFormatAttributes(const FunctionDecl *FDecl,
6854+
ArrayRef<const Expr *> Args,
6855+
SourceLocation Loc) {
6856+
assert(FDecl);
6857+
6858+
const FunctionDecl *ParentFuncDecl = getCurFunctionDecl();
6859+
if (!ParentFuncDecl)
6860+
return;
6861+
6862+
// If function is a member of struct/union/class, format attribute argument
6863+
// indexing starts from 2. Otherwise, it starts from 1.
6864+
const unsigned int FormatArgumentIndexOffset =
6865+
isInstanceMethod(FDecl) ? 2 : 1;
6866+
const unsigned int ParentFunctionFormatArgumentIndexOffset =
6867+
isInstanceMethod(ParentFuncDecl) ? 2 : 1;
6868+
6869+
// Check if function has format attribute with forwarded format string.
6870+
IdentifierInfo *AttrType;
6871+
const ParmVarDecl *FormatArg;
6872+
if (!llvm::any_of(
6873+
FDecl->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
6874+
const int FormatIndexOffseted =
6875+
Attr->getFormatIdx() - FormatArgumentIndexOffset;
6876+
if (FormatIndexOffseted < 0 ||
6877+
(unsigned)FormatIndexOffseted >= Args.size())
6878+
return false;
6879+
6880+
const DeclRefExpr *FormatArgExpr = dyn_cast_or_null<DeclRefExpr>(
6881+
Args[FormatIndexOffseted]->IgnoreParenCasts());
6882+
if (!FormatArgExpr)
6883+
return false;
6884+
6885+
FormatArg = dyn_cast_or_null<ParmVarDecl>(
6886+
FormatArgExpr->getReferencedDeclOfCallee());
6887+
if (!FormatArg)
6888+
return false;
6889+
6890+
AttrType = Attr->getType();
6891+
return true;
6892+
}))
6893+
return;
6894+
6895+
// Check if format string argument is parent function parameter.
6896+
unsigned int StringIndex = 0;
6897+
if (!llvm::any_of(ParentFuncDecl->parameters(),
6898+
[&](const ParmVarDecl *Param) {
6899+
StringIndex = Param->getFunctionScopeIndex() +
6900+
ParentFunctionFormatArgumentIndexOffset;
6901+
6902+
return Param == FormatArg;
6903+
}))
6904+
return;
6905+
6906+
unsigned NumOfParentFunctionParams = ParentFuncDecl->getNumParams();
6907+
6908+
// Compare parent and calling function format attribute arguments (archetype
6909+
// and format string).
6910+
if (llvm::any_of(
6911+
ParentFuncDecl->specific_attrs<FormatAttr>(),
6912+
[&](const FormatAttr *Attr) {
6913+
if (Attr->getType() != AttrType)
6914+
return false;
6915+
const int FormatIndexOffseted =
6916+
Attr->getFormatIdx() - ParentFunctionFormatArgumentIndexOffset;
6917+
6918+
if (FormatIndexOffseted < 0 ||
6919+
(unsigned)FormatIndexOffseted >= NumOfParentFunctionParams)
6920+
return false;
6921+
6922+
if (ParentFuncDecl->parameters()[FormatIndexOffseted] != FormatArg)
6923+
return false;
6924+
6925+
return true;
6926+
}))
6927+
return;
6928+
6929+
// If parent function is variadic, check if last argument of child function is
6930+
// va_list.
6931+
unsigned FirstToCheck = [&]() -> unsigned {
6932+
if (!ParentFuncDecl->isVariadic())
6933+
return 0;
6934+
const DeclRefExpr *FirstToCheckArg = dyn_cast_or_null<DeclRefExpr>(
6935+
Args[Args.size() - 1]->IgnoreParenCasts());
6936+
if (!FirstToCheckArg)
6937+
return 0;
6938+
6939+
if (FirstToCheckArg->getType().getAsString() != "va_list")
6940+
return 0;
6941+
return NumOfParentFunctionParams + ParentFunctionFormatArgumentIndexOffset;
6942+
}();
6943+
6944+
// Emit warning
6945+
std::string InsertionText;
6946+
llvm::raw_string_ostream OS(InsertionText);
6947+
OS << "__attribute__((format(" << AttrType->getName() << ", " << StringIndex
6948+
<< ", " << FirstToCheck << ")))";
6949+
SourceLocation ParentFuncLoc = ParentFuncDecl->getLocation();
6950+
Diag(ParentFuncLoc, diag::warn_missing_format_attribute)
6951+
<< AttrType << ParentFuncDecl
6952+
<< FixItHint::CreateInsertion(ParentFuncLoc, InsertionText);
6953+
}
6954+
68526955
//===----------------------------------------------------------------------===//
68536956
// Microsoft specific attribute handlers.
68546957
//===----------------------------------------------------------------------===//

clang/test/Sema/attr-format-missing.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -Wmissing-format-attribute %s
2+
3+
#include <stdarg.h>
4+
#include <stdio.h>
5+
#include <uchar.h>
6+
#include <wchar.h>
7+
8+
__attribute__((__format__ (__scanf__, 1, 4)))
9+
void f1(char *out, const size_t len, const char *format, ... /* args */)
10+
{
11+
va_list args;
12+
vsnprintf(out, len, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f1'}}
13+
// CHECK-FIXES: __attribute__((format(printf, 3, 4)))
14+
}
15+
16+
__attribute__((__format__ (__printf__, 1, 4)))
17+
void f2(char *out, const size_t len, const char *format, ... /* args */)
18+
{
19+
va_list args;
20+
vsnprintf(out, len, format, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f2'}}
21+
// CHECK-FIXES: __attribute__((format(printf, 3, 4)))
22+
}
23+
24+
void f3(char *out, va_list args)
25+
{
26+
vprintf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f3'}}
27+
// CHECK-FIXES: __attribute__((format(printf, 1, 0)))
28+
vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f3'}}
29+
// CHECK-FIXES: __attribute__((format(scanf, 1, 0)))
30+
}
31+
32+
void f4(char* out, ... /* args */)
33+
{
34+
va_list args;
35+
vprintf("test", args); // no warning
36+
37+
const char *ch;
38+
vscanf(ch, args); // no warning
39+
}
40+
41+
void f5(va_list args)
42+
{
43+
char *ch;
44+
vscanf(ch, args); // no warning
45+
}
46+
47+
void f6(char *out, va_list args)
48+
{
49+
char *ch;
50+
vscanf(ch, args); // no warning
51+
vprintf("test", args); // no warning
52+
}
53+
54+
void f7(const char *out, ... /* args */)
55+
{
56+
va_list args;
57+
58+
vscanf(out, &args[0]); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f7'}}
59+
// CHECK-FIXES: __attribute__((format(scanf, 1, 0)))
60+
vprintf(out, &args[0]); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f7'}}
61+
// CHECK-FIXES: __attribute__((format(printf, 1, 0)))
62+
}
63+
64+
__attribute__((format(scanf, 1, 0)))
65+
__attribute__((format(printf, 1, 2)))
66+
void f8(const char *out, ... /* args */)
67+
{
68+
va_list args;
69+
70+
vscanf(out, &args[0]); // no warning
71+
vprintf(out, &args[0]); // no warning
72+
}
73+
74+
void f9(const char out[], ... /* args */)
75+
{
76+
va_list args;
77+
vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f9'}}
78+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
79+
char *ch;
80+
vprintf(ch, args); // no warning
81+
}
82+
83+
void f10(const wchar_t *out, ... /* args */)
84+
{
85+
va_list args;
86+
vprintf(out, args); // expected-warning {{incompatible pointer types passing 'const wchar_t *' (aka 'const int *') to parameter of type 'const char *'}}
87+
// expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}}
88+
// CHECK-FIXES: __attribute__((format(printf, 1, 2)))
89+
vscanf((const char *) out, args); // no warning
90+
// expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}}
91+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
92+
vscanf((char *) out, args); // no warning
93+
// expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f10'}}
94+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
95+
}
96+
97+
__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
98+
void f11(const wchar_t *out, ... /* args */);
99+
100+
void f12(const char16_t *out, ... /* args */)
101+
{
102+
va_list args;
103+
vscanf(out, args); // expected-warning {{incompatible pointer types passing 'const char16_t *' (aka 'const unsigned short *') to parameter of type 'const char *'}}
104+
// expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f12'}}
105+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
106+
}
107+
108+
__attribute__((format(printf, 1, 2))) // expected-error {{format argument not a string type}}
109+
void f13(const char16_t *out, ... /* args */);
110+
111+
void f14(const char32_t *out, ... /* args */)
112+
{
113+
va_list args;
114+
vscanf(out, args); // expected-warning {{incompatible pointer types passing 'const char32_t *' (aka 'const unsigned int *') to parameter of type 'const char *'}}
115+
// expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f14'}}
116+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
117+
}
118+
119+
__attribute__((format(scanf, 1, 2))) // expected-error {{format argument not a string type}}
120+
void f15(const char32_t *out, ... /* args */);
121+
122+
void f16(const unsigned char *out, ... /* args */)
123+
{
124+
va_list args;
125+
vprintf(out, args); // expected-warning {{passing 'const unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
126+
// expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f16'}}
127+
// CHECK-FIXES: __attribute__((format(printf, 1, 2)))
128+
vscanf((const char *) out, args); // no warning
129+
// expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}}
130+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
131+
vscanf((char *) out, args); // no warning
132+
// expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f16'}}
133+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
134+
}
135+
136+
__attribute__((format(printf, 1, 2)))
137+
void f17(const unsigned char *out, ... /* args */)
138+
{
139+
va_list args;
140+
vprintf(out, args); // expected-warning {{passing 'const unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
141+
vscanf((const char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}}
142+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
143+
vprintf((const char *) out, args); // no warning
144+
vscanf((char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f17'}}
145+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
146+
vprintf((char *) out, args); // no warning
147+
}
148+
149+
void f18(signed char *out, ... /* args */)
150+
{
151+
va_list args;
152+
vscanf(out, args); // expected-warning {{passing 'signed char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
153+
// expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f18'}}
154+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
155+
vscanf((const char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f18'}}
156+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
157+
vprintf((char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f18'}}
158+
// CHECK-FIXES: __attribute__((format(printf, 1, 2)))
159+
}
160+
161+
__attribute__((format(scanf, 1, 2)))
162+
void f19(signed char *out, ... /* args */)
163+
{
164+
va_list args;
165+
vprintf(out, args); // expected-warning {{passing 'signed char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
166+
// expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}}
167+
// CHECK-FIXES: __attribute__((format(printf, 1, 2)))
168+
vscanf((const char *) out, args); // no warning
169+
vprintf((const char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}}
170+
// CHECK-FIXES: __attribute__((format(printf, 1, 2)))
171+
vscanf((char *) out, args); // no warning
172+
vprintf((char *) out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f19'}}
173+
// CHECK-FIXES: __attribute__((format(printf, 1, 2)))
174+
}
175+
176+
__attribute__((format(printf, 1, 2)))
177+
void f20(unsigned char out[], ... /* args */)
178+
{
179+
va_list args;
180+
vprintf(out, args); // expected-warning {{passing 'unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
181+
vscanf(out, args); // expected-warning {{passing 'unsigned char *' to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not}}
182+
// expected-warning {{diagnostic behavior may be improved by adding the 'scanf' format attribute to the declaration of 'f20'}}
183+
// CHECK-FIXES: __attribute__((format(scanf, 1, 2)))
184+
}
185+
186+
void f21(char* out) {
187+
va_list args;
188+
const char* ch;
189+
vsprintf(out, ch, args); // no warning
190+
vscanf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f21'}}
191+
// CHECK-FIXES: __attribute__((format(scanf, 1, 0)))
192+
}
193+
194+
void f22(const char *out, ... /* args */)
195+
{
196+
int a;
197+
printf(out, a); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f22'}}
198+
// CHECK-FIXES: __attribute__((format(printf, 1, 0)))
199+
printf(out, 1); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f22'}}
200+
// CHECK-FIXES: __attribute__((format(printf, 1, 0)))
201+
}
202+
203+
__attribute__((format(printf, 1, 2)))
204+
void f23(const char *out, ... /* args */)
205+
{
206+
int a;
207+
printf(out, a); // no warning
208+
printf(out, 1); // no warning
209+
}
210+
211+
void f24(char* ch, const char *out, ... /* args */)
212+
{
213+
va_list args;
214+
printf(ch, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24}}
215+
// CHECK-FIXES: __attribute__((format(printf, 1, 3)))
216+
int a;
217+
printf(out, a); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}}
218+
// CHECK-FIXES: __attribute__((format(printf, 2, 0)))
219+
printf(out, 1); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}}
220+
// CHECK-FIXES: __attribute__((format(printf, 2, 0)))
221+
printf(out, args); // expected-warning {{diagnostic behavior may be improved by adding the 'printf' format attribute to the declaration of 'f24'}}
222+
// CHECK-FIXES: __attribute__((format(printf, 2, 3)))
223+
}

0 commit comments

Comments
 (0)