xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp (revision ed740e741ec22f9aaea09bfc0b87d0801a7c492f)
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 "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/PPCallbacks.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include <cassert>
15 
16 using namespace clang::ast_matchers;
17 using namespace llvm;
18 
19 namespace clang {
20 namespace tidy {
21 namespace bugprone {
22 
23 static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions =
24     "ReportMoreUnsafeFunctions";
25 
26 static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId =
27     "FunctionNamesWithAnnexKReplacement";
28 static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames";
29 static constexpr llvm::StringLiteral AdditionalFunctionNamesId =
30     "AdditionalFunctionsNames";
31 static constexpr llvm::StringLiteral DeclRefId = "DRE";
32 
33 static std::optional<std::string>
34 getAnnexKReplacementFor(StringRef FunctionName) {
35   return StringSwitch<std::string>(FunctionName)
36       .Case("strlen", "strnlen_s")
37       .Case("wcslen", "wcsnlen_s")
38       .Default((Twine{FunctionName} + "_s").str());
39 }
40 
41 static StringRef getReplacementFor(StringRef FunctionName,
42                                    bool IsAnnexKAvailable) {
43   if (IsAnnexKAvailable) {
44     // Try to find a better replacement from Annex K first.
45     StringRef AnnexKReplacementFunction =
46         StringSwitch<StringRef>(FunctionName)
47             .Cases("asctime", "asctime_r", "asctime_s")
48             .Case("gets", "gets_s")
49             .Default({});
50     if (!AnnexKReplacementFunction.empty())
51       return AnnexKReplacementFunction;
52   }
53 
54   // FIXME: Some of these functions are available in C++ under "std::", and
55   // should be matched and suggested.
56   return StringSwitch<StringRef>(FunctionName)
57       .Cases("asctime", "asctime_r", "strftime")
58       .Case("gets", "fgets")
59       .Case("rewind", "fseek")
60       .Case("setbuf", "setvbuf");
61 }
62 
63 static StringRef getReplacementForAdditional(StringRef FunctionName,
64                                              bool IsAnnexKAvailable) {
65   if (IsAnnexKAvailable) {
66     // Try to find a better replacement from Annex K first.
67     StringRef AnnexKReplacementFunction = StringSwitch<StringRef>(FunctionName)
68                                               .Case("bcopy", "memcpy_s")
69                                               .Case("bzero", "memset_s")
70                                               .Default({});
71 
72     if (!AnnexKReplacementFunction.empty())
73       return AnnexKReplacementFunction;
74   }
75 
76   return StringSwitch<StringRef>(FunctionName)
77       .Case("bcmp", "memcmp")
78       .Case("bcopy", "memcpy")
79       .Case("bzero", "memset")
80       .Case("getpw", "getpwuid")
81       .Case("vfork", "posix_spawn");
82 }
83 
84 /// \returns The rationale for replacing the function \p FunctionName with the
85 /// safer alternative.
86 static StringRef getRationaleFor(StringRef FunctionName) {
87   return StringSwitch<StringRef>(FunctionName)
88       .Cases("asctime", "asctime_r", "ctime",
89              "is not bounds-checking and non-reentrant")
90       .Cases("bcmp", "bcopy", "bzero", "is deprecated")
91       .Cases("fopen", "freopen", "has no exclusive access to the opened file")
92       .Case("gets", "is insecure, was deprecated and removed in C11 and C++14")
93       .Case("getpw", "is dangerous as it may overflow the provided buffer")
94       .Cases("rewind", "setbuf", "has no error detection")
95       .Case("vfork", "is insecure as it can lead to denial of service "
96                      "situations in the parent process")
97       .Default("is not bounds-checking");
98 }
99 
100 /// Calculates whether Annex K is available for the current translation unit
101 /// based on the macro definitions and the language options.
102 ///
103 /// The result is cached and saved in \p CacheVar.
104 static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP,
105                               const LangOptions &LO) {
106   if (CacheVar.has_value())
107     return *CacheVar;
108 
109   if (!LO.C11)
110     // TODO: How is "Annex K" available in C++ mode?
111     return (CacheVar = false).value();
112 
113   assert(PP && "No Preprocessor registered.");
114 
115   if (!PP->isMacroDefined("__STDC_LIB_EXT1__") ||
116       !PP->isMacroDefined("__STDC_WANT_LIB_EXT1__"))
117     return (CacheVar = false).value();
118 
119   const auto *MI =
120       PP->getMacroInfo(PP->getIdentifierInfo("__STDC_WANT_LIB_EXT1__"));
121   if (!MI || MI->tokens_empty())
122     return (CacheVar = false).value();
123 
124   const Token &T = MI->tokens().back();
125   if (!T.isLiteral() || !T.getLiteralData())
126     return (CacheVar = false).value();
127 
128   CacheVar = StringRef(T.getLiteralData(), T.getLength()) == "1";
129   return CacheVar.value();
130 }
131 
132 UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name,
133                                            ClangTidyContext *Context)
134     : ClangTidyCheck(Name, Context),
135       ReportMoreUnsafeFunctions(
136           Options.get(OptionNameReportMoreUnsafeFunctions, true)) {}
137 
138 void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
139   Options.store(Opts, OptionNameReportMoreUnsafeFunctions,
140                 ReportMoreUnsafeFunctions);
141 }
142 
143 void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) {
144   if (getLangOpts().C11) {
145     // Matching functions with safe replacements only in Annex K.
146     auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName(
147         "::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", "::fscanf",
148         "::fwprintf", "::fwscanf", "::getenv", "::gmtime", "::localtime",
149         "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", "::memset",
150         "::printf", "::qsort", "::scanf", "::snprintf", "::sprintf", "::sscanf",
151         "::strcat", "::strcpy", "::strerror", "::strlen", "::strncat",
152         "::strncpy", "::strtok", "::swprintf", "::swscanf", "::vfprintf",
153         "::vfscanf", "::vfwprintf", "::vfwscanf", "::vprintf", "::vscanf",
154         "::vsnprintf", "::vsprintf", "::vsscanf", "::vswprintf", "::vswscanf",
155         "::vwprintf", "::vwscanf", "::wcrtomb", "::wcscat", "::wcscpy",
156         "::wcslen", "::wcsncat", "::wcsncpy", "::wcsrtombs", "::wcstok",
157         "::wcstombs", "::wctomb", "::wmemcpy", "::wmemmove", "::wprintf",
158         "::wscanf");
159     Finder->addMatcher(
160         declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher)
161                            .bind(FunctionNamesWithAnnexKReplacementId)))
162             .bind(DeclRefId),
163         this);
164   }
165 
166   // Matching functions with replacements without Annex K.
167   auto FunctionNamesMatcher =
168       hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf");
169   Finder->addMatcher(
170       declRefExpr(to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId)))
171           .bind(DeclRefId),
172       this);
173 
174   if (ReportMoreUnsafeFunctions) {
175     // Matching functions with replacements without Annex K, at user request.
176     auto AdditionalFunctionNamesMatcher =
177         hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork");
178     Finder->addMatcher(
179         declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher)
180                            .bind(AdditionalFunctionNamesId)))
181             .bind(DeclRefId),
182         this);
183   }
184 }
185 
186 void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
187   const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefId);
188   const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl());
189   assert(DeclRef && FuncDecl && "No valid matched node in check()");
190 
191   const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>(
192       FunctionNamesWithAnnexKReplacementId);
193   const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(FunctionNamesId);
194   const auto *Additional =
195       Result.Nodes.getNodeAs<FunctionDecl>(AdditionalFunctionNamesId);
196   assert((AnnexK || Normal || Additional) && "No valid match category.");
197 
198   bool AnnexKIsAvailable =
199       isAnnexKAvailable(IsAnnexKAvailable, PP, getLangOpts());
200   StringRef FunctionName = FuncDecl->getName();
201   const std::optional<std::string> ReplacementFunctionName =
202       [&]() -> std::optional<std::string> {
203     if (AnnexK) {
204       if (AnnexKIsAvailable)
205         return getAnnexKReplacementFor(FunctionName);
206       return std::nullopt;
207     }
208 
209     if (Normal)
210       return getReplacementFor(FunctionName, AnnexKIsAvailable).str();
211 
212     if (Additional)
213       return getReplacementForAdditional(FunctionName, AnnexKIsAvailable).str();
214 
215     llvm_unreachable("Unhandled match category");
216   }();
217   if (!ReplacementFunctionName)
218     return;
219 
220   diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead")
221       << FuncDecl << getRationaleFor(FunctionName)
222       << ReplacementFunctionName.value() << DeclRef->getSourceRange();
223 }
224 
225 void UnsafeFunctionsCheck::registerPPCallbacks(
226     const SourceManager &SM, Preprocessor *PP,
227     Preprocessor * /*ModuleExpanderPP*/) {
228   this->PP = PP;
229   IsAnnexKAvailable.reset();
230 }
231 
232 } // namespace bugprone
233 } // namespace tidy
234 } // namespace clang
235