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::tidy::bugprone { |
20 | |
21 | static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions = |
22 | "ReportMoreUnsafeFunctions" ; |
23 | |
24 | static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId = |
25 | "FunctionNamesWithAnnexKReplacement" ; |
26 | static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames" ; |
27 | static constexpr llvm::StringLiteral AdditionalFunctionNamesId = |
28 | "AdditionalFunctionsNames" ; |
29 | static constexpr llvm::StringLiteral DeclRefId = "DRE" ; |
30 | |
31 | static std::optional<std::string> |
32 | getAnnexKReplacementFor(StringRef FunctionName) { |
33 | return StringSwitch<std::string>(FunctionName) |
34 | .Case(S: "strlen" , Value: "strnlen_s" ) |
35 | .Case(S: "wcslen" , Value: "wcsnlen_s" ) |
36 | .Default(Value: (Twine{FunctionName} + "_s" ).str()); |
37 | } |
38 | |
39 | static StringRef getReplacementFor(StringRef FunctionName, |
40 | bool IsAnnexKAvailable) { |
41 | if (IsAnnexKAvailable) { |
42 | // Try to find a better replacement from Annex K first. |
43 | StringRef AnnexKReplacementFunction = |
44 | StringSwitch<StringRef>(FunctionName) |
45 | .Cases(S0: "asctime" , S1: "asctime_r" , Value: "asctime_s" ) |
46 | .Case(S: "gets" , Value: "gets_s" ) |
47 | .Default(Value: {}); |
48 | if (!AnnexKReplacementFunction.empty()) |
49 | return AnnexKReplacementFunction; |
50 | } |
51 | |
52 | // FIXME: Some of these functions are available in C++ under "std::", and |
53 | // should be matched and suggested. |
54 | return StringSwitch<StringRef>(FunctionName) |
55 | .Cases(S0: "asctime" , S1: "asctime_r" , Value: "strftime" ) |
56 | .Case(S: "gets" , Value: "fgets" ) |
57 | .Case(S: "rewind" , Value: "fseek" ) |
58 | .Case(S: "setbuf" , Value: "setvbuf" ); |
59 | } |
60 | |
61 | static StringRef getReplacementForAdditional(StringRef FunctionName, |
62 | bool IsAnnexKAvailable) { |
63 | if (IsAnnexKAvailable) { |
64 | // Try to find a better replacement from Annex K first. |
65 | StringRef AnnexKReplacementFunction = StringSwitch<StringRef>(FunctionName) |
66 | .Case(S: "bcopy" , Value: "memcpy_s" ) |
67 | .Case(S: "bzero" , Value: "memset_s" ) |
68 | .Default(Value: {}); |
69 | |
70 | if (!AnnexKReplacementFunction.empty()) |
71 | return AnnexKReplacementFunction; |
72 | } |
73 | |
74 | return StringSwitch<StringRef>(FunctionName) |
75 | .Case(S: "bcmp" , Value: "memcmp" ) |
76 | .Case(S: "bcopy" , Value: "memcpy" ) |
77 | .Case(S: "bzero" , Value: "memset" ) |
78 | .Case(S: "getpw" , Value: "getpwuid" ) |
79 | .Case(S: "vfork" , Value: "posix_spawn" ); |
80 | } |
81 | |
82 | /// \returns The rationale for replacing the function \p FunctionName with the |
83 | /// safer alternative. |
84 | static StringRef getRationaleFor(StringRef FunctionName) { |
85 | return StringSwitch<StringRef>(FunctionName) |
86 | .Cases(S0: "asctime" , S1: "asctime_r" , S2: "ctime" , |
87 | Value: "is not bounds-checking and non-reentrant" ) |
88 | .Cases(S0: "bcmp" , S1: "bcopy" , S2: "bzero" , Value: "is deprecated" ) |
89 | .Cases(S0: "fopen" , S1: "freopen" , Value: "has no exclusive access to the opened file" ) |
90 | .Case(S: "gets" , Value: "is insecure, was deprecated and removed in C11 and C++14" ) |
91 | .Case(S: "getpw" , Value: "is dangerous as it may overflow the provided buffer" ) |
92 | .Cases(S0: "rewind" , S1: "setbuf" , Value: "has no error detection" ) |
93 | .Case(S: "vfork" , Value: "is insecure as it can lead to denial of service " |
94 | "situations in the parent process" ) |
95 | .Default(Value: "is not bounds-checking" ); |
96 | } |
97 | |
98 | /// Calculates whether Annex K is available for the current translation unit |
99 | /// based on the macro definitions and the language options. |
100 | /// |
101 | /// The result is cached and saved in \p CacheVar. |
102 | static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP, |
103 | const LangOptions &LO) { |
104 | if (CacheVar.has_value()) |
105 | return *CacheVar; |
106 | |
107 | if (!LO.C11) |
108 | // TODO: How is "Annex K" available in C++ mode? |
109 | return (CacheVar = false).value(); |
110 | |
111 | assert(PP && "No Preprocessor registered." ); |
112 | |
113 | if (!PP->isMacroDefined(Id: "__STDC_LIB_EXT1__" ) || |
114 | !PP->isMacroDefined(Id: "__STDC_WANT_LIB_EXT1__" )) |
115 | return (CacheVar = false).value(); |
116 | |
117 | const auto *MI = |
118 | PP->getMacroInfo(II: PP->getIdentifierInfo(Name: "__STDC_WANT_LIB_EXT1__" )); |
119 | if (!MI || MI->tokens_empty()) |
120 | return (CacheVar = false).value(); |
121 | |
122 | const Token &T = MI->tokens().back(); |
123 | if (!T.isLiteral() || !T.getLiteralData()) |
124 | return (CacheVar = false).value(); |
125 | |
126 | CacheVar = StringRef(T.getLiteralData(), T.getLength()) == "1" ; |
127 | return CacheVar.value(); |
128 | } |
129 | |
130 | UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name, |
131 | ClangTidyContext *Context) |
132 | : ClangTidyCheck(Name, Context), |
133 | ReportMoreUnsafeFunctions( |
134 | Options.get(LocalName: OptionNameReportMoreUnsafeFunctions, Default: true)) {} |
135 | |
136 | void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
137 | Options.store(Options&: Opts, LocalName: OptionNameReportMoreUnsafeFunctions, |
138 | Value: ReportMoreUnsafeFunctions); |
139 | } |
140 | |
141 | 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" ); |
157 | Finder->addMatcher( |
158 | NodeMatch: declRefExpr(to(InnerMatcher: functionDecl(FunctionNamesWithAnnexKReplacementMatcher) |
159 | .bind(ID: FunctionNamesWithAnnexKReplacementId))) |
160 | .bind(ID: DeclRefId), |
161 | Action: this); |
162 | } |
163 | |
164 | // Matching functions with replacements without Annex K. |
165 | auto FunctionNamesMatcher = |
166 | hasAnyName("::asctime" , "asctime_r" , "::gets" , "::rewind" , "::setbuf" ); |
167 | Finder->addMatcher( |
168 | NodeMatch: declRefExpr(to(InnerMatcher: functionDecl(FunctionNamesMatcher).bind(ID: FunctionNamesId))) |
169 | .bind(ID: DeclRefId), |
170 | Action: 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 | NodeMatch: declRefExpr(to(InnerMatcher: functionDecl(AdditionalFunctionNamesMatcher) |
178 | .bind(ID: AdditionalFunctionNamesId))) |
179 | .bind(ID: DeclRefId), |
180 | Action: this); |
181 | } |
182 | } |
183 | |
184 | void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) { |
185 | const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(ID: DeclRefId); |
186 | const auto *FuncDecl = cast<FunctionDecl>(Val: DeclRef->getDecl()); |
187 | assert(DeclRef && FuncDecl && "No valid matched node in check()" ); |
188 | |
189 | const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>( |
190 | ID: FunctionNamesWithAnnexKReplacementId); |
191 | const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(ID: FunctionNamesId); |
192 | const auto *Additional = |
193 | Result.Nodes.getNodeAs<FunctionDecl>(ID: AdditionalFunctionNamesId); |
194 | assert((AnnexK || Normal || Additional) && "No valid match category." ); |
195 | |
196 | bool AnnexKIsAvailable = |
197 | isAnnexKAvailable(CacheVar&: IsAnnexKAvailable, PP, LO: getLangOpts()); |
198 | StringRef FunctionName = FuncDecl->getName(); |
199 | const std::optional<std::string> ReplacementFunctionName = |
200 | [&]() -> std::optional<std::string> { |
201 | if (AnnexK) { |
202 | if (AnnexKIsAvailable) |
203 | return getAnnexKReplacementFor(FunctionName); |
204 | return std::nullopt; |
205 | } |
206 | |
207 | if (Normal) |
208 | return getReplacementFor(FunctionName, IsAnnexKAvailable: AnnexKIsAvailable).str(); |
209 | |
210 | if (Additional) |
211 | return getReplacementForAdditional(FunctionName, IsAnnexKAvailable: AnnexKIsAvailable).str(); |
212 | |
213 | llvm_unreachable("Unhandled match category" ); |
214 | }(); |
215 | if (!ReplacementFunctionName) |
216 | return; |
217 | |
218 | diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead" ) |
219 | << FuncDecl << getRationaleFor(FunctionName) |
220 | << ReplacementFunctionName.value() << DeclRef->getSourceRange(); |
221 | } |
222 | |
223 | void UnsafeFunctionsCheck::registerPPCallbacks( |
224 | const SourceManager &SM, Preprocessor *PP, |
225 | Preprocessor * /*ModuleExpanderPP*/) { |
226 | this->PP = PP; |
227 | } |
228 | |
229 | void UnsafeFunctionsCheck::onEndOfTranslationUnit() { |
230 | this->PP = nullptr; |
231 | IsAnnexKAvailable.reset(); |
232 | } |
233 | |
234 | } // namespace clang::tidy::bugprone |
235 | |