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