1 //===--- StandaloneEmptyCheck.cpp - clang-tidy ----------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
27 namespace clang::tidy::bugprone
{
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
;
50 const Expr
*getCondition(const BoundNodes
&Nodes
, const StringRef NodeId
) {
51 const auto *If
= Nodes
.getNodeAs
<IfStmt
>(NodeId
);
55 const auto *For
= Nodes
.getNodeAs
<ForStmt
>(NodeId
);
57 return For
->getCond();
59 const auto *While
= Nodes
.getNodeAs
<WhileStmt
>(NodeId
);
61 return While
->getCond();
63 const auto *Do
= Nodes
.getNodeAs
<DoStmt
>(NodeId
);
67 const auto *Switch
= Nodes
.getNodeAs
<SwitchStmt
>(NodeId
);
68 if (Switch
!= nullptr)
69 return Switch
->getCond();
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(
79 hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
81 unless(hasAncestor(classTemplateDecl())),
82 callee(functionDecl(hasName("empty"), unless(returns(voidType())))))
84 const auto MemberMatcher
=
85 expr(ignoringImplicit(ignoringParenImpCasts(cxxMemberCallExpr(
86 hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
88 callee(cxxMethodDecl(hasName("empty"),
89 unless(returns(voidType()))))))))
92 Finder
->addMatcher(MemberMatcher
, this);
93 Finder
->addMatcher(NonMemberMatcher
, this);
96 void StandaloneEmptyCheck::check(const MatchFinder::MatchResult
&Result
) {
97 // Skip if the parent node is Expr.
98 if (Result
.Nodes
.getNodeAs
<Expr
>("parent"))
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");
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())
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())
116 // Skip if it's a return statement
117 if (ParentReturnStmt
)
120 SourceLocation MemberLoc
= MemberCall
->getBeginLoc();
121 SourceLocation ReplacementLoc
= MemberCall
->getExprLoc();
122 SourceRange ReplacementRange
= SourceRange(ReplacementLoc
, ReplacementLoc
);
124 ASTContext
&Context
= MemberCall
->getRecordDecl()->getASTContext();
125 DeclarationName Name
=
126 Context
.DeclarationNames
.getIdentifier(&Context
.Idents
.get("clear"));
128 auto Candidates
= MemberCall
->getRecordDecl()->lookupDependentName(
129 Name
, [](const NamedDecl
*ND
) {
130 return isa
<CXXMethodDecl
>(ND
) &&
131 llvm::cast
<CXXMethodDecl
>(ND
)->getMinRequiredArguments() ==
133 !llvm::cast
<CXXMethodDecl
>(ND
)->isConst();
136 bool HasClear
= !Candidates
.empty();
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
) {
145 "ignoring the result of 'empty()'; did you mean 'clear()'? ")
146 << FixItHint::CreateReplacement(ReplacementRange
, "clear");
151 diag(MemberLoc
, "ignoring the result of 'empty()'");
153 } else if (const auto *NonMemberCall
=
154 Result
.Nodes
.getNodeAs
<CallExpr
>("empty")) {
155 if (ParentCond
== NonMemberCall
->getExprStmt())
157 if (PParentStmtExpr
&& ParentCompStmt
&&
158 ParentCompStmt
->body_back() == NonMemberCall
->getExprStmt())
160 if (ParentReturnStmt
)
162 if (NonMemberCall
->getNumArgs() != 1)
165 SourceLocation NonMemberLoc
= NonMemberCall
->getExprLoc();
166 SourceLocation NonMemberEndLoc
= NonMemberCall
->getEndLoc();
168 const Expr
*Arg
= NonMemberCall
->getArg(0);
169 CXXRecordDecl
*ArgRecordDecl
= Arg
->getType()->getAsCXXRecordDecl();
170 if (ArgRecordDecl
== nullptr)
173 ASTContext
&Context
= ArgRecordDecl
->getASTContext();
174 DeclarationName Name
=
175 Context
.DeclarationNames
.getIdentifier(&Context
.Idents
.get("clear"));
178 ArgRecordDecl
->lookupDependentName(Name
, [](const NamedDecl
*ND
) {
179 return isa
<CXXMethodDecl
>(ND
) &&
180 llvm::cast
<CXXMethodDecl
>(ND
)->getMinRequiredArguments() ==
182 !llvm::cast
<CXXMethodDecl
>(ND
)->isConst();
185 bool HasClear
= !Candidates
.empty();
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())) +
198 SourceRange ReplacementRange
=
199 SourceRange(NonMemberLoc
, NonMemberEndLoc
);
201 "ignoring the result of '%0'; did you mean 'clear()'?")
202 << llvm::dyn_cast
<NamedDecl
>(NonMemberCall
->getCalleeDecl())
203 ->getQualifiedNameAsString()
204 << FixItHint::CreateReplacement(ReplacementRange
, ReplacementText
);
209 diag(NonMemberLoc
, "ignoring the result of '%0'")
210 << llvm::dyn_cast
<NamedDecl
>(NonMemberCall
->getCalleeDecl())
211 ->getQualifiedNameAsString();
215 } // namespace clang::tidy::bugprone