xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp (revision 0b8866d15ac5806a980d2ff2ea63240d8acfa778)
1 //===--- UnsafeFunctionsCheck.cpp - clang-tidy ----------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "UnsafeFunctionsCheck.h"
10 #include "../utils/OptionsUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/PPCallbacks.h"
14 #include "clang/Lex/Preprocessor.h"
15 #include <cassert>
16 
17 using namespace clang::ast_matchers;
18 using namespace llvm;
19 
20 namespace clang::tidy::bugprone {
21 
22 static constexpr llvm::StringLiteral OptionNameCustomFunctions =
23     "CustomFunctions";
24 static constexpr llvm::StringLiteral OptionNameReportDefaultFunctions =
25     "ReportDefaultFunctions";
26 static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions =
27     "ReportMoreUnsafeFunctions";
28 
29 static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId =
30     "FunctionNamesWithAnnexKReplacement";
31 static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames";
32 static constexpr llvm::StringLiteral AdditionalFunctionNamesId =
33     "AdditionalFunctionsNames";
34 static constexpr llvm::StringLiteral CustomFunctionNamesId =
35     "CustomFunctionNames";
36 static constexpr llvm::StringLiteral DeclRefId = "DRE";
37 
38 static std::optional<std::string>
39 getAnnexKReplacementFor(StringRef FunctionName) {
40   return StringSwitch<std::string>(FunctionName)
41       .Case("strlen", "strnlen_s")
42       .Case("wcslen", "wcsnlen_s")
43       .Default((Twine{FunctionName} + "_s").str());
44 }
45 
46 static StringRef getReplacementFor(StringRef FunctionName,
47                                    bool IsAnnexKAvailable) {
48   if (IsAnnexKAvailable) {
49     // Try to find a better replacement from Annex K first.
50     StringRef AnnexKReplacementFunction =
51         StringSwitch<StringRef>(FunctionName)
52             .Cases("asctime", "asctime_r", "asctime_s")
53             .Case("gets", "gets_s")
54             .Default({});
55     if (!AnnexKReplacementFunction.empty())
56       return AnnexKReplacementFunction;
57   }
58 
59   // FIXME: Some of these functions are available in C++ under "std::", and
60   // should be matched and suggested.
61   return StringSwitch<StringRef>(FunctionName)
62       .Cases("asctime", "asctime_r", "strftime")
63       .Case("gets", "fgets")
64       .Case("rewind", "fseek")
65       .Case("setbuf", "setvbuf");
66 }
67 
68 static StringRef getReplacementForAdditional(StringRef FunctionName,
69                                              bool IsAnnexKAvailable) {
70   if (IsAnnexKAvailable) {
71     // Try to find a better replacement from Annex K first.
72     StringRef AnnexKReplacementFunction = StringSwitch<StringRef>(FunctionName)
73                                               .Case("bcopy", "memcpy_s")
74                                               .Case("bzero", "memset_s")
75                                               .Default({});
76 
77     if (!AnnexKReplacementFunction.empty())
78       return AnnexKReplacementFunction;
79   }
80 
81   return StringSwitch<StringRef>(FunctionName)
82       .Case("bcmp", "memcmp")
83       .Case("bcopy", "memcpy")
84       .Case("bzero", "memset")
85       .Case("getpw", "getpwuid")
86       .Case("vfork", "posix_spawn");
87 }
88 
89 /// \returns The rationale for replacing the function \p FunctionName with the
90 /// safer alternative.
91 static StringRef getRationaleFor(StringRef FunctionName) {
92   return StringSwitch<StringRef>(FunctionName)
93       .Cases("asctime", "asctime_r", "ctime",
94              "is not bounds-checking and non-reentrant")
95       .Cases("bcmp", "bcopy", "bzero", "is deprecated")
96       .Cases("fopen", "freopen", "has no exclusive access to the opened file")
97       .Case("gets", "is insecure, was deprecated and removed in C11 and C++14")
98       .Case("getpw", "is dangerous as it may overflow the provided buffer")
99       .Cases("rewind", "setbuf", "has no error detection")
100       .Case("vfork", "is insecure as it can lead to denial of service "
101                      "situations in the parent process")
102       .Default("is not bounds-checking");
103 }
104 
105 /// Calculates whether Annex K is available for the current translation unit
106 /// based on the macro definitions and the language options.
107 ///
108 /// The result is cached and saved in \p CacheVar.
109 static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP,
110                               const LangOptions &LO) {
111   if (CacheVar.has_value())
112     return *CacheVar;
113 
114   if (!LO.C11)
115     // TODO: How is "Annex K" available in C++ mode?
116     return (CacheVar = false).value();
117 
118   assert(PP && "No Preprocessor registered.");
119 
120   if (!PP->isMacroDefined("__STDC_LIB_EXT1__") ||
121       !PP->isMacroDefined("__STDC_WANT_LIB_EXT1__"))
122     return (CacheVar = false).value();
123 
124   const auto *MI =
125       PP->getMacroInfo(PP->getIdentifierInfo("__STDC_WANT_LIB_EXT1__"));
126   if (!MI || MI->tokens_empty())
127     return (CacheVar = false).value();
128 
129   const Token &T = MI->tokens().back();
130   if (!T.isLiteral() || !T.getLiteralData())
131     return (CacheVar = false).value();
132 
133   CacheVar = StringRef(T.getLiteralData(), T.getLength()) == "1";
134   return CacheVar.value();
135 }
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 
183 UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name,
184                                            ClangTidyContext *Context)
185     : ClangTidyCheck(Name, Context),
186       CustomFunctions(parseCheckedFunctions(
187           Options.get(OptionNameCustomFunctions, ""), Context)),
188       ReportDefaultFunctions(
189           Options.get(OptionNameReportDefaultFunctions, true)),
190       ReportMoreUnsafeFunctions(
191           Options.get(OptionNameReportMoreUnsafeFunctions, true)) {}
192 
193 void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
194   Options.store(Opts, OptionNameCustomFunctions,
195                 serializeCheckedFunctions(CustomFunctions));
196   Options.store(Opts, OptionNameReportDefaultFunctions, ReportDefaultFunctions);
197   Options.store(Opts, OptionNameReportMoreUnsafeFunctions,
198                 ReportMoreUnsafeFunctions);
199 }
200 
201 void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) {
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");
228     Finder->addMatcher(
229         declRefExpr(
230             to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId)))
231             .bind(DeclRefId),
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     }
244   }
245 
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);
259   }
260 }
261 
262 void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
263   const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefId);
264   const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl());
265   assert(DeclRef && FuncDecl && "No valid matched node in check()");
266 
267   // Only one of these are matched at a time.
268   const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>(
269       FunctionNamesWithAnnexKReplacementId);
270   const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(FunctionNamesId);
271   const auto *Additional =
272       Result.Nodes.getNodeAs<FunctionDecl>(AdditionalFunctionNamesId);
273   const auto *Custom =
274       Result.Nodes.getNodeAs<FunctionDecl>(CustomFunctionNamesId);
275   assert((AnnexK || Normal || Additional || Custom) &&
276          "No valid match category.");
277 
278   bool AnnexKIsAvailable =
279       isAnnexKAvailable(IsAnnexKAvailable, PP, getLangOpts());
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 
307   const std::optional<std::string> ReplacementFunctionName =
308       [&]() -> std::optional<std::string> {
309     if (AnnexK) {
310       if (AnnexKIsAvailable)
311         return getAnnexKReplacementFor(FunctionName);
312       return std::nullopt;
313     }
314 
315     if (Normal)
316       return getReplacementFor(FunctionName, AnnexKIsAvailable).str();
317 
318     if (Additional)
319       return getReplacementForAdditional(FunctionName, AnnexKIsAvailable).str();
320 
321     llvm_unreachable("Unhandled match category");
322   }();
323   if (!ReplacementFunctionName)
324     return;
325 
326   diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead")
327       << FuncDecl << getRationaleFor(FunctionName)
328       << ReplacementFunctionName.value() << DeclRef->getSourceRange();
329 }
330 
331 void UnsafeFunctionsCheck::registerPPCallbacks(
332     const SourceManager &SM, Preprocessor *PP,
333     Preprocessor * /*ModuleExpanderPP*/) {
334   this->PP = PP;
335 }
336 
337 void UnsafeFunctionsCheck::onEndOfTranslationUnit() {
338   this->PP = nullptr;
339   IsAnnexKAvailable.reset();
340 }
341 
342 } // namespace clang::tidy::bugprone
343