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: |
34 | bool VisitDeclRefExpr(const DeclRefExpr *E1) { |
35 | const auto *E2 = dyn_cast<DeclRefExpr>(Val: OtherE); |
36 | if (!E2) |
37 | return false; |
38 | const Decl *D1 = E1->getDecl()->getCanonicalDecl(); |
39 | return isa<VarDecl, FieldDecl>(Val: D1) && |
40 | D1 == E2->getDecl()->getCanonicalDecl(); |
41 | } |
42 | |
43 | bool VisitMemberExpr(const MemberExpr *E1) { |
44 | const auto *E2 = dyn_cast<MemberExpr>(Val: OtherE); |
45 | if (!E2) |
46 | return false; |
47 | if (!check(E1: E1->getBase(), E2: E2->getBase())) |
48 | return false; |
49 | DeclAccessPair FD = E1->getFoundDecl(); |
50 | return isa<FieldDecl>(Val: FD.getDecl()) && FD == E2->getFoundDecl(); |
51 | } |
52 | |
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 | |
70 | bool isAccessForVar(const Expr *E) const { |
71 | if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Val: E->IgnoreParenCasts())) |
72 | return DeclRef->getDecl() && |
73 | DeclRef->getDecl()->getCanonicalDecl() == Var && |
74 | SM.isBeforeInTranslationUnit(LHS: E->getBeginLoc(), |
75 | RHS: VarRef->getBeginLoc()); |
76 | return false; |
77 | } |
78 | |
79 | public: |
80 | FindAssignToVarBefore(const VarDecl *Var, const DeclRefExpr *VarRef, |
81 | SourceManager &SM) |
82 | : Var(Var->getCanonicalDecl()), VarRef(VarRef), SM(SM) {} |
83 | |
84 | bool VisitDeclStmt(const DeclStmt *S) { |
85 | for (const Decl *D : S->getDeclGroup()) |
86 | if (const auto *LeftVar = dyn_cast<VarDecl>(Val: D)) |
87 | if (LeftVar->hasInit()) |
88 | return isAccessForVar(E: LeftVar->getInit()); |
89 | return false; |
90 | } |
91 | bool VisitBinaryOperator(const BinaryOperator *S) { |
92 | if (S->getOpcode() == BO_Assign) |
93 | return isAccessForVar(E: S->getRHS()); |
94 | return false; |
95 | } |
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 | |
108 | void SuspiciousReallocUsageCheck::registerMatchers(MatchFinder *Finder) { |
109 | // void *realloc(void *ptr, size_t size); |
110 | auto ReallocDecl = |
111 | functionDecl(hasName(Name: "::realloc" ), parameterCountIs(N: 2), |
112 | hasParameter(N: 0, InnerMatcher: hasType(InnerMatcher: pointerType(pointee(voidType())))), |
113 | hasParameter(N: 1, InnerMatcher: hasType(InnerMatcher: isInteger()))) |
114 | .bind(ID: "realloc" ); |
115 | |
116 | auto ReallocCall = |
117 | callExpr(callee(InnerMatcher: ReallocDecl), hasArgument(N: 0, InnerMatcher: expr().bind(ID: "ptr_input" )), |
118 | hasAncestor(functionDecl().bind(ID: "parent_function" ))) |
119 | .bind(ID: "call" ); |
120 | Finder->addMatcher(NodeMatch: binaryOperator(hasOperatorName(Name: "=" ), |
121 | hasLHS(InnerMatcher: expr().bind(ID: "ptr_result" )), |
122 | hasRHS(InnerMatcher: ignoringParenCasts(InnerMatcher: ReallocCall))), |
123 | Action: this); |
124 | } |
125 | |
126 | void SuspiciousReallocUsageCheck::check( |
127 | const MatchFinder::MatchResult &Result) { |
128 | const auto *Call = Result.Nodes.getNodeAs<CallExpr>(ID: "call" ); |
129 | if (!Call) |
130 | return; |
131 | const auto *PtrInputExpr = Result.Nodes.getNodeAs<Expr>(ID: "ptr_input" ); |
132 | const auto *PtrResultExpr = Result.Nodes.getNodeAs<Expr>(ID: "ptr_result" ); |
133 | if (!PtrInputExpr || !PtrResultExpr) |
134 | return; |
135 | const auto *ReallocD = Result.Nodes.getNodeAs<Decl>(ID: "realloc" ); |
136 | assert(ReallocD && "Value for 'realloc' should exist if 'call' was found." ); |
137 | SourceManager &SM = ReallocD->getASTContext().getSourceManager(); |
138 | |
139 | if (!IsSamePtrExpr{}.check(E1: PtrInputExpr, E2: PtrResultExpr)) |
140 | return; |
141 | |
142 | if (const auto *DeclRef = |
143 | dyn_cast<DeclRefExpr>(Val: PtrInputExpr->IgnoreParenImpCasts())) |
144 | if (const auto *Var = dyn_cast<VarDecl>(Val: DeclRef->getDecl())) |
145 | if (const auto *Func = |
146 | Result.Nodes.getNodeAs<FunctionDecl>(ID: "parent_function" )) |
147 | if (FindAssignToVarBefore{Var, DeclRef, SM}.Visit(Func->getBody())) |
148 | return; |
149 | |
150 | StringRef CodeOfAssignedExpr = Lexer::getSourceText( |
151 | Range: CharSourceRange::getTokenRange(PtrResultExpr->getSourceRange()), SM, |
152 | LangOpts: getLangOpts()); |
153 | diag(Loc: Call->getBeginLoc(), Description: "'%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 | |