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 | |
50 | const Expr *getCondition(const BoundNodes &Nodes, const StringRef NodeId) { |
51 | const auto *If = Nodes.getNodeAs<IfStmt>(ID: NodeId); |
52 | if (If != nullptr) |
53 | return If->getCond(); |
54 | |
55 | const auto *For = Nodes.getNodeAs<ForStmt>(ID: NodeId); |
56 | if (For != nullptr) |
57 | return For->getCond(); |
58 | |
59 | const auto *While = Nodes.getNodeAs<WhileStmt>(ID: NodeId); |
60 | if (While != nullptr) |
61 | return While->getCond(); |
62 | |
63 | const auto *Do = Nodes.getNodeAs<DoStmt>(ID: NodeId); |
64 | if (Do != nullptr) |
65 | return Do->getCond(); |
66 | |
67 | const auto *Switch = Nodes.getNodeAs<SwitchStmt>(ID: NodeId); |
68 | if (Switch != nullptr) |
69 | return Switch->getCond(); |
70 | |
71 | return nullptr; |
72 | } |
73 | |
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(InnerMatcher: ignoringParenImpCasts( |
78 | InnerMatcher: callExpr( |
79 | hasParent(stmt(optionally(hasParent(stmtExpr().bind(ID: "stexpr" )))) |
80 | .bind(ID: "parent" )), |
81 | unless(hasAncestor(classTemplateDecl())), |
82 | callee(InnerMatcher: functionDecl(hasName(Name: "empty" ), unless(returns(InnerMatcher: voidType()))))) |
83 | .bind(ID: "empty" )))); |
84 | const auto MemberMatcher = |
85 | expr(ignoringImplicit(InnerMatcher: ignoringParenImpCasts(InnerMatcher: cxxMemberCallExpr( |
86 | hasParent(stmt(optionally(hasParent(stmtExpr().bind(ID: "stexpr" )))) |
87 | .bind(ID: "parent" )), |
88 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "empty" ), |
89 | unless(returns(InnerMatcher: voidType())))))))) |
90 | .bind(ID: "empty" ); |
91 | |
92 | Finder->addMatcher(NodeMatch: MemberMatcher, Action: this); |
93 | Finder->addMatcher(NodeMatch: NonMemberMatcher, Action: this); |
94 | } |
95 | |
96 | void StandaloneEmptyCheck::check(const MatchFinder::MatchResult &Result) { |
97 | // Skip if the parent node is Expr. |
98 | if (Result.Nodes.getNodeAs<Expr>(ID: "parent" )) |
99 | return; |
100 | |
101 | const auto PParentStmtExpr = Result.Nodes.getNodeAs<Expr>(ID: "stexpr" ); |
102 | const auto ParentCompStmt = Result.Nodes.getNodeAs<CompoundStmt>(ID: "parent" ); |
103 | const auto *ParentCond = getCondition(Nodes: Result.Nodes, NodeId: "parent" ); |
104 | const auto *ParentReturnStmt = Result.Nodes.getNodeAs<ReturnStmt>(ID: "parent" ); |
105 | |
106 | if (const auto *MemberCall = |
107 | Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: "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(ID: &Context.Idents.get(Name: "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(Loc: MemberLoc, |
145 | Description: "ignoring the result of 'empty()'; did you mean 'clear()'? " ) |
146 | << FixItHint::CreateReplacement(RemoveRange: ReplacementRange, Code: "clear" ); |
147 | return; |
148 | } |
149 | } |
150 | |
151 | diag(Loc: MemberLoc, Description: "ignoring the result of 'empty()'" ); |
152 | |
153 | } else if (const auto *NonMemberCall = |
154 | Result.Nodes.getNodeAs<CallExpr>(ID: "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(Arg: 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(ID: &Context.Idents.get(Name: "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 | Range: CharSourceRange::getTokenRange(Arg->getSourceRange()), |
196 | SM: *Result.SourceManager, LangOpts: getLangOpts())) + |
197 | ".clear()" ; |
198 | SourceRange ReplacementRange = |
199 | SourceRange(NonMemberLoc, NonMemberEndLoc); |
200 | diag(Loc: NonMemberLoc, |
201 | Description: "ignoring the result of '%0'; did you mean 'clear()'?" ) |
202 | << llvm::dyn_cast<NamedDecl>(Val: NonMemberCall->getCalleeDecl()) |
203 | ->getQualifiedNameAsString() |
204 | << FixItHint::CreateReplacement(RemoveRange: ReplacementRange, Code: ReplacementText); |
205 | return; |
206 | } |
207 | } |
208 | |
209 | diag(Loc: NonMemberLoc, Description: "ignoring the result of '%0'" ) |
210 | << llvm::dyn_cast<NamedDecl>(Val: NonMemberCall->getCalleeDecl()) |
211 | ->getQualifiedNameAsString(); |
212 | } |
213 | } |
214 | |
215 | } // namespace clang::tidy::bugprone |
216 | |