1 | //===--- NotNullTerminatedResultCheck.cpp - clang-tidy ----------*- C++ -*-===// |
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 "NotNullTerminatedResultCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/Frontend/CompilerInstance.h" |
13 | #include "clang/Lex/Lexer.h" |
14 | #include "clang/Lex/PPCallbacks.h" |
15 | #include "clang/Lex/Preprocessor.h" |
16 | #include <optional> |
17 | |
18 | using namespace clang::ast_matchers; |
19 | |
20 | namespace clang::tidy::bugprone { |
21 | |
22 | constexpr llvm::StringLiteral FunctionExprName = "FunctionExpr" ; |
23 | constexpr llvm::StringLiteral CastExprName = "CastExpr" ; |
24 | constexpr llvm::StringLiteral UnknownDestName = "UnknownDest" ; |
25 | constexpr llvm::StringLiteral DestArrayTyName = "DestArrayTy" ; |
26 | constexpr llvm::StringLiteral DestVarDeclName = "DestVarDecl" ; |
27 | constexpr llvm::StringLiteral DestMallocExprName = "DestMalloc" ; |
28 | constexpr llvm::StringLiteral DestExprName = "DestExpr" ; |
29 | constexpr llvm::StringLiteral SrcVarDeclName = "SrcVarDecl" ; |
30 | constexpr llvm::StringLiteral SrcExprName = "SrcExpr" ; |
31 | constexpr llvm::StringLiteral LengthExprName = "LengthExpr" ; |
32 | constexpr llvm::StringLiteral WrongLengthExprName = "WrongLength" ; |
33 | constexpr llvm::StringLiteral UnknownLengthName = "UnknownLength" ; |
34 | |
35 | enum class LengthHandleKind { Increase, Decrease }; |
36 | |
37 | namespace { |
38 | static Preprocessor *PP; |
39 | } // namespace |
40 | |
41 | // Returns the expression of destination's capacity which is part of a |
42 | // 'VariableArrayType', 'ConstantArrayTypeLoc' or an argument of a 'malloc()' |
43 | // family function call. |
44 | static const Expr *getDestCapacityExpr(const MatchFinder::MatchResult &Result) { |
45 | if (const auto *DestMalloc = Result.Nodes.getNodeAs<Expr>(ID: DestMallocExprName)) |
46 | return DestMalloc; |
47 | |
48 | if (const auto *DestVAT = |
49 | Result.Nodes.getNodeAs<VariableArrayType>(ID: DestArrayTyName)) |
50 | return DestVAT->getSizeExpr(); |
51 | |
52 | if (const auto *DestVD = Result.Nodes.getNodeAs<VarDecl>(ID: DestVarDeclName)) |
53 | if (const TypeLoc DestTL = DestVD->getTypeSourceInfo()->getTypeLoc()) |
54 | if (const auto DestCTL = DestTL.getAs<ConstantArrayTypeLoc>()) |
55 | return DestCTL.getSizeExpr(); |
56 | |
57 | return nullptr; |
58 | } |
59 | |
60 | // Returns the length of \p E as an 'IntegerLiteral' or a 'StringLiteral' |
61 | // without the null-terminator. |
62 | static unsigned getLength(const Expr *E, |
63 | const MatchFinder::MatchResult &Result) { |
64 | if (!E) |
65 | return 0; |
66 | |
67 | Expr::EvalResult Length; |
68 | E = E->IgnoreImpCasts(); |
69 | |
70 | if (const auto *LengthDRE = dyn_cast<DeclRefExpr>(Val: E)) |
71 | if (const auto *LengthVD = dyn_cast<VarDecl>(Val: LengthDRE->getDecl())) |
72 | if (!isa<ParmVarDecl>(Val: LengthVD)) |
73 | if (const Expr *LengthInit = LengthVD->getInit()) |
74 | if (LengthInit->EvaluateAsInt(Result&: Length, Ctx: *Result.Context)) |
75 | return Length.Val.getInt().getZExtValue(); |
76 | |
77 | if (const auto *LengthIL = dyn_cast<IntegerLiteral>(Val: E)) |
78 | return LengthIL->getValue().getZExtValue(); |
79 | |
80 | if (const auto *StrDRE = dyn_cast<DeclRefExpr>(Val: E)) |
81 | if (const auto *StrVD = dyn_cast<VarDecl>(Val: StrDRE->getDecl())) |
82 | if (const Expr *StrInit = StrVD->getInit()) |
83 | if (const auto *StrSL = |
84 | dyn_cast<StringLiteral>(Val: StrInit->IgnoreImpCasts())) |
85 | return StrSL->getLength(); |
86 | |
87 | if (const auto *SrcSL = dyn_cast<StringLiteral>(Val: E)) |
88 | return SrcSL->getLength(); |
89 | |
90 | return 0; |
91 | } |
92 | |
93 | // Returns the capacity of the destination array. |
94 | // For example in 'char dest[13]; memcpy(dest, ...)' it returns 13. |
95 | static int getDestCapacity(const MatchFinder::MatchResult &Result) { |
96 | if (const auto *DestCapacityExpr = getDestCapacityExpr(Result)) |
97 | return getLength(E: DestCapacityExpr, Result); |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | // Returns the 'strlen()' if it is the given length. |
103 | static const CallExpr *getStrlenExpr(const MatchFinder::MatchResult &Result) { |
104 | if (const auto *StrlenExpr = |
105 | Result.Nodes.getNodeAs<CallExpr>(ID: WrongLengthExprName)) |
106 | if (const Decl *D = StrlenExpr->getCalleeDecl()) |
107 | if (const FunctionDecl *FD = D->getAsFunction()) |
108 | if (const IdentifierInfo *II = FD->getIdentifier()) |
109 | if (II->isStr(Str: "strlen" ) || II->isStr(Str: "wcslen" )) |
110 | return StrlenExpr; |
111 | |
112 | return nullptr; |
113 | } |
114 | |
115 | // Returns the length which is given in the memory/string handler function. |
116 | // For example in 'memcpy(dest, "foobar", 3)' it returns 3. |
117 | static int getGivenLength(const MatchFinder::MatchResult &Result) { |
118 | if (Result.Nodes.getNodeAs<Expr>(ID: UnknownLengthName)) |
119 | return 0; |
120 | |
121 | if (int Length = |
122 | getLength(E: Result.Nodes.getNodeAs<Expr>(ID: WrongLengthExprName), Result)) |
123 | return Length; |
124 | |
125 | if (int Length = |
126 | getLength(E: Result.Nodes.getNodeAs<Expr>(ID: LengthExprName), Result)) |
127 | return Length; |
128 | |
129 | // Special case, for example 'strlen("foo")'. |
130 | if (const CallExpr *StrlenCE = getStrlenExpr(Result)) |
131 | if (const Expr *Arg = StrlenCE->getArg(Arg: 0)->IgnoreImpCasts()) |
132 | if (int ArgLength = getLength(E: Arg, Result)) |
133 | return ArgLength; |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | // Returns a string representation of \p E. |
139 | static StringRef exprToStr(const Expr *E, |
140 | const MatchFinder::MatchResult &Result) { |
141 | if (!E) |
142 | return "" ; |
143 | |
144 | return Lexer::getSourceText( |
145 | Range: CharSourceRange::getTokenRange(E->getSourceRange()), |
146 | SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts(), Invalid: nullptr); |
147 | } |
148 | |
149 | // Returns the proper token based end location of \p E. |
150 | static SourceLocation exprLocEnd(const Expr *E, |
151 | const MatchFinder::MatchResult &Result) { |
152 | return Lexer::getLocForEndOfToken(Loc: E->getEndLoc(), Offset: 0, SM: *Result.SourceManager, |
153 | LangOpts: Result.Context->getLangOpts()); |
154 | } |
155 | |
156 | //===----------------------------------------------------------------------===// |
157 | // Rewrite decision helper functions. |
158 | //===----------------------------------------------------------------------===// |
159 | |
160 | // Increment by integer '1' can result in overflow if it is the maximal value. |
161 | // After that it would be extended to 'size_t' and its value would be wrong, |
162 | // therefore we have to inject '+ 1UL' instead. |
163 | static bool isInjectUL(const MatchFinder::MatchResult &Result) { |
164 | return getGivenLength(Result) == std::numeric_limits<int>::max(); |
165 | } |
166 | |
167 | // If the capacity of the destination array is unknown it is denoted as unknown. |
168 | static bool isKnownDest(const MatchFinder::MatchResult &Result) { |
169 | return !Result.Nodes.getNodeAs<Expr>(ID: UnknownDestName); |
170 | } |
171 | |
172 | // True if the capacity of the destination array is based on the given length, |
173 | // therefore we assume that it cannot overflow (e.g. 'malloc(given_length + 1)' |
174 | static bool isDestBasedOnGivenLength(const MatchFinder::MatchResult &Result) { |
175 | StringRef DestCapacityExprStr = |
176 | exprToStr(E: getDestCapacityExpr(Result), Result).trim(); |
177 | StringRef LengthExprStr = |
178 | exprToStr(E: Result.Nodes.getNodeAs<Expr>(ID: LengthExprName), Result).trim(); |
179 | |
180 | return !DestCapacityExprStr.empty() && !LengthExprStr.empty() && |
181 | DestCapacityExprStr.contains(Other: LengthExprStr); |
182 | } |
183 | |
184 | // Writing and reading from the same memory cannot remove the null-terminator. |
185 | static bool isDestAndSrcEquals(const MatchFinder::MatchResult &Result) { |
186 | if (const auto *DestDRE = Result.Nodes.getNodeAs<DeclRefExpr>(ID: DestExprName)) |
187 | if (const auto *SrcDRE = Result.Nodes.getNodeAs<DeclRefExpr>(ID: SrcExprName)) |
188 | return DestDRE->getDecl()->getCanonicalDecl() == |
189 | SrcDRE->getDecl()->getCanonicalDecl(); |
190 | |
191 | return false; |
192 | } |
193 | |
194 | // For example 'std::string str = "foo"; memcpy(dst, str.data(), str.length())'. |
195 | static bool isStringDataAndLength(const MatchFinder::MatchResult &Result) { |
196 | const auto *DestExpr = |
197 | Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: DestExprName); |
198 | const auto *SrcExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: SrcExprName); |
199 | const auto *LengthExpr = |
200 | Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: WrongLengthExprName); |
201 | |
202 | StringRef DestStr = "" , SrcStr = "" , LengthStr = "" ; |
203 | if (DestExpr) |
204 | if (const CXXMethodDecl *DestMD = DestExpr->getMethodDecl()) |
205 | DestStr = DestMD->getName(); |
206 | |
207 | if (SrcExpr) |
208 | if (const CXXMethodDecl *SrcMD = SrcExpr->getMethodDecl()) |
209 | SrcStr = SrcMD->getName(); |
210 | |
211 | if (LengthExpr) |
212 | if (const CXXMethodDecl *LengthMD = LengthExpr->getMethodDecl()) |
213 | LengthStr = LengthMD->getName(); |
214 | |
215 | return (LengthStr == "length" || LengthStr == "size" ) && |
216 | (SrcStr == "data" || DestStr == "data" ); |
217 | } |
218 | |
219 | static bool |
220 | isGivenLengthEqualToSrcLength(const MatchFinder::MatchResult &Result) { |
221 | if (Result.Nodes.getNodeAs<Expr>(ID: UnknownLengthName)) |
222 | return false; |
223 | |
224 | if (isStringDataAndLength(Result)) |
225 | return true; |
226 | |
227 | int GivenLength = getGivenLength(Result); |
228 | int SrcLength = getLength(E: Result.Nodes.getNodeAs<Expr>(ID: SrcExprName), Result); |
229 | |
230 | if (GivenLength != 0 && SrcLength != 0 && GivenLength == SrcLength) |
231 | return true; |
232 | |
233 | if (const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(ID: LengthExprName)) |
234 | if (isa<BinaryOperator>(Val: LengthExpr->IgnoreParenImpCasts())) |
235 | return false; |
236 | |
237 | // Check the strlen()'s argument's 'VarDecl' is equal to the source 'VarDecl'. |
238 | if (const CallExpr *StrlenCE = getStrlenExpr(Result)) |
239 | if (const auto *ArgDRE = |
240 | dyn_cast<DeclRefExpr>(Val: StrlenCE->getArg(Arg: 0)->IgnoreImpCasts())) |
241 | if (const auto *SrcVD = Result.Nodes.getNodeAs<VarDecl>(ID: SrcVarDeclName)) |
242 | return dyn_cast<VarDecl>(Val: ArgDRE->getDecl()) == SrcVD; |
243 | |
244 | return false; |
245 | } |
246 | |
247 | static bool isCorrectGivenLength(const MatchFinder::MatchResult &Result) { |
248 | if (Result.Nodes.getNodeAs<Expr>(ID: UnknownLengthName)) |
249 | return false; |
250 | |
251 | return !isGivenLengthEqualToSrcLength(Result); |
252 | } |
253 | |
254 | // If we rewrite the function call we need to create extra space to hold the |
255 | // null terminator. The new necessary capacity overflows without that '+ 1' |
256 | // size and we need to correct the given capacity. |
257 | static bool isDestCapacityOverflows(const MatchFinder::MatchResult &Result) { |
258 | if (!isKnownDest(Result)) |
259 | return true; |
260 | |
261 | const Expr *DestCapacityExpr = getDestCapacityExpr(Result); |
262 | int DestCapacity = getLength(E: DestCapacityExpr, Result); |
263 | int GivenLength = getGivenLength(Result); |
264 | |
265 | if (GivenLength != 0 && DestCapacity != 0) |
266 | return isGivenLengthEqualToSrcLength(Result) && DestCapacity == GivenLength; |
267 | |
268 | // Assume that the destination array's capacity cannot overflow if the |
269 | // expression of the memory allocation contains '+ 1'. |
270 | StringRef DestCapacityExprStr = exprToStr(E: DestCapacityExpr, Result); |
271 | if (DestCapacityExprStr.contains(Other: "+1" ) || DestCapacityExprStr.contains(Other: "+ 1" )) |
272 | return false; |
273 | |
274 | return true; |
275 | } |
276 | |
277 | static bool |
278 | isFixedGivenLengthAndUnknownSrc(const MatchFinder::MatchResult &Result) { |
279 | if (Result.Nodes.getNodeAs<IntegerLiteral>(ID: WrongLengthExprName)) |
280 | return !getLength(E: Result.Nodes.getNodeAs<Expr>(ID: SrcExprName), Result); |
281 | |
282 | return false; |
283 | } |
284 | |
285 | //===----------------------------------------------------------------------===// |
286 | // Code injection functions. |
287 | //===----------------------------------------------------------------------===// |
288 | |
289 | // Increase or decrease \p LengthExpr by one. |
290 | static void lengthExprHandle(const Expr *LengthExpr, |
291 | LengthHandleKind LengthHandle, |
292 | const MatchFinder::MatchResult &Result, |
293 | DiagnosticBuilder &Diag) { |
294 | LengthExpr = LengthExpr->IgnoreParenImpCasts(); |
295 | |
296 | // See whether we work with a macro. |
297 | bool IsMacroDefinition = false; |
298 | StringRef LengthExprStr = exprToStr(E: LengthExpr, Result); |
299 | Preprocessor::macro_iterator It = PP->macro_begin(); |
300 | while (It != PP->macro_end() && !IsMacroDefinition) { |
301 | if (It->first->getName() == LengthExprStr) |
302 | IsMacroDefinition = true; |
303 | |
304 | ++It; |
305 | } |
306 | |
307 | // Try to obtain an 'IntegerLiteral' and adjust it. |
308 | if (!IsMacroDefinition) { |
309 | if (const auto *LengthIL = dyn_cast<IntegerLiteral>(Val: LengthExpr)) { |
310 | size_t NewLength = LengthIL->getValue().getZExtValue() + |
311 | (LengthHandle == LengthHandleKind::Increase |
312 | ? (isInjectUL(Result) ? 1UL : 1) |
313 | : -1); |
314 | |
315 | const auto NewLengthFix = FixItHint::CreateReplacement( |
316 | LengthIL->getSourceRange(), |
317 | (Twine(NewLength) + (isInjectUL(Result) ? "UL" : "" )).str()); |
318 | Diag << NewLengthFix; |
319 | return; |
320 | } |
321 | } |
322 | |
323 | // Try to obtain and remove the '+ 1' string as a decrement fix. |
324 | const auto *BO = dyn_cast<BinaryOperator>(Val: LengthExpr); |
325 | if (BO && BO->getOpcode() == BO_Add && |
326 | LengthHandle == LengthHandleKind::Decrease) { |
327 | const Expr *LhsExpr = BO->getLHS()->IgnoreImpCasts(); |
328 | const Expr *RhsExpr = BO->getRHS()->IgnoreImpCasts(); |
329 | |
330 | if (const auto *LhsIL = dyn_cast<IntegerLiteral>(Val: LhsExpr)) { |
331 | if (LhsIL->getValue().getZExtValue() == 1) { |
332 | Diag << FixItHint::CreateRemoval( |
333 | {LhsIL->getBeginLoc(), |
334 | RhsExpr->getBeginLoc().getLocWithOffset(-1)}); |
335 | return; |
336 | } |
337 | } |
338 | |
339 | if (const auto *RhsIL = dyn_cast<IntegerLiteral>(Val: RhsExpr)) { |
340 | if (RhsIL->getValue().getZExtValue() == 1) { |
341 | Diag << FixItHint::CreateRemoval( |
342 | {LhsExpr->getEndLoc().getLocWithOffset(1), RhsIL->getEndLoc()}); |
343 | return; |
344 | } |
345 | } |
346 | } |
347 | |
348 | // Try to inject the '+ 1'/'- 1' string. |
349 | bool NeedInnerParen = BO && BO->getOpcode() != BO_Add; |
350 | |
351 | if (NeedInnerParen) |
352 | Diag << FixItHint::CreateInsertion(InsertionLoc: LengthExpr->getBeginLoc(), Code: "(" ); |
353 | |
354 | SmallString<8> Injection; |
355 | if (NeedInnerParen) |
356 | Injection += ')'; |
357 | Injection += LengthHandle == LengthHandleKind::Increase ? " + 1" : " - 1" ; |
358 | if (isInjectUL(Result)) |
359 | Injection += "UL" ; |
360 | |
361 | Diag << FixItHint::CreateInsertion(InsertionLoc: exprLocEnd(E: LengthExpr, Result), Code: Injection); |
362 | } |
363 | |
364 | static void lengthArgHandle(LengthHandleKind LengthHandle, |
365 | const MatchFinder::MatchResult &Result, |
366 | DiagnosticBuilder &Diag) { |
367 | const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(ID: LengthExprName); |
368 | lengthExprHandle(LengthExpr, LengthHandle, Result, Diag); |
369 | } |
370 | |
371 | static void lengthArgPosHandle(unsigned ArgPos, LengthHandleKind LengthHandle, |
372 | const MatchFinder::MatchResult &Result, |
373 | DiagnosticBuilder &Diag) { |
374 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
375 | lengthExprHandle(LengthExpr: FunctionExpr->getArg(Arg: ArgPos), LengthHandle, Result, Diag); |
376 | } |
377 | |
378 | // The string handler functions are only operates with plain 'char'/'wchar_t' |
379 | // without 'unsigned/signed', therefore we need to cast it. |
380 | static bool isDestExprFix(const MatchFinder::MatchResult &Result, |
381 | DiagnosticBuilder &Diag) { |
382 | const auto *Dest = Result.Nodes.getNodeAs<Expr>(ID: DestExprName); |
383 | if (!Dest) |
384 | return false; |
385 | |
386 | std::string TempTyStr = Dest->getType().getAsString(); |
387 | StringRef TyStr = TempTyStr; |
388 | if (TyStr.starts_with(Prefix: "char" ) || TyStr.starts_with(Prefix: "wchar_t" )) |
389 | return false; |
390 | |
391 | Diag << FixItHint::CreateInsertion(InsertionLoc: Dest->getBeginLoc(), Code: "(char *)" ); |
392 | return true; |
393 | } |
394 | |
395 | // If the destination array is the same length as the given length we have to |
396 | // increase the capacity by one to create space for the null terminator. |
397 | static bool isDestCapacityFix(const MatchFinder::MatchResult &Result, |
398 | DiagnosticBuilder &Diag) { |
399 | bool IsOverflows = isDestCapacityOverflows(Result); |
400 | if (IsOverflows) |
401 | if (const Expr *CapacityExpr = getDestCapacityExpr(Result)) |
402 | lengthExprHandle(LengthExpr: CapacityExpr, LengthHandle: LengthHandleKind::Increase, Result, Diag); |
403 | |
404 | return IsOverflows; |
405 | } |
406 | |
407 | static void removeArg(int ArgPos, const MatchFinder::MatchResult &Result, |
408 | DiagnosticBuilder &Diag) { |
409 | // This is the following structure: (src, '\0', strlen(src)) |
410 | // ArgToRemove: ~~~~~~~~~~~ |
411 | // LHSArg: ~~~~ |
412 | // RemoveArgFix: ~~~~~~~~~~~~~ |
413 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
414 | const Expr *ArgToRemove = FunctionExpr->getArg(Arg: ArgPos); |
415 | const Expr *LHSArg = FunctionExpr->getArg(Arg: ArgPos - 1); |
416 | const auto RemoveArgFix = FixItHint::CreateRemoval( |
417 | RemoveRange: SourceRange(exprLocEnd(E: LHSArg, Result), |
418 | exprLocEnd(E: ArgToRemove, Result).getLocWithOffset(Offset: -1))); |
419 | Diag << RemoveArgFix; |
420 | } |
421 | |
422 | static void renameFunc(StringRef NewFuncName, |
423 | const MatchFinder::MatchResult &Result, |
424 | DiagnosticBuilder &Diag) { |
425 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
426 | int FuncNameLength = |
427 | FunctionExpr->getDirectCallee()->getIdentifier()->getLength(); |
428 | SourceRange FuncNameRange( |
429 | FunctionExpr->getBeginLoc(), |
430 | FunctionExpr->getBeginLoc().getLocWithOffset(Offset: FuncNameLength - 1)); |
431 | |
432 | const auto FuncNameFix = |
433 | FixItHint::CreateReplacement(RemoveRange: FuncNameRange, Code: NewFuncName); |
434 | Diag << FuncNameFix; |
435 | } |
436 | |
437 | static void renameMemcpy(StringRef Name, bool IsCopy, bool IsSafe, |
438 | const MatchFinder::MatchResult &Result, |
439 | DiagnosticBuilder &Diag) { |
440 | SmallString<10> NewFuncName; |
441 | NewFuncName = (Name[0] != 'w') ? "str" : "wcs" ; |
442 | NewFuncName += IsCopy ? "cpy" : "ncpy" ; |
443 | NewFuncName += IsSafe ? "_s" : "" ; |
444 | renameFunc(NewFuncName, Result, Diag); |
445 | } |
446 | |
447 | static void insertDestCapacityArg(bool IsOverflows, StringRef Name, |
448 | const MatchFinder::MatchResult &Result, |
449 | DiagnosticBuilder &Diag) { |
450 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
451 | SmallString<64> NewSecondArg; |
452 | |
453 | if (int DestLength = getDestCapacity(Result)) { |
454 | NewSecondArg = Twine(IsOverflows ? DestLength + 1 : DestLength).str(); |
455 | } else { |
456 | NewSecondArg = |
457 | (Twine(exprToStr(E: getDestCapacityExpr(Result), Result)) + |
458 | (IsOverflows ? (!isInjectUL(Result) ? " + 1" : " + 1UL" ) : "" )) |
459 | .str(); |
460 | } |
461 | |
462 | NewSecondArg += ", " ; |
463 | const auto InsertNewArgFix = FixItHint::CreateInsertion( |
464 | InsertionLoc: FunctionExpr->getArg(Arg: 1)->getBeginLoc(), Code: NewSecondArg); |
465 | Diag << InsertNewArgFix; |
466 | } |
467 | |
468 | static void insertNullTerminatorExpr(StringRef Name, |
469 | const MatchFinder::MatchResult &Result, |
470 | DiagnosticBuilder &Diag) { |
471 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
472 | int FuncLocStartColumn = Result.SourceManager->getPresumedColumnNumber( |
473 | Loc: FunctionExpr->getBeginLoc()); |
474 | SourceRange SpaceRange( |
475 | FunctionExpr->getBeginLoc().getLocWithOffset(Offset: -FuncLocStartColumn + 1), |
476 | FunctionExpr->getBeginLoc()); |
477 | StringRef SpaceBeforeStmtStr = Lexer::getSourceText( |
478 | Range: CharSourceRange::getCharRange(R: SpaceRange), SM: *Result.SourceManager, |
479 | LangOpts: Result.Context->getLangOpts(), Invalid: nullptr); |
480 | |
481 | SmallString<128> NewAddNullTermExprStr; |
482 | NewAddNullTermExprStr = |
483 | (Twine('\n') + SpaceBeforeStmtStr + |
484 | exprToStr(E: Result.Nodes.getNodeAs<Expr>(ID: DestExprName), Result) + "[" + |
485 | exprToStr(E: Result.Nodes.getNodeAs<Expr>(ID: LengthExprName), Result) + |
486 | "] = " + ((Name[0] != 'w') ? R"('\0';)" : R"(L'\0';)" )) |
487 | .str(); |
488 | |
489 | const auto AddNullTerminatorExprFix = FixItHint::CreateInsertion( |
490 | InsertionLoc: exprLocEnd(FunctionExpr, Result).getLocWithOffset(Offset: 1), |
491 | Code: NewAddNullTermExprStr); |
492 | Diag << AddNullTerminatorExprFix; |
493 | } |
494 | |
495 | //===----------------------------------------------------------------------===// |
496 | // Checker logic with the matchers. |
497 | //===----------------------------------------------------------------------===// |
498 | |
499 | NotNullTerminatedResultCheck::NotNullTerminatedResultCheck( |
500 | StringRef Name, ClangTidyContext *Context) |
501 | : ClangTidyCheck(Name, Context), |
502 | WantToUseSafeFunctions(Options.get(LocalName: "WantToUseSafeFunctions" , Default: true)) {} |
503 | |
504 | void NotNullTerminatedResultCheck::storeOptions( |
505 | ClangTidyOptions::OptionMap &Opts) { |
506 | Options.store(Options&: Opts, LocalName: "WantToUseSafeFunctions" , Value: WantToUseSafeFunctions); |
507 | } |
508 | |
509 | void NotNullTerminatedResultCheck::registerPPCallbacks( |
510 | const SourceManager &SM, Preprocessor *Pp, Preprocessor *ModuleExpanderPP) { |
511 | PP = Pp; |
512 | } |
513 | |
514 | namespace { |
515 | AST_MATCHER_P(Expr, hasDefinition, ast_matchers::internal::Matcher<Expr>, |
516 | InnerMatcher) { |
517 | const Expr *SimpleNode = &Node; |
518 | SimpleNode = SimpleNode->IgnoreParenImpCasts(); |
519 | |
520 | if (InnerMatcher.matches(Node: *SimpleNode, Finder, Builder)) |
521 | return true; |
522 | |
523 | auto DREHasInit = ignoringImpCasts( |
524 | InnerMatcher: declRefExpr(to(InnerMatcher: varDecl(hasInitializer(InnerMatcher: ignoringImpCasts(InnerMatcher)))))); |
525 | |
526 | if (DREHasInit.matches(Node: *SimpleNode, Finder, Builder)) |
527 | return true; |
528 | |
529 | const char *const VarDeclName = "variable-declaration" ; |
530 | auto DREHasDefinition = ignoringImpCasts(InnerMatcher: declRefExpr( |
531 | to(InnerMatcher: varDecl().bind(ID: VarDeclName)), |
532 | hasAncestor(compoundStmt(hasDescendant(binaryOperator( |
533 | hasLHS(InnerMatcher: declRefExpr(to(InnerMatcher: varDecl(equalsBoundNode(ID: VarDeclName))))), |
534 | hasRHS(InnerMatcher: ignoringImpCasts(InnerMatcher)))))))); |
535 | |
536 | if (DREHasDefinition.matches(Node: *SimpleNode, Finder, Builder)) |
537 | return true; |
538 | |
539 | return false; |
540 | } |
541 | } // namespace |
542 | |
543 | void NotNullTerminatedResultCheck::registerMatchers(MatchFinder *Finder) { |
544 | auto IncOp = |
545 | binaryOperator(hasOperatorName(Name: "+" ), |
546 | hasEitherOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: integerLiteral()))); |
547 | |
548 | auto DecOp = |
549 | binaryOperator(hasOperatorName(Name: "-" ), |
550 | hasEitherOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: integerLiteral()))); |
551 | |
552 | auto HasIncOp = anyOf(ignoringImpCasts(InnerMatcher: IncOp), hasDescendant(IncOp)); |
553 | auto HasDecOp = anyOf(ignoringImpCasts(InnerMatcher: DecOp), hasDescendant(DecOp)); |
554 | |
555 | auto Container = ignoringImpCasts(InnerMatcher: cxxMemberCallExpr(hasDescendant(declRefExpr( |
556 | hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(hasDeclaration(InnerMatcher: recordDecl( |
557 | hasAnyName("::std::vector" , "::std::list" , "::std::deque" )))))))))); |
558 | |
559 | auto StringTy = type(hasUnqualifiedDesugaredType(InnerMatcher: recordType( |
560 | hasDeclaration(InnerMatcher: cxxRecordDecl(hasName(Name: "::std::basic_string" )))))); |
561 | |
562 | auto AnyOfStringTy = |
563 | anyOf(hasType(InnerMatcher: StringTy), hasType(InnerMatcher: qualType(pointsTo(InnerMatcher: StringTy)))); |
564 | |
565 | auto CharTyArray = hasType(InnerMatcher: qualType(hasCanonicalType( |
566 | InnerMatcher: arrayType(hasElementType(isAnyCharacter())).bind(ID: DestArrayTyName)))); |
567 | |
568 | auto CharTyPointer = hasType( |
569 | InnerMatcher: qualType(hasCanonicalType(InnerMatcher: pointerType(pointee(isAnyCharacter()))))); |
570 | |
571 | auto AnyOfCharTy = anyOf(CharTyArray, CharTyPointer); |
572 | |
573 | //===--------------------------------------------------------------------===// |
574 | // The following six cases match problematic length expressions. |
575 | //===--------------------------------------------------------------------===// |
576 | |
577 | // - Example: char src[] = "foo"; strlen(src); |
578 | auto Strlen = |
579 | callExpr(callee(InnerMatcher: functionDecl(hasAnyName("::strlen" , "::wcslen" )))) |
580 | .bind(ID: WrongLengthExprName); |
581 | |
582 | // - Example: std::string str = "foo"; str.size(); |
583 | auto SizeOrLength = |
584 | cxxMemberCallExpr(on(InnerMatcher: expr(AnyOfStringTy).bind(ID: "Foo" )), |
585 | has(memberExpr(member(InnerMatcher: hasAnyName("size" , "length" ))))) |
586 | .bind(ID: WrongLengthExprName); |
587 | |
588 | // - Example: char src[] = "foo"; sizeof(src); |
589 | auto SizeOfCharExpr = unaryExprOrTypeTraitExpr(has(expr(AnyOfCharTy))); |
590 | |
591 | auto WrongLength = |
592 | ignoringImpCasts(InnerMatcher: anyOf(Strlen, SizeOrLength, hasDescendant(Strlen), |
593 | hasDescendant(SizeOrLength))); |
594 | |
595 | // - Example: length = strlen(src); |
596 | auto DREWithoutInc = |
597 | ignoringImpCasts(InnerMatcher: declRefExpr(to(InnerMatcher: varDecl(hasInitializer(InnerMatcher: WrongLength))))); |
598 | |
599 | auto AnyOfCallOrDREWithoutInc = anyOf(DREWithoutInc, WrongLength); |
600 | |
601 | // - Example: int getLength(const char *str) { return strlen(str); } |
602 | auto CallExprReturnWithoutInc = ignoringImpCasts(InnerMatcher: callExpr(callee(InnerMatcher: functionDecl( |
603 | hasBody(InnerMatcher: has(returnStmt(hasReturnValue(InnerMatcher: AnyOfCallOrDREWithoutInc)))))))); |
604 | |
605 | // - Example: int length = getLength(src); |
606 | auto DREHasReturnWithoutInc = ignoringImpCasts( |
607 | InnerMatcher: declRefExpr(to(InnerMatcher: varDecl(hasInitializer(InnerMatcher: CallExprReturnWithoutInc))))); |
608 | |
609 | auto AnyOfWrongLengthInit = |
610 | anyOf(WrongLength, AnyOfCallOrDREWithoutInc, CallExprReturnWithoutInc, |
611 | DREHasReturnWithoutInc); |
612 | |
613 | //===--------------------------------------------------------------------===// |
614 | // The following five cases match the 'destination' array length's |
615 | // expression which is used in 'memcpy()' and 'memmove()' matchers. |
616 | //===--------------------------------------------------------------------===// |
617 | |
618 | // Note: Sometimes the size of char is explicitly written out. |
619 | auto SizeExpr = anyOf(SizeOfCharExpr, integerLiteral(equals(Value: 1))); |
620 | |
621 | auto MallocLengthExpr = allOf( |
622 | callee(InnerMatcher: functionDecl( |
623 | hasAnyName("::alloca" , "::calloc" , "malloc" , "realloc" ))), |
624 | hasAnyArgument(InnerMatcher: allOf(unless(SizeExpr), expr().bind(ID: DestMallocExprName)))); |
625 | |
626 | // - Example: (char *)malloc(length); |
627 | auto DestMalloc = anyOf(callExpr(MallocLengthExpr), |
628 | hasDescendant(callExpr(MallocLengthExpr))); |
629 | |
630 | // - Example: new char[length]; |
631 | auto DestCXXNewExpr = ignoringImpCasts( |
632 | InnerMatcher: cxxNewExpr(hasArraySize(InnerMatcher: expr().bind(ID: DestMallocExprName)))); |
633 | |
634 | auto AnyOfDestInit = anyOf(DestMalloc, DestCXXNewExpr); |
635 | |
636 | // - Example: char dest[13]; or char dest[length]; |
637 | auto DestArrayTyDecl = declRefExpr( |
638 | to(InnerMatcher: anyOf(varDecl(CharTyArray).bind(ID: DestVarDeclName), |
639 | varDecl(hasInitializer(InnerMatcher: AnyOfDestInit)).bind(ID: DestVarDeclName)))); |
640 | |
641 | // - Example: foo[bar[baz]].qux; (or just ParmVarDecl) |
642 | auto DestUnknownDecl = |
643 | declRefExpr(to(InnerMatcher: varDecl(AnyOfCharTy).bind(ID: DestVarDeclName)), |
644 | expr().bind(ID: UnknownDestName)) |
645 | .bind(ID: DestExprName); |
646 | |
647 | auto AnyOfDestDecl = ignoringImpCasts( |
648 | InnerMatcher: anyOf(allOf(hasDefinition(InnerMatcher: anyOf(AnyOfDestInit, DestArrayTyDecl, |
649 | hasDescendant(DestArrayTyDecl))), |
650 | expr().bind(ID: DestExprName)), |
651 | anyOf(DestUnknownDecl, hasDescendant(DestUnknownDecl)))); |
652 | |
653 | auto NullTerminatorExpr = binaryOperator( |
654 | hasLHS(InnerMatcher: anyOf(hasDescendant(declRefExpr(to(InnerMatcher: varDecl( |
655 | equalsBoundNode(ID: std::string(DestVarDeclName)))))), |
656 | hasDescendant(declRefExpr( |
657 | equalsBoundNode(ID: std::string(DestExprName)))))), |
658 | hasRHS(InnerMatcher: ignoringImpCasts( |
659 | InnerMatcher: anyOf(characterLiteral(equals(Value: 0U)), integerLiteral(equals(Value: 0)))))); |
660 | |
661 | auto SrcDecl = |
662 | declRefExpr(to(InnerMatcher: decl().bind(ID: SrcVarDeclName)), |
663 | anyOf(hasAncestor(cxxMemberCallExpr().bind(ID: SrcExprName)), |
664 | expr().bind(ID: SrcExprName))); |
665 | |
666 | auto AnyOfSrcDecl = |
667 | ignoringImpCasts(InnerMatcher: anyOf(stringLiteral().bind(ID: SrcExprName), |
668 | hasDescendant(stringLiteral().bind(ID: SrcExprName)), |
669 | SrcDecl, hasDescendant(SrcDecl))); |
670 | |
671 | //===--------------------------------------------------------------------===// |
672 | // Match the problematic function calls. |
673 | //===--------------------------------------------------------------------===// |
674 | |
675 | struct CallContext { |
676 | CallContext(StringRef Name, std::optional<unsigned> DestinationPos, |
677 | std::optional<unsigned> SourcePos, unsigned LengthPos, |
678 | bool WithIncrease) |
679 | : Name(Name), DestinationPos(DestinationPos), SourcePos(SourcePos), |
680 | LengthPos(LengthPos), WithIncrease(WithIncrease){}; |
681 | |
682 | StringRef Name; |
683 | std::optional<unsigned> DestinationPos; |
684 | std::optional<unsigned> SourcePos; |
685 | unsigned LengthPos; |
686 | bool WithIncrease; |
687 | }; |
688 | |
689 | auto MatchDestination = [=](CallContext CC) { |
690 | return hasArgument(N: *CC.DestinationPos, |
691 | InnerMatcher: allOf(AnyOfDestDecl, |
692 | unless(hasAncestor(compoundStmt( |
693 | hasDescendant(NullTerminatorExpr)))), |
694 | unless(Container))); |
695 | }; |
696 | |
697 | auto MatchSource = [=](CallContext CC) { |
698 | return hasArgument(N: *CC.SourcePos, InnerMatcher: AnyOfSrcDecl); |
699 | }; |
700 | |
701 | auto MatchGivenLength = [=](CallContext CC) { |
702 | return hasArgument( |
703 | N: CC.LengthPos, |
704 | InnerMatcher: allOf( |
705 | anyOf( |
706 | ignoringImpCasts(InnerMatcher: integerLiteral().bind(ID: WrongLengthExprName)), |
707 | allOf(unless(hasDefinition(InnerMatcher: SizeOfCharExpr)), |
708 | allOf(CC.WithIncrease |
709 | ? ignoringImpCasts(InnerMatcher: hasDefinition(InnerMatcher: HasIncOp)) |
710 | : ignoringImpCasts(InnerMatcher: allOf( |
711 | unless(hasDefinition(InnerMatcher: HasIncOp)), |
712 | anyOf(hasDefinition(InnerMatcher: binaryOperator().bind( |
713 | ID: UnknownLengthName)), |
714 | hasDefinition(InnerMatcher: anything())))), |
715 | AnyOfWrongLengthInit))), |
716 | expr().bind(ID: LengthExprName))); |
717 | }; |
718 | |
719 | auto MatchCall = [=](CallContext CC) { |
720 | std::string CharHandlerFuncName = "::" + CC.Name.str(); |
721 | |
722 | // Try to match with 'wchar_t' based function calls. |
723 | std::string WcharHandlerFuncName = |
724 | "::" + (CC.Name.starts_with(Prefix: "mem" ) ? "w" + CC.Name.str() |
725 | : "wcs" + CC.Name.substr(Start: 3).str()); |
726 | |
727 | return allOf(callee(InnerMatcher: functionDecl( |
728 | hasAnyName(CharHandlerFuncName, WcharHandlerFuncName))), |
729 | MatchGivenLength(CC)); |
730 | }; |
731 | |
732 | auto Match = [=](CallContext CC) { |
733 | if (CC.DestinationPos && CC.SourcePos) |
734 | return allOf(MatchCall(CC), MatchDestination(CC), MatchSource(CC)); |
735 | |
736 | if (CC.DestinationPos && !CC.SourcePos) |
737 | return allOf(MatchCall(CC), MatchDestination(CC), |
738 | hasArgument(N: *CC.DestinationPos, InnerMatcher: anything())); |
739 | |
740 | if (!CC.DestinationPos && CC.SourcePos) |
741 | return allOf(MatchCall(CC), MatchSource(CC), |
742 | hasArgument(N: *CC.SourcePos, InnerMatcher: anything())); |
743 | |
744 | llvm_unreachable("Unhandled match" ); |
745 | }; |
746 | |
747 | // void *memcpy(void *dest, const void *src, size_t count) |
748 | auto Memcpy = Match({"memcpy" , 0, 1, 2, false}); |
749 | |
750 | // errno_t memcpy_s(void *dest, size_t ds, const void *src, size_t count) |
751 | auto MemcpyS = Match({"memcpy_s" , 0, 2, 3, false}); |
752 | |
753 | // void *memchr(const void *src, int c, size_t count) |
754 | auto Memchr = Match({"memchr" , std::nullopt, 0, 2, false}); |
755 | |
756 | // void *memmove(void *dest, const void *src, size_t count) |
757 | auto Memmove = Match({"memmove" , 0, 1, 2, false}); |
758 | |
759 | // errno_t memmove_s(void *dest, size_t ds, const void *src, size_t count) |
760 | auto MemmoveS = Match({"memmove_s" , 0, 2, 3, false}); |
761 | |
762 | // int strncmp(const char *str1, const char *str2, size_t count); |
763 | auto StrncmpRHS = Match({"strncmp" , std::nullopt, 1, 2, true}); |
764 | auto StrncmpLHS = Match({"strncmp" , std::nullopt, 0, 2, true}); |
765 | |
766 | // size_t strxfrm(char *dest, const char *src, size_t count); |
767 | auto Strxfrm = Match({"strxfrm" , 0, 1, 2, false}); |
768 | |
769 | // errno_t strerror_s(char *buffer, size_t bufferSize, int errnum); |
770 | auto StrerrorS = Match({"strerror_s" , 0, std::nullopt, 1, false}); |
771 | |
772 | auto AnyOfMatchers = anyOf(Memcpy, MemcpyS, Memmove, MemmoveS, StrncmpRHS, |
773 | StrncmpLHS, Strxfrm, StrerrorS); |
774 | |
775 | Finder->addMatcher(NodeMatch: callExpr(AnyOfMatchers).bind(ID: FunctionExprName), Action: this); |
776 | |
777 | // Need to remove the CastExpr from 'memchr()' as 'strchr()' returns 'char *'. |
778 | Finder->addMatcher( |
779 | NodeMatch: callExpr(Memchr, |
780 | unless(hasAncestor(castExpr(unless(implicitCastExpr()))))) |
781 | .bind(ID: FunctionExprName), |
782 | Action: this); |
783 | Finder->addMatcher( |
784 | NodeMatch: castExpr(allOf(unless(implicitCastExpr()), |
785 | has(callExpr(Memchr).bind(ID: FunctionExprName)))) |
786 | .bind(ID: CastExprName), |
787 | Action: this); |
788 | } |
789 | |
790 | void NotNullTerminatedResultCheck::check( |
791 | const MatchFinder::MatchResult &Result) { |
792 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
793 | if (FunctionExpr->getBeginLoc().isMacroID()) |
794 | return; |
795 | |
796 | if (WantToUseSafeFunctions && PP->isMacroDefined(Id: "__STDC_LIB_EXT1__" )) { |
797 | std::optional<bool> AreSafeFunctionsWanted; |
798 | |
799 | Preprocessor::macro_iterator It = PP->macro_begin(); |
800 | while (It != PP->macro_end() && !AreSafeFunctionsWanted) { |
801 | if (It->first->getName() == "__STDC_WANT_LIB_EXT1__" ) { |
802 | const auto *MI = PP->getMacroInfo(II: It->first); |
803 | // PP->getMacroInfo() returns nullptr if macro has no definition. |
804 | if (MI) { |
805 | const auto &T = MI->tokens().back(); |
806 | if (T.isLiteral() && T.getLiteralData()) { |
807 | StringRef ValueStr = StringRef(T.getLiteralData(), T.getLength()); |
808 | llvm::APInt IntValue; |
809 | ValueStr.getAsInteger(Radix: 10, Result&: IntValue); |
810 | AreSafeFunctionsWanted = IntValue.getZExtValue(); |
811 | } |
812 | } |
813 | } |
814 | |
815 | ++It; |
816 | } |
817 | |
818 | if (AreSafeFunctionsWanted) |
819 | UseSafeFunctions = *AreSafeFunctionsWanted; |
820 | } |
821 | |
822 | StringRef Name = FunctionExpr->getDirectCallee()->getName(); |
823 | if (Name.starts_with(Prefix: "mem" ) || Name.starts_with(Prefix: "wmem" )) |
824 | memoryHandlerFunctionFix(Name, Result); |
825 | else if (Name == "strerror_s" ) |
826 | strerror_sFix(Result); |
827 | else if (Name.ends_with(Suffix: "ncmp" )) |
828 | ncmpFix(Name, Result); |
829 | else if (Name.ends_with(Suffix: "xfrm" )) |
830 | xfrmFix(Name, Result); |
831 | } |
832 | |
833 | void NotNullTerminatedResultCheck::memoryHandlerFunctionFix( |
834 | StringRef Name, const MatchFinder::MatchResult &Result) { |
835 | if (isCorrectGivenLength(Result)) |
836 | return; |
837 | |
838 | if (Name.ends_with(Suffix: "chr" )) { |
839 | memchrFix(Name, Result); |
840 | return; |
841 | } |
842 | |
843 | if ((Name.contains(Other: "cpy" ) || Name.contains(Other: "move" )) && |
844 | (isDestAndSrcEquals(Result) || isFixedGivenLengthAndUnknownSrc(Result))) |
845 | return; |
846 | |
847 | auto Diag = |
848 | diag(Loc: Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName)->getBeginLoc(), |
849 | Description: "the result from calling '%0' is not null-terminated" ) |
850 | << Name; |
851 | |
852 | if (Name.ends_with(Suffix: "cpy" )) { |
853 | memcpyFix(Name, Result, Diag); |
854 | } else if (Name.ends_with(Suffix: "cpy_s" )) { |
855 | memcpy_sFix(Name, Result, Diag); |
856 | } else if (Name.ends_with(Suffix: "move" )) { |
857 | memmoveFix(Name, Result, Diag); |
858 | } else if (Name.ends_with(Suffix: "move_s" )) { |
859 | isDestCapacityFix(Result, Diag); |
860 | lengthArgHandle(LengthHandle: LengthHandleKind::Increase, Result, Diag); |
861 | } |
862 | } |
863 | |
864 | void NotNullTerminatedResultCheck::memcpyFix( |
865 | StringRef Name, const MatchFinder::MatchResult &Result, |
866 | DiagnosticBuilder &Diag) { |
867 | bool IsOverflows = isDestCapacityFix(Result, Diag); |
868 | bool IsDestFixed = isDestExprFix(Result, Diag); |
869 | |
870 | bool IsCopy = |
871 | isGivenLengthEqualToSrcLength(Result) || isDestBasedOnGivenLength(Result); |
872 | |
873 | bool IsSafe = UseSafeFunctions && IsOverflows && isKnownDest(Result) && |
874 | !isDestBasedOnGivenLength(Result); |
875 | |
876 | bool IsDestLengthNotRequired = |
877 | IsSafe && getLangOpts().CPlusPlus && |
878 | Result.Nodes.getNodeAs<ArrayType>(ID: DestArrayTyName) && !IsDestFixed; |
879 | |
880 | renameMemcpy(Name, IsCopy, IsSafe, Result, Diag); |
881 | |
882 | if (IsSafe && !IsDestLengthNotRequired) |
883 | insertDestCapacityArg(IsOverflows, Name, Result, Diag); |
884 | |
885 | if (IsCopy) |
886 | removeArg(ArgPos: 2, Result, Diag); |
887 | |
888 | if (!IsCopy && !IsSafe) |
889 | insertNullTerminatorExpr(Name, Result, Diag); |
890 | } |
891 | |
892 | void NotNullTerminatedResultCheck::memcpy_sFix( |
893 | StringRef Name, const MatchFinder::MatchResult &Result, |
894 | DiagnosticBuilder &Diag) { |
895 | bool IsOverflows = isDestCapacityFix(Result, Diag); |
896 | bool IsDestFixed = isDestExprFix(Result, Diag); |
897 | |
898 | bool RemoveDestLength = getLangOpts().CPlusPlus && |
899 | Result.Nodes.getNodeAs<ArrayType>(ID: DestArrayTyName) && |
900 | !IsDestFixed; |
901 | bool IsCopy = isGivenLengthEqualToSrcLength(Result); |
902 | bool IsSafe = IsOverflows; |
903 | |
904 | renameMemcpy(Name, IsCopy, IsSafe, Result, Diag); |
905 | |
906 | if (!IsSafe || (IsSafe && RemoveDestLength)) |
907 | removeArg(ArgPos: 1, Result, Diag); |
908 | else if (IsOverflows && isKnownDest(Result)) |
909 | lengthArgPosHandle(ArgPos: 1, LengthHandle: LengthHandleKind::Increase, Result, Diag); |
910 | |
911 | if (IsCopy) |
912 | removeArg(ArgPos: 3, Result, Diag); |
913 | |
914 | if (!IsCopy && !IsSafe) |
915 | insertNullTerminatorExpr(Name, Result, Diag); |
916 | } |
917 | |
918 | void NotNullTerminatedResultCheck::memchrFix( |
919 | StringRef Name, const MatchFinder::MatchResult &Result) { |
920 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
921 | if (const auto *GivenCL = dyn_cast<CharacterLiteral>(Val: FunctionExpr->getArg(Arg: 1))) |
922 | if (GivenCL->getValue() != 0) |
923 | return; |
924 | |
925 | auto Diag = diag(FunctionExpr->getArg(Arg: 2)->IgnoreParenCasts()->getBeginLoc(), |
926 | "the length is too short to include the null terminator" ); |
927 | |
928 | if (const auto *CastExpr = Result.Nodes.getNodeAs<Expr>(ID: CastExprName)) { |
929 | const auto CastRemoveFix = FixItHint::CreateRemoval( |
930 | RemoveRange: SourceRange(CastExpr->getBeginLoc(), |
931 | FunctionExpr->getBeginLoc().getLocWithOffset(Offset: -1))); |
932 | Diag << CastRemoveFix; |
933 | } |
934 | |
935 | StringRef NewFuncName = (Name[0] != 'w') ? "strchr" : "wcschr" ; |
936 | renameFunc(NewFuncName, Result, Diag); |
937 | removeArg(2, Result, Diag); |
938 | } |
939 | |
940 | void NotNullTerminatedResultCheck::memmoveFix( |
941 | StringRef Name, const MatchFinder::MatchResult &Result, |
942 | DiagnosticBuilder &Diag) const { |
943 | bool IsOverflows = isDestCapacityFix(Result, Diag); |
944 | |
945 | if (UseSafeFunctions && isKnownDest(Result)) { |
946 | renameFunc(NewFuncName: (Name[0] != 'w') ? "memmove_s" : "wmemmove_s" , Result, Diag); |
947 | insertDestCapacityArg(IsOverflows, Name, Result, Diag); |
948 | } |
949 | |
950 | lengthArgHandle(LengthHandle: LengthHandleKind::Increase, Result, Diag); |
951 | } |
952 | |
953 | void NotNullTerminatedResultCheck::strerror_sFix( |
954 | const MatchFinder::MatchResult &Result) { |
955 | auto Diag = |
956 | diag(Loc: Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName)->getBeginLoc(), |
957 | Description: "the result from calling 'strerror_s' is not null-terminated and " |
958 | "missing the last character of the error message" ); |
959 | |
960 | isDestCapacityFix(Result, Diag); |
961 | lengthArgHandle(LengthHandle: LengthHandleKind::Increase, Result, Diag); |
962 | } |
963 | |
964 | void NotNullTerminatedResultCheck::ncmpFix( |
965 | StringRef Name, const MatchFinder::MatchResult &Result) { |
966 | const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName); |
967 | const Expr *FirstArgExpr = FunctionExpr->getArg(Arg: 0)->IgnoreImpCasts(); |
968 | const Expr *SecondArgExpr = FunctionExpr->getArg(Arg: 1)->IgnoreImpCasts(); |
969 | bool IsLengthTooLong = false; |
970 | |
971 | if (const CallExpr *StrlenExpr = getStrlenExpr(Result)) { |
972 | const Expr *LengthExprArg = StrlenExpr->getArg(Arg: 0); |
973 | StringRef FirstExprStr = exprToStr(E: FirstArgExpr, Result).trim(); |
974 | StringRef SecondExprStr = exprToStr(E: SecondArgExpr, Result).trim(); |
975 | StringRef LengthArgStr = exprToStr(E: LengthExprArg, Result).trim(); |
976 | IsLengthTooLong = |
977 | LengthArgStr == FirstExprStr || LengthArgStr == SecondExprStr; |
978 | } else { |
979 | int SrcLength = |
980 | getLength(E: Result.Nodes.getNodeAs<Expr>(ID: SrcExprName), Result); |
981 | int GivenLength = getGivenLength(Result); |
982 | if (SrcLength != 0 && GivenLength != 0) |
983 | IsLengthTooLong = GivenLength > SrcLength; |
984 | } |
985 | |
986 | if (!IsLengthTooLong && !isStringDataAndLength(Result)) |
987 | return; |
988 | |
989 | auto Diag = diag(FunctionExpr->getArg(Arg: 2)->IgnoreParenCasts()->getBeginLoc(), |
990 | "comparison length is too long and might lead to a " |
991 | "buffer overflow" ); |
992 | |
993 | lengthArgHandle(LengthHandleKind::Decrease, Result, Diag); |
994 | } |
995 | |
996 | void NotNullTerminatedResultCheck::xfrmFix( |
997 | StringRef Name, const MatchFinder::MatchResult &Result) { |
998 | if (!isDestCapacityOverflows(Result)) |
999 | return; |
1000 | |
1001 | auto Diag = |
1002 | diag(Loc: Result.Nodes.getNodeAs<CallExpr>(ID: FunctionExprName)->getBeginLoc(), |
1003 | Description: "the result from calling '%0' is not null-terminated" ) |
1004 | << Name; |
1005 | |
1006 | isDestCapacityFix(Result, Diag); |
1007 | lengthArgHandle(LengthHandle: LengthHandleKind::Increase, Result, Diag); |
1008 | } |
1009 | |
1010 | } // namespace clang::tidy::bugprone |
1011 | |