77// ===----------------------------------------------------------------------===//
88
99#include " UnsafeFunctionsCheck.h"
10+ #include " ../utils/OptionsUtils.h"
1011#include " clang/AST/ASTContext.h"
1112#include " clang/ASTMatchers/ASTMatchFinder.h"
1213#include " clang/Lex/PPCallbacks.h"
@@ -18,6 +19,10 @@ using namespace llvm;
1819
1920namespace clang ::tidy::bugprone {
2021
22+ static constexpr llvm::StringLiteral OptionNameCustomFunctions =
23+ " CustomFunctions" ;
24+ static constexpr llvm::StringLiteral OptionNameReportDefaultFunctions =
25+ " ReportDefaultFunctions" ;
2126static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions =
2227 " ReportMoreUnsafeFunctions" ;
2328
@@ -26,6 +31,8 @@ static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId =
2631static constexpr llvm::StringLiteral FunctionNamesId = " FunctionsNames" ;
2732static constexpr llvm::StringLiteral AdditionalFunctionNamesId =
2833 " AdditionalFunctionsNames" ;
34+ static constexpr llvm::StringLiteral CustomFunctionNamesId =
35+ " CustomFunctionNames" ;
2936static constexpr llvm::StringLiteral DeclRefId = " DRE" ;
3037
3138static std::optional<std::string>
@@ -127,57 +134,128 @@ static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP,
127134 return CacheVar.value ();
128135}
129136
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+
130183UnsafeFunctionsCheck::UnsafeFunctionsCheck (StringRef Name,
131184 ClangTidyContext *Context)
132185 : ClangTidyCheck(Name, Context),
186+ CustomFunctions (parseCheckedFunctions(
187+ Options.get(OptionNameCustomFunctions, " " ), Context)),
188+ ReportDefaultFunctions(
189+ Options.get(OptionNameReportDefaultFunctions, true )),
133190 ReportMoreUnsafeFunctions(
134191 Options.get(OptionNameReportMoreUnsafeFunctions, true )) {}
135192
136193void UnsafeFunctionsCheck::storeOptions (ClangTidyOptions::OptionMap &Opts) {
194+ Options.store (Opts, OptionNameCustomFunctions,
195+ serializeCheckedFunctions (CustomFunctions));
196+ Options.store (Opts, OptionNameReportDefaultFunctions, ReportDefaultFunctions);
137197 Options.store (Opts, OptionNameReportMoreUnsafeFunctions,
138198 ReportMoreUnsafeFunctions);
139199}
140200
141201void 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" );
157228 Finder->addMatcher (
158- declRefExpr (to ( functionDecl (FunctionNamesWithAnnexKReplacementMatcher)
159- .bind (FunctionNamesWithAnnexKReplacementId )))
229+ declRefExpr (
230+ to ( functionDecl (FunctionNamesMatcher) .bind (FunctionNamesId )))
160231 .bind (DeclRefId),
161232 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+ }
162244 }
163245
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 );
181259 }
182260}
183261
@@ -186,16 +264,46 @@ void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
186264 const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl ());
187265 assert (DeclRef && FuncDecl && " No valid matched node in check()" );
188266
267+ // Only one of these are matched at a time.
189268 const auto *AnnexK = Result.Nodes .getNodeAs <FunctionDecl>(
190269 FunctionNamesWithAnnexKReplacementId);
191270 const auto *Normal = Result.Nodes .getNodeAs <FunctionDecl>(FunctionNamesId);
192271 const auto *Additional =
193272 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." );
195277
196278 bool AnnexKIsAvailable =
197279 isAnnexKAvailable (IsAnnexKAvailable, PP, getLangOpts ());
198280 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+
199307 const std::optional<std::string> ReplacementFunctionName =
200308 [&]() -> std::optional<std::string> {
201309 if (AnnexK) {
0 commit comments