7
7
// ===----------------------------------------------------------------------===//
8
8
9
9
#include " UnsafeFunctionsCheck.h"
10
+ #include " ../utils/OptionsUtils.h"
10
11
#include " clang/AST/ASTContext.h"
11
12
#include " clang/ASTMatchers/ASTMatchFinder.h"
12
13
#include " clang/Lex/PPCallbacks.h"
@@ -18,6 +19,10 @@ using namespace llvm;
18
19
19
20
namespace clang ::tidy::bugprone {
20
21
22
+ static constexpr llvm::StringLiteral OptionNameCustomFunctions =
23
+ " CustomFunctions" ;
24
+ static constexpr llvm::StringLiteral OptionNameReportDefaultFunctions =
25
+ " ReportDefaultFunctions" ;
21
26
static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions =
22
27
" ReportMoreUnsafeFunctions" ;
23
28
@@ -26,6 +31,8 @@ static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId =
26
31
static constexpr llvm::StringLiteral FunctionNamesId = " FunctionsNames" ;
27
32
static constexpr llvm::StringLiteral AdditionalFunctionNamesId =
28
33
" AdditionalFunctionsNames" ;
34
+ static constexpr llvm::StringLiteral CustomFunctionNamesId =
35
+ " CustomFunctionNames" ;
29
36
static constexpr llvm::StringLiteral DeclRefId = " DRE" ;
30
37
31
38
static std::optional<std::string>
@@ -127,57 +134,128 @@ static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP,
127
134
return CacheVar.value ();
128
135
}
129
136
137
+ static std::vector<UnsafeFunctionsCheck::CheckedFunction>
138
+ parseCheckedFunctions (StringRef Option, ClangTidyContext *Context) {
139
+ const std::vector<StringRef> Functions =
140
+ utils::options::parseStringList (Option);
141
+ std::vector<UnsafeFunctionsCheck::CheckedFunction> Result;
142
+ Result.reserve (Functions.size ());
143
+
144
+ for (StringRef Function : Functions) {
145
+ if (Function.empty ())
146
+ continue ;
147
+
148
+ const auto [Name, Rest] = Function.split (' ,' );
149
+ const auto [Replacement, Reason] = Rest.split (' ,' );
150
+
151
+ if (Name.trim ().empty ()) {
152
+ Context->configurationDiag (" invalid configuration value for option '%0'; "
153
+ " expected the name of an unsafe function" )
154
+ << OptionNameCustomFunctions;
155
+ continue ;
156
+ }
157
+
158
+ Result.push_back (
159
+ {Name.trim ().str (),
160
+ matchers::MatchesAnyListedNameMatcher::NameMatcher (Name.trim ()),
161
+ Replacement.trim ().str (), Reason.trim ().str ()});
162
+ }
163
+
164
+ return Result;
165
+ }
166
+
167
+ static std::string serializeCheckedFunctions (
168
+ const std::vector<UnsafeFunctionsCheck::CheckedFunction> &Functions) {
169
+ std::vector<std::string> Result;
170
+ Result.reserve (Functions.size ());
171
+
172
+ for (const auto &Entry : Functions) {
173
+ if (Entry.Reason .empty ())
174
+ Result.push_back (Entry.Name + " ," + Entry.Replacement );
175
+ else
176
+ Result.push_back (Entry.Name + " ," + Entry.Replacement + " ," +
177
+ Entry.Reason );
178
+ }
179
+
180
+ return llvm::join (Result, " ;" );
181
+ }
182
+
130
183
UnsafeFunctionsCheck::UnsafeFunctionsCheck (StringRef Name,
131
184
ClangTidyContext *Context)
132
185
: ClangTidyCheck(Name, Context),
186
+ CustomFunctions (parseCheckedFunctions(
187
+ Options.get(OptionNameCustomFunctions, " " ), Context)),
188
+ ReportDefaultFunctions(
189
+ Options.get(OptionNameReportDefaultFunctions, true )),
133
190
ReportMoreUnsafeFunctions(
134
191
Options.get(OptionNameReportMoreUnsafeFunctions, true )) {}
135
192
136
193
void UnsafeFunctionsCheck::storeOptions (ClangTidyOptions::OptionMap &Opts) {
194
+ Options.store (Opts, OptionNameCustomFunctions,
195
+ serializeCheckedFunctions (CustomFunctions));
196
+ Options.store (Opts, OptionNameReportDefaultFunctions, ReportDefaultFunctions);
137
197
Options.store (Opts, OptionNameReportMoreUnsafeFunctions,
138
198
ReportMoreUnsafeFunctions);
139
199
}
140
200
141
201
void UnsafeFunctionsCheck::registerMatchers (MatchFinder *Finder) {
142
- if (getLangOpts ().C11 ) {
143
- // Matching functions with safe replacements only in Annex K.
144
- auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName (
145
- " ::bsearch" , " ::ctime" , " ::fopen" , " ::fprintf" , " ::freopen" , " ::fscanf" ,
146
- " ::fwprintf" , " ::fwscanf" , " ::getenv" , " ::gmtime" , " ::localtime" ,
147
- " ::mbsrtowcs" , " ::mbstowcs" , " ::memcpy" , " ::memmove" , " ::memset" ,
148
- " ::printf" , " ::qsort" , " ::scanf" , " ::snprintf" , " ::sprintf" , " ::sscanf" ,
149
- " ::strcat" , " ::strcpy" , " ::strerror" , " ::strlen" , " ::strncat" ,
150
- " ::strncpy" , " ::strtok" , " ::swprintf" , " ::swscanf" , " ::vfprintf" ,
151
- " ::vfscanf" , " ::vfwprintf" , " ::vfwscanf" , " ::vprintf" , " ::vscanf" ,
152
- " ::vsnprintf" , " ::vsprintf" , " ::vsscanf" , " ::vswprintf" , " ::vswscanf" ,
153
- " ::vwprintf" , " ::vwscanf" , " ::wcrtomb" , " ::wcscat" , " ::wcscpy" ,
154
- " ::wcslen" , " ::wcsncat" , " ::wcsncpy" , " ::wcsrtombs" , " ::wcstok" ,
155
- " ::wcstombs" , " ::wctomb" , " ::wmemcpy" , " ::wmemmove" , " ::wprintf" ,
156
- " ::wscanf" );
202
+ if (ReportDefaultFunctions) {
203
+ if (getLangOpts ().C11 ) {
204
+ // Matching functions with safe replacements only in Annex K.
205
+ auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName (
206
+ " ::bsearch" , " ::ctime" , " ::fopen" , " ::fprintf" , " ::freopen" ,
207
+ " ::fscanf" , " ::fwprintf" , " ::fwscanf" , " ::getenv" , " ::gmtime" ,
208
+ " ::localtime" , " ::mbsrtowcs" , " ::mbstowcs" , " ::memcpy" , " ::memmove" ,
209
+ " ::memset" , " ::printf" , " ::qsort" , " ::scanf" , " ::snprintf" ,
210
+ " ::sprintf" , " ::sscanf" , " ::strcat" , " ::strcpy" , " ::strerror" ,
211
+ " ::strlen" , " ::strncat" , " ::strncpy" , " ::strtok" , " ::swprintf" ,
212
+ " ::swscanf" , " ::vfprintf" , " ::vfscanf" , " ::vfwprintf" , " ::vfwscanf" ,
213
+ " ::vprintf" , " ::vscanf" , " ::vsnprintf" , " ::vsprintf" , " ::vsscanf" ,
214
+ " ::vswprintf" , " ::vswscanf" , " ::vwprintf" , " ::vwscanf" , " ::wcrtomb" ,
215
+ " ::wcscat" , " ::wcscpy" , " ::wcslen" , " ::wcsncat" , " ::wcsncpy" ,
216
+ " ::wcsrtombs" , " ::wcstok" , " ::wcstombs" , " ::wctomb" , " ::wmemcpy" ,
217
+ " ::wmemmove" , " ::wprintf" , " ::wscanf" );
218
+ Finder->addMatcher (
219
+ declRefExpr (to (functionDecl (FunctionNamesWithAnnexKReplacementMatcher)
220
+ .bind (FunctionNamesWithAnnexKReplacementId)))
221
+ .bind (DeclRefId),
222
+ this );
223
+ }
224
+
225
+ // Matching functions with replacements without Annex K.
226
+ auto FunctionNamesMatcher =
227
+ hasAnyName (" ::asctime" , " asctime_r" , " ::gets" , " ::rewind" , " ::setbuf" );
157
228
Finder->addMatcher (
158
- declRefExpr (to ( functionDecl (FunctionNamesWithAnnexKReplacementMatcher)
159
- .bind (FunctionNamesWithAnnexKReplacementId )))
229
+ declRefExpr (
230
+ to ( functionDecl (FunctionNamesMatcher) .bind (FunctionNamesId )))
160
231
.bind (DeclRefId),
161
232
this );
233
+
234
+ if (ReportMoreUnsafeFunctions) {
235
+ // Matching functions with replacements without Annex K, at user request.
236
+ auto AdditionalFunctionNamesMatcher =
237
+ hasAnyName (" ::bcmp" , " ::bcopy" , " ::bzero" , " ::getpw" , " ::vfork" );
238
+ Finder->addMatcher (
239
+ declRefExpr (to (functionDecl (AdditionalFunctionNamesMatcher)
240
+ .bind (AdditionalFunctionNamesId)))
241
+ .bind (DeclRefId),
242
+ this );
243
+ }
162
244
}
163
245
164
- // Matching functions with replacements without Annex K.
165
- auto FunctionNamesMatcher =
166
- hasAnyName (" ::asctime" , " asctime_r" , " ::gets" , " ::rewind" , " ::setbuf" );
167
- Finder->addMatcher (
168
- declRefExpr (to (functionDecl (FunctionNamesMatcher).bind (FunctionNamesId)))
169
- .bind (DeclRefId),
170
- this );
171
-
172
- if (ReportMoreUnsafeFunctions) {
173
- // Matching functions with replacements without Annex K, at user request.
174
- auto AdditionalFunctionNamesMatcher =
175
- hasAnyName (" ::bcmp" , " ::bcopy" , " ::bzero" , " ::getpw" , " ::vfork" );
176
- Finder->addMatcher (
177
- declRefExpr (to (functionDecl (AdditionalFunctionNamesMatcher)
178
- .bind (AdditionalFunctionNamesId)))
179
- .bind (DeclRefId),
180
- this );
246
+ if (!CustomFunctions.empty ()) {
247
+ std::vector<llvm::StringRef> FunctionNames;
248
+ FunctionNames.reserve (CustomFunctions.size ());
249
+
250
+ for (const auto &Entry : CustomFunctions)
251
+ FunctionNames.push_back (Entry.Name );
252
+
253
+ auto CustomFunctionsMatcher = matchers::matchesAnyListedName (FunctionNames);
254
+
255
+ Finder->addMatcher (declRefExpr (to (functionDecl (CustomFunctionsMatcher)
256
+ .bind (CustomFunctionNamesId)))
257
+ .bind (DeclRefId),
258
+ this );
181
259
}
182
260
}
183
261
@@ -186,16 +264,46 @@ void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
186
264
const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl ());
187
265
assert (DeclRef && FuncDecl && " No valid matched node in check()" );
188
266
267
+ // Only one of these are matched at a time.
189
268
const auto *AnnexK = Result.Nodes .getNodeAs <FunctionDecl>(
190
269
FunctionNamesWithAnnexKReplacementId);
191
270
const auto *Normal = Result.Nodes .getNodeAs <FunctionDecl>(FunctionNamesId);
192
271
const auto *Additional =
193
272
Result.Nodes .getNodeAs <FunctionDecl>(AdditionalFunctionNamesId);
194
- assert ((AnnexK || Normal || Additional) && " No valid match category." );
273
+ const auto *Custom =
274
+ Result.Nodes .getNodeAs <FunctionDecl>(CustomFunctionNamesId);
275
+ assert ((AnnexK || Normal || Additional || Custom) &&
276
+ " No valid match category." );
195
277
196
278
bool AnnexKIsAvailable =
197
279
isAnnexKAvailable (IsAnnexKAvailable, PP, getLangOpts ());
198
280
StringRef FunctionName = FuncDecl->getName ();
281
+
282
+ if (Custom) {
283
+ for (const auto &Entry : CustomFunctions) {
284
+ if (Entry.Pattern .match (*FuncDecl)) {
285
+ StringRef Reason =
286
+ Entry.Reason .empty () ? " is marked as unsafe" : Entry.Reason .c_str ();
287
+
288
+ if (Entry.Replacement .empty ()) {
289
+ diag (DeclRef->getExprLoc (), " function %0 %1; it should not be used" )
290
+ << FuncDecl << Reason << Entry.Replacement
291
+ << DeclRef->getSourceRange ();
292
+ } else {
293
+ diag (DeclRef->getExprLoc (),
294
+ " function %0 %1; '%2' should be used instead" )
295
+ << FuncDecl << Reason << Entry.Replacement
296
+ << DeclRef->getSourceRange ();
297
+ }
298
+
299
+ return ;
300
+ }
301
+ }
302
+
303
+ llvm_unreachable (" No custom function was matched." );
304
+ return ;
305
+ }
306
+
199
307
const std::optional<std::string> ReplacementFunctionName =
200
308
[&]() -> std::optional<std::string> {
201
309
if (AnnexK) {
0 commit comments