xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp (revision fc2a9ad10e21bda3dafbb85d8317ef5e3e5f99a1)
1 //===--- StandaloneEmptyCheck.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 "StandaloneEmptyCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/Decl.h"
12 #include "clang/AST/DeclBase.h"
13 #include "clang/AST/DeclCXX.h"
14 #include "clang/AST/Expr.h"
15 #include "clang/AST/ExprCXX.h"
16 #include "clang/AST/Stmt.h"
17 #include "clang/AST/Type.h"
18 #include "clang/ASTMatchers/ASTMatchFinder.h"
19 #include "clang/ASTMatchers/ASTMatchers.h"
20 #include "clang/Basic/Diagnostic.h"
21 #include "clang/Basic/SourceLocation.h"
22 #include "clang/Lex/Lexer.h"
23 #include "llvm/ADT/STLExtras.h"
24 #include "llvm/ADT/SmallVector.h"
25 #include "llvm/Support/Casting.h"
26 
27 namespace clang::tidy::bugprone {
28 
29 using ast_matchers::BoundNodes;
30 using ast_matchers::callee;
31 using ast_matchers::callExpr;
32 using ast_matchers::classTemplateDecl;
33 using ast_matchers::cxxMemberCallExpr;
34 using ast_matchers::cxxMethodDecl;
35 using ast_matchers::expr;
36 using ast_matchers::functionDecl;
37 using ast_matchers::hasAncestor;
38 using ast_matchers::hasName;
39 using ast_matchers::hasParent;
40 using ast_matchers::ignoringImplicit;
41 using ast_matchers::ignoringParenImpCasts;
42 using ast_matchers::MatchFinder;
43 using ast_matchers::optionally;
44 using ast_matchers::returns;
45 using ast_matchers::stmt;
46 using ast_matchers::stmtExpr;
47 using ast_matchers::unless;
48 using ast_matchers::voidType;
49 
getCondition(const BoundNodes & Nodes,const StringRef NodeId)50 const Expr *getCondition(const BoundNodes &Nodes, const StringRef NodeId) {
51   const auto *If = Nodes.getNodeAs<IfStmt>(NodeId);
52   if (If != nullptr)
53     return If->getCond();
54 
55   const auto *For = Nodes.getNodeAs<ForStmt>(NodeId);
56   if (For != nullptr)
57     return For->getCond();
58 
59   const auto *While = Nodes.getNodeAs<WhileStmt>(NodeId);
60   if (While != nullptr)
61     return While->getCond();
62 
63   const auto *Do = Nodes.getNodeAs<DoStmt>(NodeId);
64   if (Do != nullptr)
65     return Do->getCond();
66 
67   const auto *Switch = Nodes.getNodeAs<SwitchStmt>(NodeId);
68   if (Switch != nullptr)
69     return Switch->getCond();
70 
71   return nullptr;
72 }
73 
registerMatchers(ast_matchers::MatchFinder * Finder)74 void StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
75   // Ignore empty calls in a template definition which fall under callExpr
76   // non-member matcher even if they are methods.
77   const auto NonMemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts(
78       callExpr(
79           hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
80                         .bind("parent")),
81           unless(hasAncestor(classTemplateDecl())),
82           callee(functionDecl(hasName("empty"), unless(returns(voidType())))))
83           .bind("empty"))));
84   const auto MemberMatcher =
85       expr(ignoringImplicit(ignoringParenImpCasts(cxxMemberCallExpr(
86                hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
87                              .bind("parent")),
88                callee(cxxMethodDecl(hasName("empty"),
89                                     unless(returns(voidType()))))))))
90           .bind("empty");
91 
92   Finder->addMatcher(MemberMatcher, this);
93   Finder->addMatcher(NonMemberMatcher, this);
94 }
95 
check(const MatchFinder::MatchResult & Result)96 void StandaloneEmptyCheck::check(const MatchFinder::MatchResult &Result) {
97   // Skip if the parent node is Expr.
98   if (Result.Nodes.getNodeAs<Expr>("parent"))
99     return;
100 
101   const auto PParentStmtExpr = Result.Nodes.getNodeAs<Expr>("stexpr");
102   const auto ParentCompStmt = Result.Nodes.getNodeAs<CompoundStmt>("parent");
103   const auto *ParentCond = getCondition(Result.Nodes, "parent");
104   const auto *ParentReturnStmt = Result.Nodes.getNodeAs<ReturnStmt>("parent");
105 
106   if (const auto *MemberCall =
107           Result.Nodes.getNodeAs<CXXMemberCallExpr>("empty")) {
108     // Skip if it's a condition of the parent statement.
109     if (ParentCond == MemberCall->getExprStmt())
110       return;
111     // Skip if it's the last statement in the GNU extension
112     // statement expression.
113     if (PParentStmtExpr && ParentCompStmt &&
114         ParentCompStmt->body_back() == MemberCall->getExprStmt())
115       return;
116     // Skip if it's a return statement
117     if (ParentReturnStmt)
118       return;
119 
120     SourceLocation MemberLoc = MemberCall->getBeginLoc();
121     SourceLocation ReplacementLoc = MemberCall->getExprLoc();
122     SourceRange ReplacementRange = SourceRange(ReplacementLoc, ReplacementLoc);
123 
124     ASTContext &Context = MemberCall->getRecordDecl()->getASTContext();
125     DeclarationName Name =
126         Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear"));
127 
128     auto Candidates = MemberCall->getRecordDecl()->lookupDependentName(
129         Name, [](const NamedDecl *ND) {
130           return isa<CXXMethodDecl>(ND) &&
131                  llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==
132                      0 &&
133                  !llvm::cast<CXXMethodDecl>(ND)->isConst();
134         });
135 
136     bool HasClear = !Candidates.empty();
137     if (HasClear) {
138       const auto *Clear = llvm::cast<CXXMethodDecl>(Candidates.at(0));
139       QualType RangeType = MemberCall->getImplicitObjectArgument()->getType();
140       bool QualifierIncompatible =
141           (!Clear->isVolatile() && RangeType.isVolatileQualified()) ||
142           RangeType.isConstQualified();
143       if (!QualifierIncompatible) {
144         diag(MemberLoc,
145              "ignoring the result of 'empty()'; did you mean 'clear()'? ")
146             << FixItHint::CreateReplacement(ReplacementRange, "clear");
147         return;
148       }
149     }
150 
151     diag(MemberLoc, "ignoring the result of 'empty()'");
152 
153   } else if (const auto *NonMemberCall =
154                  Result.Nodes.getNodeAs<CallExpr>("empty")) {
155     if (ParentCond == NonMemberCall->getExprStmt())
156       return;
157     if (PParentStmtExpr && ParentCompStmt &&
158         ParentCompStmt->body_back() == NonMemberCall->getExprStmt())
159       return;
160     if (ParentReturnStmt)
161       return;
162     if (NonMemberCall->getNumArgs() != 1)
163       return;
164 
165     SourceLocation NonMemberLoc = NonMemberCall->getExprLoc();
166     SourceLocation NonMemberEndLoc = NonMemberCall->getEndLoc();
167 
168     const Expr *Arg = NonMemberCall->getArg(0);
169     CXXRecordDecl *ArgRecordDecl = Arg->getType()->getAsCXXRecordDecl();
170     if (ArgRecordDecl == nullptr)
171       return;
172 
173     ASTContext &Context = ArgRecordDecl->getASTContext();
174     DeclarationName Name =
175         Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear"));
176 
177     auto Candidates =
178         ArgRecordDecl->lookupDependentName(Name, [](const NamedDecl *ND) {
179           return isa<CXXMethodDecl>(ND) &&
180                  llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==
181                      0 &&
182                  !llvm::cast<CXXMethodDecl>(ND)->isConst();
183         });
184 
185     bool HasClear = !Candidates.empty();
186 
187     if (HasClear) {
188       const auto *Clear = llvm::cast<CXXMethodDecl>(Candidates.at(0));
189       bool QualifierIncompatible =
190           (!Clear->isVolatile() && Arg->getType().isVolatileQualified()) ||
191           Arg->getType().isConstQualified();
192       if (!QualifierIncompatible) {
193         std::string ReplacementText =
194             std::string(Lexer::getSourceText(
195                 CharSourceRange::getTokenRange(Arg->getSourceRange()),
196                 *Result.SourceManager, getLangOpts())) +
197             ".clear()";
198         SourceRange ReplacementRange =
199             SourceRange(NonMemberLoc, NonMemberEndLoc);
200         diag(NonMemberLoc,
201              "ignoring the result of '%0'; did you mean 'clear()'?")
202             << llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())
203                    ->getQualifiedNameAsString()
204             << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
205         return;
206       }
207     }
208 
209     diag(NonMemberLoc, "ignoring the result of '%0'")
210         << llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())
211                ->getQualifiedNameAsString();
212   }
213 }
214 
215 } // namespace clang::tidy::bugprone
216