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