clang-tools 22.0.0git
StandaloneEmptyCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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 "clang/Sema/HeuristicResolver.h"
24#include "llvm/Support/Casting.h"
25
26namespace clang::tidy::bugprone {
27
28using ast_matchers::BoundNodes;
29using ast_matchers::callee;
30using ast_matchers::callExpr;
31using ast_matchers::classTemplateDecl;
32using ast_matchers::cxxMemberCallExpr;
33using ast_matchers::cxxMethodDecl;
34using ast_matchers::expr;
35using ast_matchers::functionDecl;
36using ast_matchers::hasAncestor;
37using ast_matchers::hasName;
38using ast_matchers::hasParent;
39using ast_matchers::ignoringImplicit;
40using ast_matchers::ignoringParenImpCasts;
41using ast_matchers::MatchFinder;
42using ast_matchers::optionally;
43using ast_matchers::returns;
44using ast_matchers::stmt;
45using ast_matchers::stmtExpr;
46using ast_matchers::unless;
47using ast_matchers::voidType;
48
49static const Expr *getCondition(const BoundNodes &Nodes,
50 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
74void 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
96void 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 const SourceLocation MemberLoc = MemberCall->getBeginLoc();
121 const SourceLocation ReplacementLoc = MemberCall->getExprLoc();
122 const SourceRange ReplacementRange =
123 SourceRange(ReplacementLoc, ReplacementLoc);
124
125 ASTContext &Context = MemberCall->getRecordDecl()->getASTContext();
126 const DeclarationName Name =
127 Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear"));
128
129 auto Candidates = HeuristicResolver(Context).lookupDependentName(
130 MemberCall->getRecordDecl(), Name, [](const NamedDecl *ND) {
131 return isa<CXXMethodDecl>(ND) &&
132 llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==
133 0 &&
134 !llvm::cast<CXXMethodDecl>(ND)->isConst();
135 });
136
137 const bool HasClear = !Candidates.empty();
138 if (HasClear) {
139 const auto *Clear = llvm::cast<CXXMethodDecl>(Candidates.at(0));
140 const QualType RangeType =
141 MemberCall->getImplicitObjectArgument()->getType();
142 const bool QualifierIncompatible =
143 (!Clear->isVolatile() && RangeType.isVolatileQualified()) ||
144 RangeType.isConstQualified();
145 if (!QualifierIncompatible) {
146 diag(MemberLoc,
147 "ignoring the result of 'empty()'; did you mean 'clear()'? ")
148 << FixItHint::CreateReplacement(ReplacementRange, "clear");
149 return;
150 }
151 }
152
153 diag(MemberLoc, "ignoring the result of 'empty()'");
154
155 } else if (const auto *NonMemberCall =
156 Result.Nodes.getNodeAs<CallExpr>("empty")) {
157 if (ParentCond == NonMemberCall->getExprStmt())
158 return;
159 if (PParentStmtExpr && ParentCompStmt &&
160 ParentCompStmt->body_back() == NonMemberCall->getExprStmt())
161 return;
162 if (ParentReturnStmt)
163 return;
164 if (NonMemberCall->getNumArgs() != 1)
165 return;
166
167 const SourceLocation NonMemberLoc = NonMemberCall->getExprLoc();
168 const SourceLocation NonMemberEndLoc = NonMemberCall->getEndLoc();
169
170 const Expr *Arg = NonMemberCall->getArg(0);
171 CXXRecordDecl *ArgRecordDecl = Arg->getType()->getAsCXXRecordDecl();
172 if (ArgRecordDecl == nullptr)
173 return;
174
175 ASTContext &Context = ArgRecordDecl->getASTContext();
176 const DeclarationName Name =
177 Context.DeclarationNames.getIdentifier(&Context.Idents.get("clear"));
178
179 auto Candidates = HeuristicResolver(Context).lookupDependentName(
180 ArgRecordDecl, Name, [](const NamedDecl *ND) {
181 return isa<CXXMethodDecl>(ND) &&
182 llvm::cast<CXXMethodDecl>(ND)->getMinRequiredArguments() ==
183 0 &&
184 !llvm::cast<CXXMethodDecl>(ND)->isConst();
185 });
186
187 const bool HasClear = !Candidates.empty();
188
189 if (HasClear) {
190 const auto *Clear = llvm::cast<CXXMethodDecl>(Candidates.at(0));
191 const bool QualifierIncompatible =
192 (!Clear->isVolatile() && Arg->getType().isVolatileQualified()) ||
193 Arg->getType().isConstQualified();
194 if (!QualifierIncompatible) {
195 const std::string ReplacementText =
196 std::string(Lexer::getSourceText(
197 CharSourceRange::getTokenRange(Arg->getSourceRange()),
198 *Result.SourceManager, getLangOpts())) +
199 ".clear()";
200 const SourceRange ReplacementRange =
201 SourceRange(NonMemberLoc, NonMemberEndLoc);
202 diag(NonMemberLoc,
203 "ignoring the result of '%0'; did you mean 'clear()'?")
204 << llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())
205 ->getQualifiedNameAsString()
206 << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
207 return;
208 }
209 }
210
211 diag(NonMemberLoc, "ignoring the result of '%0'")
212 << llvm::dyn_cast<NamedDecl>(NonMemberCall->getCalleeDecl())
213 ->getQualifiedNameAsString();
214 }
215}
216
217} // namespace clang::tidy::bugprone
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static const Expr * getCondition(const BoundNodes &Nodes, const StringRef NodeId)