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