| 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 | |