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(S: "strlen" , Value: "strnlen_s" ) |
42 | .Case(S: "wcslen" , Value: "wcsnlen_s" ) |
43 | .Default(Value: (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(S0: "asctime" , S1: "asctime_r" , Value: "asctime_s" ) |
53 | .Case(S: "gets" , Value: "gets_s" ) |
54 | .Default(Value: {}); |
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(S0: "asctime" , S1: "asctime_r" , Value: "strftime" ) |
63 | .Case(S: "gets" , Value: "fgets" ) |
64 | .Case(S: "rewind" , Value: "fseek" ) |
65 | .Case(S: "setbuf" , Value: "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(S: "bcopy" , Value: "memcpy_s" ) |
74 | .Case(S: "bzero" , Value: "memset_s" ) |
75 | .Default(Value: {}); |
76 | |
77 | if (!AnnexKReplacementFunction.empty()) |
78 | return AnnexKReplacementFunction; |
79 | } |
80 | |
81 | return StringSwitch<StringRef>(FunctionName) |
82 | .Case(S: "bcmp" , Value: "memcmp" ) |
83 | .Case(S: "bcopy" , Value: "memcpy" ) |
84 | .Case(S: "bzero" , Value: "memset" ) |
85 | .Case(S: "getpw" , Value: "getpwuid" ) |
86 | .Case(S: "vfork" , Value: "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(S0: "asctime" , S1: "asctime_r" , S2: "ctime" , |
94 | Value: "is not bounds-checking and non-reentrant" ) |
95 | .Cases(S0: "bcmp" , S1: "bcopy" , S2: "bzero" , Value: "is deprecated" ) |
96 | .Cases(S0: "fopen" , S1: "freopen" , Value: "has no exclusive access to the opened file" ) |
97 | .Case(S: "gets" , Value: "is insecure, was deprecated and removed in C11 and C++14" ) |
98 | .Case(S: "getpw" , Value: "is dangerous as it may overflow the provided buffer" ) |
99 | .Cases(S0: "rewind" , S1: "setbuf" , Value: "has no error detection" ) |
100 | .Case(S: "vfork" , Value: "is insecure as it can lead to denial of service " |
101 | "situations in the parent process" ) |
102 | .Default(Value: "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(Id: "__STDC_LIB_EXT1__" ) || |
121 | !PP->isMacroDefined(Id: "__STDC_WANT_LIB_EXT1__" )) |
122 | return (CacheVar = false).value(); |
123 | |
124 | const auto *MI = |
125 | PP->getMacroInfo(II: PP->getIdentifierInfo(Name: "__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(n: Functions.size()); |
143 | |
144 | for (StringRef Function : Functions) { |
145 | if (Function.empty()) |
146 | continue; |
147 | |
148 | const auto [Name, Rest] = Function.split(Separator: ','); |
149 | const auto [Replacement, Reason] = Rest.split(Separator: ','); |
150 | |
151 | if (Name.trim().empty()) { |
152 | Context->configurationDiag(Message: "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 | x: {.Name: Name.trim().str(), |
160 | .Pattern: matchers::MatchesAnyListedNameMatcher::NameMatcher(Name.trim()), |
161 | .Replacement: Replacement.trim().str(), .Reason: 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(n: Functions.size()); |
171 | |
172 | for (const auto &Entry : Functions) { |
173 | if (Entry.Reason.empty()) |
174 | Result.push_back(x: Entry.Name + "," + Entry.Replacement); |
175 | else |
176 | Result.push_back(x: Entry.Name + "," + Entry.Replacement + "," + |
177 | Entry.Reason); |
178 | } |
179 | |
180 | return llvm::join(R&: Result, Separator: ";" ); |
181 | } |
182 | |
183 | UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name, |
184 | ClangTidyContext *Context) |
185 | : ClangTidyCheck(Name, Context), |
186 | CustomFunctions(parseCheckedFunctions( |
187 | Option: Options.get(LocalName: OptionNameCustomFunctions, Default: "" ), Context)), |
188 | ReportDefaultFunctions( |
189 | Options.get(LocalName: OptionNameReportDefaultFunctions, Default: true)), |
190 | ReportMoreUnsafeFunctions( |
191 | Options.get(LocalName: OptionNameReportMoreUnsafeFunctions, Default: true)) {} |
192 | |
193 | void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
194 | Options.store(Options&: Opts, LocalName: OptionNameCustomFunctions, |
195 | Value: serializeCheckedFunctions(Functions: CustomFunctions)); |
196 | Options.store(Options&: Opts, LocalName: OptionNameReportDefaultFunctions, Value: ReportDefaultFunctions); |
197 | Options.store(Options&: Opts, LocalName: OptionNameReportMoreUnsafeFunctions, |
198 | Value: 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 | NodeMatch: declRefExpr(to(InnerMatcher: functionDecl(FunctionNamesWithAnnexKReplacementMatcher) |
220 | .bind(ID: FunctionNamesWithAnnexKReplacementId))) |
221 | .bind(ID: DeclRefId), |
222 | Action: 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 | NodeMatch: declRefExpr( |
230 | to(InnerMatcher: functionDecl(FunctionNamesMatcher).bind(ID: FunctionNamesId))) |
231 | .bind(ID: DeclRefId), |
232 | Action: 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 | NodeMatch: declRefExpr(to(InnerMatcher: functionDecl(AdditionalFunctionNamesMatcher) |
240 | .bind(ID: AdditionalFunctionNamesId))) |
241 | .bind(ID: DeclRefId), |
242 | Action: this); |
243 | } |
244 | } |
245 | |
246 | if (!CustomFunctions.empty()) { |
247 | std::vector<llvm::StringRef> FunctionNames; |
248 | FunctionNames.reserve(n: CustomFunctions.size()); |
249 | |
250 | for (const auto &Entry : CustomFunctions) |
251 | FunctionNames.push_back(x: Entry.Name); |
252 | |
253 | auto CustomFunctionsMatcher = matchers::matchesAnyListedName(NameList: FunctionNames); |
254 | |
255 | Finder->addMatcher(NodeMatch: declRefExpr(to(InnerMatcher: functionDecl(CustomFunctionsMatcher) |
256 | .bind(ID: CustomFunctionNamesId))) |
257 | .bind(ID: DeclRefId), |
258 | Action: this); |
259 | // C++ member calls do not contain a DeclRefExpr to the function decl. |
260 | // Instead, they contain a MemberExpr that refers to the decl. |
261 | Finder->addMatcher(NodeMatch: memberExpr(member(InnerMatcher: functionDecl(CustomFunctionsMatcher) |
262 | .bind(ID: CustomFunctionNamesId))) |
263 | .bind(ID: DeclRefId), |
264 | Action: this); |
265 | } |
266 | } |
267 | |
268 | void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) { |
269 | const Expr *SourceExpr; |
270 | const FunctionDecl *FuncDecl; |
271 | |
272 | if (const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(ID: DeclRefId)) { |
273 | SourceExpr = DeclRef; |
274 | FuncDecl = cast<FunctionDecl>(Val: DeclRef->getDecl()); |
275 | } else if (const auto *Member = |
276 | Result.Nodes.getNodeAs<MemberExpr>(ID: DeclRefId)) { |
277 | SourceExpr = Member; |
278 | FuncDecl = cast<FunctionDecl>(Val: Member->getMemberDecl()); |
279 | } else { |
280 | llvm_unreachable("No valid matched node in check()" ); |
281 | return; |
282 | } |
283 | |
284 | assert(SourceExpr && FuncDecl && "No valid matched node in check()" ); |
285 | |
286 | // Only one of these are matched at a time. |
287 | const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>( |
288 | ID: FunctionNamesWithAnnexKReplacementId); |
289 | const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(ID: FunctionNamesId); |
290 | const auto *Additional = |
291 | Result.Nodes.getNodeAs<FunctionDecl>(ID: AdditionalFunctionNamesId); |
292 | const auto *Custom = |
293 | Result.Nodes.getNodeAs<FunctionDecl>(ID: CustomFunctionNamesId); |
294 | assert((AnnexK || Normal || Additional || Custom) && |
295 | "No valid match category." ); |
296 | |
297 | bool AnnexKIsAvailable = |
298 | isAnnexKAvailable(CacheVar&: IsAnnexKAvailable, PP, LO: getLangOpts()); |
299 | StringRef FunctionName = FuncDecl->getName(); |
300 | |
301 | if (Custom) { |
302 | for (const auto &Entry : CustomFunctions) { |
303 | if (Entry.Pattern.match(*FuncDecl)) { |
304 | StringRef Reason = |
305 | Entry.Reason.empty() ? "is marked as unsafe" : Entry.Reason.c_str(); |
306 | |
307 | if (Entry.Replacement.empty()) { |
308 | diag(Loc: SourceExpr->getExprLoc(), |
309 | Description: "function %0 %1; it should not be used" ) |
310 | << FuncDecl << Reason << Entry.Replacement |
311 | << SourceExpr->getSourceRange(); |
312 | } else { |
313 | diag(Loc: SourceExpr->getExprLoc(), |
314 | Description: "function %0 %1; '%2' should be used instead" ) |
315 | << FuncDecl << Reason << Entry.Replacement |
316 | << SourceExpr->getSourceRange(); |
317 | } |
318 | |
319 | return; |
320 | } |
321 | } |
322 | |
323 | llvm_unreachable("No custom function was matched." ); |
324 | return; |
325 | } |
326 | |
327 | const std::optional<std::string> ReplacementFunctionName = |
328 | [&]() -> std::optional<std::string> { |
329 | if (AnnexK) { |
330 | if (AnnexKIsAvailable) |
331 | return getAnnexKReplacementFor(FunctionName); |
332 | return std::nullopt; |
333 | } |
334 | |
335 | if (Normal) |
336 | return getReplacementFor(FunctionName, IsAnnexKAvailable: AnnexKIsAvailable).str(); |
337 | |
338 | if (Additional) |
339 | return getReplacementForAdditional(FunctionName, IsAnnexKAvailable: AnnexKIsAvailable).str(); |
340 | |
341 | llvm_unreachable("Unhandled match category" ); |
342 | }(); |
343 | if (!ReplacementFunctionName) |
344 | return; |
345 | |
346 | diag(Loc: SourceExpr->getExprLoc(), Description: "function %0 %1; '%2' should be used instead" ) |
347 | << FuncDecl << getRationaleFor(FunctionName) |
348 | << ReplacementFunctionName.value() << SourceExpr->getSourceRange(); |
349 | } |
350 | |
351 | void UnsafeFunctionsCheck::registerPPCallbacks( |
352 | const SourceManager &SM, Preprocessor *PP, |
353 | Preprocessor * /*ModuleExpanderPP*/) { |
354 | this->PP = PP; |
355 | } |
356 | |
357 | void UnsafeFunctionsCheck::onEndOfTranslationUnit() { |
358 | this->PP = nullptr; |
359 | IsAnnexKAvailable.reset(); |
360 | } |
361 | |
362 | } // namespace clang::tidy::bugprone |
363 | |