xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/SuspiciousReallocUsageCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
1 //===--- SuspiciousReallocUsageCheck.cpp - clang-tidy----------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "SuspiciousReallocUsageCheck.h"
10 #include "../utils/Aliasing.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/DeclVisitor.h"
13 #include "clang/AST/StmtVisitor.h"
14 #include "clang/ASTMatchers/ASTMatchFinder.h"
15 #include "clang/ASTMatchers/ASTMatchers.h"
16 #include "clang/Lex/Lexer.h"
17 
18 using namespace clang::ast_matchers;
19 using namespace clang;
20 
21 namespace {
22 /// Check if two different expression nodes denote the same
23 /// "pointer expression". The "pointer expression" can consist of member
24 /// expressions and declaration references only (like \c a->b->c), otherwise the
25 /// check is always false.
26 class IsSamePtrExpr : public StmtVisitor<IsSamePtrExpr, bool> {
27   /// The other expression to compare against.
28   /// This variable is used to pass the data from a \c check function to any of
29   /// the visit functions. Every visit function starts by converting \c OtherE
30   /// to the current type and store it locally, and do not use \c OtherE later.
31   const Expr *OtherE = nullptr;
32 
33 public:
VisitDeclRefExpr(const DeclRefExpr * E1)34   bool VisitDeclRefExpr(const DeclRefExpr *E1) {
35     const auto *E2 = dyn_cast<DeclRefExpr>(OtherE);
36     if (!E2)
37       return false;
38     const Decl *D1 = E1->getDecl()->getCanonicalDecl();
39     return isa<VarDecl, FieldDecl>(D1) &&
40            D1 == E2->getDecl()->getCanonicalDecl();
41   }
42 
VisitMemberExpr(const MemberExpr * E1)43   bool VisitMemberExpr(const MemberExpr *E1) {
44     const auto *E2 = dyn_cast<MemberExpr>(OtherE);
45     if (!E2)
46       return false;
47     if (!check(E1->getBase(), E2->getBase()))
48       return false;
49     DeclAccessPair FD = E1->getFoundDecl();
50     return isa<FieldDecl>(FD.getDecl()) && FD == E2->getFoundDecl();
51   }
52 
check(const Expr * E1,const Expr * E2)53   bool check(const Expr *E1, const Expr *E2) {
54     E1 = E1->IgnoreParenCasts();
55     E2 = E2->IgnoreParenCasts();
56     OtherE = E2;
57     return Visit(const_cast<Expr *>(E1));
58   }
59 };
60 
61 /// Check if there is an assignment or initialization that references a variable
62 /// \c Var (at right-hand side) and is before \c VarRef in the source code.
63 /// Only simple assignments like \code a = b \endcode are found.
64 class FindAssignToVarBefore
65     : public ConstStmtVisitor<FindAssignToVarBefore, bool> {
66   const VarDecl *Var;
67   const DeclRefExpr *VarRef;
68   SourceManager &SM;
69 
isAccessForVar(const Expr * E) const70   bool isAccessForVar(const Expr *E) const {
71     if (const auto *DeclRef = dyn_cast<DeclRefExpr>(E->IgnoreParenCasts()))
72       return DeclRef->getDecl() &&
73              DeclRef->getDecl()->getCanonicalDecl() == Var &&
74              SM.isBeforeInTranslationUnit(E->getBeginLoc(),
75                                           VarRef->getBeginLoc());
76     return false;
77   }
78 
79 public:
FindAssignToVarBefore(const VarDecl * Var,const DeclRefExpr * VarRef,SourceManager & SM)80   FindAssignToVarBefore(const VarDecl *Var, const DeclRefExpr *VarRef,
81                         SourceManager &SM)
82       : Var(Var->getCanonicalDecl()), VarRef(VarRef), SM(SM) {}
83 
VisitDeclStmt(const DeclStmt * S)84   bool VisitDeclStmt(const DeclStmt *S) {
85     for (const Decl *D : S->getDeclGroup())
86       if (const auto *LeftVar = dyn_cast<VarDecl>(D))
87         if (LeftVar->hasInit())
88           return isAccessForVar(LeftVar->getInit());
89     return false;
90   }
VisitBinaryOperator(const BinaryOperator * S)91   bool VisitBinaryOperator(const BinaryOperator *S) {
92     if (S->getOpcode() == BO_Assign)
93       return isAccessForVar(S->getRHS());
94     return false;
95   }
VisitStmt(const Stmt * S)96   bool VisitStmt(const Stmt *S) {
97     for (const Stmt *Child : S->children())
98       if (Child && Visit(Child))
99         return true;
100     return false;
101   }
102 };
103 
104 } // namespace
105 
106 namespace clang::tidy::bugprone {
107 
registerMatchers(MatchFinder * Finder)108 void SuspiciousReallocUsageCheck::registerMatchers(MatchFinder *Finder) {
109   // void *realloc(void *ptr, size_t size);
110   auto ReallocDecl =
111       functionDecl(hasName("::realloc"), parameterCountIs(2),
112                    hasParameter(0, hasType(pointerType(pointee(voidType())))),
113                    hasParameter(1, hasType(isInteger())))
114           .bind("realloc");
115 
116   auto ReallocCall =
117       callExpr(callee(ReallocDecl), hasArgument(0, expr().bind("ptr_input")),
118                hasAncestor(functionDecl().bind("parent_function")))
119           .bind("call");
120   Finder->addMatcher(binaryOperator(hasOperatorName("="),
121                                     hasLHS(expr().bind("ptr_result")),
122                                     hasRHS(ignoringParenCasts(ReallocCall))),
123                      this);
124 }
125 
check(const MatchFinder::MatchResult & Result)126 void SuspiciousReallocUsageCheck::check(
127     const MatchFinder::MatchResult &Result) {
128   const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
129   if (!Call)
130     return;
131   const auto *PtrInputExpr = Result.Nodes.getNodeAs<Expr>("ptr_input");
132   const auto *PtrResultExpr = Result.Nodes.getNodeAs<Expr>("ptr_result");
133   if (!PtrInputExpr || !PtrResultExpr)
134     return;
135   const auto *ReallocD = Result.Nodes.getNodeAs<Decl>("realloc");
136   assert(ReallocD && "Value for 'realloc' should exist if 'call' was found.");
137   SourceManager &SM = ReallocD->getASTContext().getSourceManager();
138 
139   if (!IsSamePtrExpr{}.check(PtrInputExpr, PtrResultExpr))
140     return;
141 
142   if (const auto *DeclRef =
143           dyn_cast<DeclRefExpr>(PtrInputExpr->IgnoreParenImpCasts()))
144     if (const auto *Var = dyn_cast<VarDecl>(DeclRef->getDecl()))
145       if (const auto *Func =
146               Result.Nodes.getNodeAs<FunctionDecl>("parent_function"))
147         if (FindAssignToVarBefore{Var, DeclRef, SM}.Visit(Func->getBody()))
148           return;
149 
150   StringRef CodeOfAssignedExpr = Lexer::getSourceText(
151       CharSourceRange::getTokenRange(PtrResultExpr->getSourceRange()), SM,
152       getLangOpts());
153   diag(Call->getBeginLoc(), "'%0' may be set to null if 'realloc' fails, which "
154                             "may result in a leak of the original buffer")
155       << CodeOfAssignedExpr << PtrInputExpr->getSourceRange()
156       << PtrResultExpr->getSourceRange();
157 }
158 
159 } // namespace clang::tidy::bugprone
160