clang-tools 22.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 llvm::StringLiteral FunctionExprName = "FunctionExpr";
23constexpr llvm::StringLiteral CastExprName = "CastExpr";
24constexpr llvm::StringLiteral UnknownDestName = "UnknownDest";
25constexpr llvm::StringLiteral DestArrayTyName = "DestArrayTy";
26constexpr llvm::StringLiteral DestVarDeclName = "DestVarDecl";
27constexpr llvm::StringLiteral DestMallocExprName = "DestMalloc";
28constexpr llvm::StringLiteral DestExprName = "DestExpr";
29constexpr llvm::StringLiteral SrcVarDeclName = "SrcVarDecl";
30constexpr llvm::StringLiteral SrcExprName = "SrcExpr";
31constexpr llvm::StringLiteral LengthExprName = "LengthExpr";
32constexpr llvm::StringLiteral WrongLengthExprName = "WrongLength";
33constexpr llvm::StringLiteral UnknownLengthName = "UnknownLength";
34
36
37namespace {
38static 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.
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 if (!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 if (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 (int Length =
124 getLength(Result.Nodes.getNodeAs<Expr>(WrongLengthExprName), Result))
125 return Length;
126
127 if (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 (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 StringRef DestCapacityExprStr =
178 exprToStr(getDestCapacityExpr(Result), Result).trim();
179 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 int GivenLength = getGivenLength(Result);
230 int SrcLength = getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);
231
232 if (GivenLength != 0 && SrcLength != 0 && GivenLength == SrcLength)
233 return true;
234
235 if (const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(LengthExprName))
236 if (isa<BinaryOperator>(LengthExpr->IgnoreParenImpCasts()))
237 return false;
238
239 // Check the strlen()'s argument's 'VarDecl' is equal to the source 'VarDecl'.
240 if (const CallExpr *StrlenCE = getStrlenExpr(Result))
241 if (const auto *ArgDRE =
242 dyn_cast<DeclRefExpr>(StrlenCE->getArg(0)->IgnoreImpCasts()))
243 if (const auto *SrcVD = Result.Nodes.getNodeAs<VarDecl>(SrcVarDeclName))
244 return dyn_cast<VarDecl>(ArgDRE->getDecl()) == SrcVD;
245
246 return false;
247}
248
249static bool isCorrectGivenLength(const MatchFinder::MatchResult &Result) {
250 if (Result.Nodes.getNodeAs<Expr>(UnknownLengthName))
251 return false;
252
253 return !isGivenLengthEqualToSrcLength(Result);
254}
255
256// If we rewrite the function call we need to create extra space to hold the
257// null terminator. The new necessary capacity overflows without that '+ 1'
258// size and we need to correct the given capacity.
259static bool isDestCapacityOverflows(const MatchFinder::MatchResult &Result) {
260 if (!isKnownDest(Result))
261 return true;
262
263 const Expr *DestCapacityExpr = getDestCapacityExpr(Result);
264 int DestCapacity = getLength(DestCapacityExpr, Result);
265 int GivenLength = getGivenLength(Result);
266
267 if (GivenLength != 0 && DestCapacity != 0)
268 return isGivenLengthEqualToSrcLength(Result) && DestCapacity == GivenLength;
269
270 // Assume that the destination array's capacity cannot overflow if the
271 // expression of the memory allocation contains '+ 1'.
272 StringRef DestCapacityExprStr = exprToStr(DestCapacityExpr, Result);
273 if (DestCapacityExprStr.contains("+1") || DestCapacityExprStr.contains("+ 1"))
274 return false;
275
276 return true;
277}
278
279static bool
280isFixedGivenLengthAndUnknownSrc(const MatchFinder::MatchResult &Result) {
281 if (Result.Nodes.getNodeAs<IntegerLiteral>(WrongLengthExprName))
282 return !getLength(Result.Nodes.getNodeAs<Expr>(SrcExprName), Result);
283
284 return false;
285}
286
287//===----------------------------------------------------------------------===//
288// Code injection functions.
289//===----------------------------------------------------------------------===//
290
291// Increase or decrease \p LengthExpr by one.
292static void lengthExprHandle(const Expr *LengthExpr,
293 LengthHandleKind LengthHandle,
294 const MatchFinder::MatchResult &Result,
295 DiagnosticBuilder &Diag) {
296 LengthExpr = LengthExpr->IgnoreParenImpCasts();
297
298 // See whether we work with a macro.
299 bool IsMacroDefinition = false;
300 StringRef LengthExprStr = exprToStr(LengthExpr, Result);
301 Preprocessor::macro_iterator It = PP->macro_begin();
302 while (It != PP->macro_end() && !IsMacroDefinition) {
303 if (It->first->getName() == LengthExprStr)
304 IsMacroDefinition = true;
305
306 ++It;
307 }
308
309 // Try to obtain an 'IntegerLiteral' and adjust it.
310 if (!IsMacroDefinition) {
311 if (const auto *LengthIL = dyn_cast<IntegerLiteral>(LengthExpr)) {
312 uint64_t NewLength =
313 LengthIL->getValue().getZExtValue() +
314 (LengthHandle == LengthHandleKind::Increase ? 1 : -1);
315
316 const auto NewLengthFix = FixItHint::CreateReplacement(
317 LengthIL->getSourceRange(),
318 (Twine(NewLength) + (isInjectUL(Result) ? "UL" : "")).str());
319 Diag << NewLengthFix;
320 return;
321 }
322 }
323
324 // Try to obtain and remove the '+ 1' string as a decrement fix.
325 const auto *BO = dyn_cast<BinaryOperator>(LengthExpr);
326 if (BO && BO->getOpcode() == BO_Add &&
327 LengthHandle == LengthHandleKind::Decrease) {
328 const Expr *LhsExpr = BO->getLHS()->IgnoreImpCasts();
329 const Expr *RhsExpr = BO->getRHS()->IgnoreImpCasts();
330
331 if (const auto *LhsIL = dyn_cast<IntegerLiteral>(LhsExpr)) {
332 if (LhsIL->getValue().getZExtValue() == 1) {
333 Diag << FixItHint::CreateRemoval(
334 {LhsIL->getBeginLoc(),
335 RhsExpr->getBeginLoc().getLocWithOffset(-1)});
336 return;
337 }
338 }
339
340 if (const auto *RhsIL = dyn_cast<IntegerLiteral>(RhsExpr)) {
341 if (RhsIL->getValue().getZExtValue() == 1) {
342 Diag << FixItHint::CreateRemoval(
343 {LhsExpr->getEndLoc().getLocWithOffset(1), RhsIL->getEndLoc()});
344 return;
345 }
346 }
347 }
348
349 // Try to inject the '+ 1'/'- 1' string.
350 bool NeedInnerParen = BO && BO->getOpcode() != BO_Add;
351
352 if (NeedInnerParen)
353 Diag << FixItHint::CreateInsertion(LengthExpr->getBeginLoc(), "(");
354
355 SmallString<8> Injection;
356 if (NeedInnerParen)
357 Injection += ')';
358 Injection += LengthHandle == LengthHandleKind::Increase ? " + 1" : " - 1";
359 if (isInjectUL(Result))
360 Injection += "UL";
361
362 Diag << FixItHint::CreateInsertion(exprLocEnd(LengthExpr, Result), Injection);
363}
364
365static void lengthArgHandle(LengthHandleKind LengthHandle,
366 const MatchFinder::MatchResult &Result,
367 DiagnosticBuilder &Diag) {
368 const auto *LengthExpr = Result.Nodes.getNodeAs<Expr>(LengthExprName);
369 lengthExprHandle(LengthExpr, LengthHandle, Result, Diag);
370}
371
372static void lengthArgPosHandle(unsigned ArgPos, LengthHandleKind LengthHandle,
373 const MatchFinder::MatchResult &Result,
374 DiagnosticBuilder &Diag) {
375 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
376 lengthExprHandle(FunctionExpr->getArg(ArgPos), LengthHandle, Result, Diag);
377}
378
379// The string handler functions are only operates with plain 'char'/'wchar_t'
380// without 'unsigned/signed', therefore we need to cast it.
381static bool isDestExprFix(const MatchFinder::MatchResult &Result,
382 DiagnosticBuilder &Diag) {
383 const auto *Dest = Result.Nodes.getNodeAs<Expr>(DestExprName);
384 if (!Dest)
385 return false;
386
387 std::string TempTyStr = Dest->getType().getAsString();
388 StringRef TyStr = TempTyStr;
389 if (TyStr.starts_with("char") || TyStr.starts_with("wchar_t"))
390 return false;
391
392 Diag << FixItHint::CreateInsertion(Dest->getBeginLoc(), "(char *)");
393 return true;
394}
395
396// If the destination array is the same length as the given length we have to
397// increase the capacity by one to create space for the null terminator.
398static bool isDestCapacityFix(const MatchFinder::MatchResult &Result,
399 DiagnosticBuilder &Diag) {
400 bool IsOverflows = isDestCapacityOverflows(Result);
401 if (IsOverflows)
402 if (const Expr *CapacityExpr = getDestCapacityExpr(Result))
403 lengthExprHandle(CapacityExpr, LengthHandleKind::Increase, Result, Diag);
404
405 return IsOverflows;
406}
407
408static void removeArg(int ArgPos, const MatchFinder::MatchResult &Result,
409 DiagnosticBuilder &Diag) {
410 // This is the following structure: (src, '\0', strlen(src))
411 // ArgToRemove: ~~~~~~~~~~~
412 // LHSArg: ~~~~
413 // RemoveArgFix: ~~~~~~~~~~~~~
414 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
415 const Expr *ArgToRemove = FunctionExpr->getArg(ArgPos);
416 const Expr *LHSArg = FunctionExpr->getArg(ArgPos - 1);
417 const auto RemoveArgFix = FixItHint::CreateRemoval(
418 SourceRange(exprLocEnd(LHSArg, Result),
419 exprLocEnd(ArgToRemove, Result).getLocWithOffset(-1)));
420 Diag << RemoveArgFix;
421}
422
423static void renameFunc(StringRef NewFuncName,
424 const MatchFinder::MatchResult &Result,
425 DiagnosticBuilder &Diag) {
426 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
427 int FuncNameLength =
428 FunctionExpr->getDirectCallee()->getIdentifier()->getLength();
429 SourceRange FuncNameRange(
430 FunctionExpr->getBeginLoc(),
431 FunctionExpr->getBeginLoc().getLocWithOffset(FuncNameLength - 1));
432
433 const auto FuncNameFix =
434 FixItHint::CreateReplacement(FuncNameRange, NewFuncName);
435 Diag << FuncNameFix;
436}
437
438static void renameMemcpy(StringRef Name, bool IsCopy, bool IsSafe,
439 const MatchFinder::MatchResult &Result,
440 DiagnosticBuilder &Diag) {
441 SmallString<10> NewFuncName;
442 NewFuncName = (Name[0] != 'w') ? "str" : "wcs";
443 NewFuncName += IsCopy ? "cpy" : "ncpy";
444 NewFuncName += IsSafe ? "_s" : "";
445 renameFunc(NewFuncName, Result, Diag);
446}
447
448static void insertDestCapacityArg(bool IsOverflows, StringRef Name,
449 const MatchFinder::MatchResult &Result,
450 DiagnosticBuilder &Diag) {
451 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
452 SmallString<64> NewSecondArg;
453
454 if (int DestLength = getDestCapacity(Result)) {
455 NewSecondArg = Twine(IsOverflows ? DestLength + 1 : DestLength).str();
456 } else {
457 NewSecondArg =
458 (Twine(exprToStr(getDestCapacityExpr(Result), Result)) +
459 (IsOverflows ? (!isInjectUL(Result) ? " + 1" : " + 1UL") : ""))
460 .str();
461 }
462
463 NewSecondArg += ", ";
464 const auto InsertNewArgFix = FixItHint::CreateInsertion(
465 FunctionExpr->getArg(1)->getBeginLoc(), NewSecondArg);
466 Diag << InsertNewArgFix;
467}
468
469static void insertNullTerminatorExpr(StringRef Name,
470 const MatchFinder::MatchResult &Result,
471 DiagnosticBuilder &Diag) {
472 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
473 int FuncLocStartColumn = Result.SourceManager->getPresumedColumnNumber(
474 FunctionExpr->getBeginLoc());
475 SourceRange SpaceRange(
476 FunctionExpr->getBeginLoc().getLocWithOffset(-FuncLocStartColumn + 1),
477 FunctionExpr->getBeginLoc());
478 StringRef SpaceBeforeStmtStr = Lexer::getSourceText(
479 CharSourceRange::getCharRange(SpaceRange), *Result.SourceManager,
480 Result.Context->getLangOpts(), nullptr);
481
482 SmallString<128> NewAddNullTermExprStr;
483 NewAddNullTermExprStr =
484 (Twine('\n') + SpaceBeforeStmtStr +
485 exprToStr(Result.Nodes.getNodeAs<Expr>(DestExprName), Result) + "[" +
486 exprToStr(Result.Nodes.getNodeAs<Expr>(LengthExprName), Result) +
487 "] = " + ((Name[0] != 'w') ? R"('\0';)" : R"(L'\0';)"))
488 .str();
489
490 const auto AddNullTerminatorExprFix = FixItHint::CreateInsertion(
491 exprLocEnd(FunctionExpr, Result).getLocWithOffset(1),
492 NewAddNullTermExprStr);
493 Diag << AddNullTerminatorExprFix;
494}
495
496//===----------------------------------------------------------------------===//
497// Checker logic with the matchers.
498//===----------------------------------------------------------------------===//
499
501 StringRef Name, ClangTidyContext *Context)
502 : ClangTidyCheck(Name, Context),
503 WantToUseSafeFunctions(Options.get("WantToUseSafeFunctions", true)) {}
504
507 Options.store(Opts, "WantToUseSafeFunctions", WantToUseSafeFunctions);
508}
509
511 const SourceManager &SM, Preprocessor *Pp, Preprocessor *ModuleExpanderPP) {
512 PP = Pp;
513}
514
515namespace {
516AST_MATCHER_P(Expr, hasDefinition, ast_matchers::internal::Matcher<Expr>,
517 InnerMatcher) {
518 const Expr *SimpleNode = &Node;
519 SimpleNode = SimpleNode->IgnoreParenImpCasts();
520
521 if (InnerMatcher.matches(*SimpleNode, Finder, Builder))
522 return true;
523
524 auto DREHasInit = ignoringImpCasts(
525 declRefExpr(to(varDecl(hasInitializer(ignoringImpCasts(InnerMatcher))))));
526
527 if (DREHasInit.matches(*SimpleNode, Finder, Builder))
528 return true;
529
530 const char *const VarDeclName = "variable-declaration";
531 auto DREHasDefinition = ignoringImpCasts(declRefExpr(
532 to(varDecl().bind(VarDeclName)),
533 hasAncestor(compoundStmt(hasDescendant(binaryOperator(
534 hasLHS(declRefExpr(to(varDecl(equalsBoundNode(VarDeclName))))),
535 hasRHS(ignoringImpCasts(InnerMatcher))))))));
536
537 if (DREHasDefinition.matches(*SimpleNode, Finder, Builder))
538 return true;
539
540 return false;
541}
542} // namespace
543
545 auto IncOp =
546 binaryOperator(hasOperatorName("+"),
547 hasEitherOperand(ignoringParenImpCasts(integerLiteral())));
548
549 auto DecOp =
550 binaryOperator(hasOperatorName("-"),
551 hasEitherOperand(ignoringParenImpCasts(integerLiteral())));
552
553 auto HasIncOp = anyOf(ignoringImpCasts(IncOp), hasDescendant(IncOp));
554 auto HasDecOp = anyOf(ignoringImpCasts(DecOp), hasDescendant(DecOp));
555
556 auto Container = ignoringImpCasts(cxxMemberCallExpr(hasDescendant(declRefExpr(
557 hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(recordDecl(
558 hasAnyName("::std::vector", "::std::list", "::std::deque"))))))))));
559
560 auto StringTy = type(hasUnqualifiedDesugaredType(recordType(
561 hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))))));
562
563 auto AnyOfStringTy =
564 anyOf(hasType(StringTy), hasType(qualType(pointsTo(StringTy))));
565
566 auto CharTyArray = hasType(qualType(hasCanonicalType(
567 arrayType(hasElementType(isAnyCharacter())).bind(DestArrayTyName))));
568
569 auto CharTyPointer = hasType(
570 qualType(hasCanonicalType(pointerType(pointee(isAnyCharacter())))));
571
572 auto AnyOfCharTy = anyOf(CharTyArray, CharTyPointer);
573
574 //===--------------------------------------------------------------------===//
575 // The following six cases match problematic length expressions.
576 //===--------------------------------------------------------------------===//
577
578 // - Example: char src[] = "foo"; strlen(src);
579 auto Strlen =
580 callExpr(callee(functionDecl(hasAnyName("::strlen", "::wcslen"))))
581 .bind(WrongLengthExprName);
582
583 // - Example: std::string str = "foo"; str.size();
584 auto SizeOrLength =
585 cxxMemberCallExpr(on(expr(AnyOfStringTy).bind("Foo")),
586 has(memberExpr(member(hasAnyName("size", "length")))))
587 .bind(WrongLengthExprName);
588
589 // - Example: char src[] = "foo"; sizeof(src);
590 auto SizeOfCharExpr = unaryExprOrTypeTraitExpr(has(expr(AnyOfCharTy)));
591
592 auto WrongLength =
593 ignoringImpCasts(anyOf(Strlen, SizeOrLength, hasDescendant(Strlen),
594 hasDescendant(SizeOrLength)));
595
596 // - Example: length = strlen(src);
597 auto DREWithoutInc =
598 ignoringImpCasts(declRefExpr(to(varDecl(hasInitializer(WrongLength)))));
599
600 auto AnyOfCallOrDREWithoutInc = anyOf(DREWithoutInc, WrongLength);
601
602 // - Example: int getLength(const char *str) { return strlen(str); }
603 auto CallExprReturnWithoutInc = ignoringImpCasts(callExpr(callee(functionDecl(
604 hasBody(has(returnStmt(hasReturnValue(AnyOfCallOrDREWithoutInc))))))));
605
606 // - Example: int length = getLength(src);
607 auto DREHasReturnWithoutInc = ignoringImpCasts(
608 declRefExpr(to(varDecl(hasInitializer(CallExprReturnWithoutInc)))));
609
610 auto AnyOfWrongLengthInit =
611 anyOf(WrongLength, AnyOfCallOrDREWithoutInc, CallExprReturnWithoutInc,
612 DREHasReturnWithoutInc);
613
614 //===--------------------------------------------------------------------===//
615 // The following five cases match the 'destination' array length's
616 // expression which is used in 'memcpy()' and 'memmove()' matchers.
617 //===--------------------------------------------------------------------===//
618
619 // Note: Sometimes the size of char is explicitly written out.
620 auto SizeExpr = anyOf(SizeOfCharExpr, integerLiteral(equals(1)));
621
622 auto MallocLengthExpr = allOf(
623 callee(functionDecl(
624 hasAnyName("::alloca", "::calloc", "malloc", "realloc"))),
625 hasAnyArgument(allOf(unless(SizeExpr), expr().bind(DestMallocExprName))));
626
627 // - Example: (char *)malloc(length);
628 auto DestMalloc = anyOf(callExpr(MallocLengthExpr),
629 hasDescendant(callExpr(MallocLengthExpr)));
630
631 // - Example: new char[length];
632 auto DestCXXNewExpr = ignoringImpCasts(
633 cxxNewExpr(hasArraySize(expr().bind(DestMallocExprName))));
634
635 auto AnyOfDestInit = anyOf(DestMalloc, DestCXXNewExpr);
636
637 // - Example: char dest[13]; or char dest[length];
638 auto DestArrayTyDecl = declRefExpr(
639 to(anyOf(varDecl(CharTyArray).bind(DestVarDeclName),
640 varDecl(hasInitializer(AnyOfDestInit)).bind(DestVarDeclName))));
641
642 // - Example: foo[bar[baz]].qux; (or just ParmVarDecl)
643 auto DestUnknownDecl =
644 declRefExpr(to(varDecl(AnyOfCharTy).bind(DestVarDeclName)),
645 expr().bind(UnknownDestName))
646 .bind(DestExprName);
647
648 auto AnyOfDestDecl = ignoringImpCasts(
649 anyOf(allOf(hasDefinition(anyOf(AnyOfDestInit, DestArrayTyDecl,
650 hasDescendant(DestArrayTyDecl))),
651 expr().bind(DestExprName)),
652 anyOf(DestUnknownDecl, hasDescendant(DestUnknownDecl))));
653
654 auto NullTerminatorExpr = binaryOperator(
655 hasLHS(anyOf(hasDescendant(declRefExpr(to(varDecl(
656 equalsBoundNode(std::string(DestVarDeclName)))))),
657 hasDescendant(declRefExpr(
658 equalsBoundNode(std::string(DestExprName)))))),
659 hasRHS(ignoringImpCasts(
660 anyOf(characterLiteral(equals(0U)), integerLiteral(equals(0))))));
661
662 auto SrcDecl =
663 declRefExpr(to(decl().bind(SrcVarDeclName)),
664 anyOf(hasAncestor(cxxMemberCallExpr().bind(SrcExprName)),
665 expr().bind(SrcExprName)));
666
667 auto AnyOfSrcDecl =
668 ignoringImpCasts(anyOf(stringLiteral().bind(SrcExprName),
669 hasDescendant(stringLiteral().bind(SrcExprName)),
670 SrcDecl, hasDescendant(SrcDecl)));
671
672 //===--------------------------------------------------------------------===//
673 // Match the problematic function calls.
674 //===--------------------------------------------------------------------===//
675
676 struct CallContext {
677 CallContext(StringRef Name, std::optional<unsigned> DestinationPos,
678 std::optional<unsigned> SourcePos, unsigned LengthPos,
679 bool WithIncrease)
680 : Name(Name), DestinationPos(DestinationPos), SourcePos(SourcePos),
681 LengthPos(LengthPos), WithIncrease(WithIncrease) {};
682
683 StringRef Name;
684 std::optional<unsigned> DestinationPos;
685 std::optional<unsigned> SourcePos;
686 unsigned LengthPos;
687 bool WithIncrease;
688 };
689
690 auto MatchDestination = [=](CallContext CC) {
691 return hasArgument(*CC.DestinationPos,
692 allOf(AnyOfDestDecl,
693 unless(hasAncestor(compoundStmt(
694 hasDescendant(NullTerminatorExpr)))),
695 unless(Container)));
696 };
697
698 auto MatchSource = [=](CallContext CC) {
699 return hasArgument(*CC.SourcePos, AnyOfSrcDecl);
700 };
701
702 auto MatchGivenLength = [=](CallContext CC) {
703 return hasArgument(
704 CC.LengthPos,
705 allOf(
706 anyOf(ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)),
707 allOf(unless(hasDefinition(SizeOfCharExpr)),
708 allOf(CC.WithIncrease
709 ? ignoringImpCasts(hasDefinition(HasIncOp))
710 : ignoringImpCasts(
711 allOf(unless(hasDefinition(HasIncOp)),
712 hasDefinition(optionally(
713 binaryOperator().bind(
715 AnyOfWrongLengthInit))),
716 expr().bind(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("mem") ? "w" + CC.Name.str()
725 : "wcs" + CC.Name.substr(3).str());
726
727 return allOf(callee(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(*CC.DestinationPos, anything()));
739
740 if (!CC.DestinationPos && CC.SourcePos)
741 return allOf(MatchCall(CC), MatchSource(CC),
742 hasArgument(*CC.SourcePos, 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(callExpr(AnyOfMatchers).bind(FunctionExprName), this);
776
777 // Need to remove the CastExpr from 'memchr()' as 'strchr()' returns 'char *'.
778 Finder->addMatcher(
779 callExpr(Memchr,
780 unless(hasAncestor(castExpr(unless(implicitCastExpr())))))
781 .bind(FunctionExprName),
782 this);
783 Finder->addMatcher(
784 castExpr(allOf(unless(implicitCastExpr()),
785 has(callExpr(Memchr).bind(FunctionExprName))))
786 .bind(CastExprName),
787 this);
788}
789
791 const MatchFinder::MatchResult &Result) {
792 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
793 if (FunctionExpr->getBeginLoc().isMacroID())
794 return;
795
796 if (WantToUseSafeFunctions && PP->isMacroDefined("__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(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(10, 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("mem") || Name.starts_with("wmem"))
824 memoryHandlerFunctionFix(Name, Result);
825 else if (Name == "strerror_s")
826 strerror_sFix(Result);
827 else if (Name.ends_with("ncmp"))
828 ncmpFix(Name, Result);
829 else if (Name.ends_with("xfrm"))
830 xfrmFix(Name, Result);
831}
832
833void NotNullTerminatedResultCheck::memoryHandlerFunctionFix(
834 StringRef Name, const MatchFinder::MatchResult &Result) {
835 if (isCorrectGivenLength(Result))
836 return;
837
838 if (Name.ends_with("chr")) {
839 memchrFix(Name, Result);
840 return;
841 }
842
843 if ((Name.contains("cpy") || Name.contains("move")) &&
845 return;
846
847 auto Diag =
848 diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),
849 "the result from calling '%0' is not null-terminated")
850 << Name;
851
852 if (Name.ends_with("cpy")) {
853 memcpyFix(Name, Result, Diag);
854 } else if (Name.ends_with("cpy_s")) {
855 memcpy_sFix(Name, Result, Diag);
856 } else if (Name.ends_with("move")) {
857 memmoveFix(Name, Result, Diag);
858 } else if (Name.ends_with("move_s")) {
859 isDestCapacityFix(Result, Diag);
861 }
862}
863
864void 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 =
872
873 bool IsSafe = UseSafeFunctions && IsOverflows && isKnownDest(Result) &&
875
876 bool IsDestLengthNotRequired =
877 IsSafe && getLangOpts().CPlusPlus &&
878 Result.Nodes.getNodeAs<ArrayType>(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(2, Result, Diag);
887
888 if (!IsCopy && !IsSafe)
889 insertNullTerminatorExpr(Name, Result, Diag);
890}
891
892void 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>(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(1, Result, Diag);
908 else if (IsOverflows && isKnownDest(Result))
910
911 if (IsCopy)
912 removeArg(3, Result, Diag);
913
914 if (!IsCopy && !IsSafe)
915 insertNullTerminatorExpr(Name, Result, Diag);
916}
917
918void NotNullTerminatedResultCheck::memchrFix(
919 StringRef Name, const MatchFinder::MatchResult &Result) {
920 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
921 if (const auto *GivenCL = dyn_cast<CharacterLiteral>(FunctionExpr->getArg(1)))
922 if (GivenCL->getValue() != 0)
923 return;
924
925 auto Diag = diag(FunctionExpr->getArg(2)->IgnoreParenCasts()->getBeginLoc(),
926 "the length is too short to include the null terminator");
927
928 if (const auto *CastExpr = Result.Nodes.getNodeAs<Expr>(CastExprName)) {
929 const auto CastRemoveFix = FixItHint::CreateRemoval(
930 SourceRange(CastExpr->getBeginLoc(),
931 FunctionExpr->getBeginLoc().getLocWithOffset(-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
940void 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((Name[0] != 'w') ? "memmove_s" : "wmemmove_s", Result, Diag);
947 insertDestCapacityArg(IsOverflows, Name, Result, Diag);
948 }
949
951}
952
953void NotNullTerminatedResultCheck::strerror_sFix(
954 const MatchFinder::MatchResult &Result) {
955 auto Diag =
956 diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),
957 "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);
962}
963
964void NotNullTerminatedResultCheck::ncmpFix(
965 StringRef Name, const MatchFinder::MatchResult &Result) {
966 const auto *FunctionExpr = Result.Nodes.getNodeAs<CallExpr>(FunctionExprName);
967 const Expr *FirstArgExpr = FunctionExpr->getArg(0)->IgnoreImpCasts();
968 const Expr *SecondArgExpr = FunctionExpr->getArg(1)->IgnoreImpCasts();
969 bool IsLengthTooLong = false;
970
971 if (const CallExpr *StrlenExpr = getStrlenExpr(Result)) {
972 const Expr *LengthExprArg = StrlenExpr->getArg(0);
973 StringRef FirstExprStr = exprToStr(FirstArgExpr, Result).trim();
974 StringRef SecondExprStr = exprToStr(SecondArgExpr, Result).trim();
975 StringRef LengthArgStr = exprToStr(LengthExprArg, Result).trim();
976 IsLengthTooLong =
977 LengthArgStr == FirstExprStr || LengthArgStr == SecondExprStr;
978 } else {
979 int SrcLength =
980 getLength(Result.Nodes.getNodeAs<Expr>(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(2)->IgnoreParenCasts()->getBeginLoc(),
990 "comparison length is too long and might lead to a "
991 "buffer overflow");
992
994}
995
996void NotNullTerminatedResultCheck::xfrmFix(
997 StringRef Name, const MatchFinder::MatchResult &Result) {
998 if (!isDestCapacityOverflows(Result))
999 return;
1000
1001 auto Diag =
1002 diag(Result.Nodes.getNodeAs<CallExpr>(FunctionExprName)->getBeginLoc(),
1003 "the result from calling '%0' is not null-terminated")
1004 << Name;
1005
1006 isDestCapacityFix(Result, Diag);
1008}
1009
1010} // 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
constexpr llvm::StringLiteral CastExprName
static bool isStringDataAndLength(const MatchFinder::MatchResult &Result)
static bool isCorrectGivenLength(const MatchFinder::MatchResult &Result)
static const CallExpr * getStrlenExpr(const MatchFinder::MatchResult &Result)
static void renameFunc(StringRef NewFuncName, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static bool isKnownDest(const MatchFinder::MatchResult &Result)
static bool isDestAndSrcEquals(const MatchFinder::MatchResult &Result)
static bool isDestExprFix(const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
constexpr llvm::StringLiteral FunctionExprName
static SourceLocation exprLocEnd(const Expr *E, const MatchFinder::MatchResult &Result)
constexpr llvm::StringLiteral UnknownLengthName
static int getGivenLength(const MatchFinder::MatchResult &Result)
constexpr llvm::StringLiteral WrongLengthExprName
constexpr llvm::StringLiteral LengthExprName
static bool isDestBasedOnGivenLength(const MatchFinder::MatchResult &Result)
constexpr llvm::StringLiteral DestMallocExprName
constexpr llvm::StringLiteral DestArrayTyName
constexpr llvm::StringLiteral UnknownDestName
static const Expr * getDestCapacityExpr(const MatchFinder::MatchResult &Result)
static bool isGivenLengthEqualToSrcLength(const MatchFinder::MatchResult &Result)
constexpr llvm::StringLiteral SrcVarDeclName
static int getDestCapacity(const MatchFinder::MatchResult &Result)
static bool isDestCapacityOverflows(const MatchFinder::MatchResult &Result)
static bool isInjectUL(const MatchFinder::MatchResult &Result)
static bool isFixedGivenLengthAndUnknownSrc(const MatchFinder::MatchResult &Result)
static void lengthExprHandle(const Expr *LengthExpr, LengthHandleKind LengthHandle, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static void lengthArgHandle(LengthHandleKind LengthHandle, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static void lengthArgPosHandle(unsigned ArgPos, LengthHandleKind LengthHandle, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static bool isDestCapacityFix(const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static StringRef exprToStr(const Expr *E, const MatchFinder::MatchResult &Result)
static void insertNullTerminatorExpr(StringRef Name, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
constexpr llvm::StringLiteral DestVarDeclName
static void removeArg(int ArgPos, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static unsigned getLength(const Expr *E, const MatchFinder::MatchResult &Result)
static void insertDestCapacityArg(bool IsOverflows, StringRef Name, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
static void renameMemcpy(StringRef Name, bool IsCopy, bool IsSafe, const MatchFinder::MatchResult &Result, DiagnosticBuilder &Diag)
constexpr llvm::StringLiteral SrcExprName
constexpr llvm::StringLiteral DestExprName
llvm::StringMap< ClangTidyValue > OptionMap