9#include "../utils/ASTUtils.h"
10#include "../utils/Matchers.h"
11#include "../utils/OptionsUtils.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Lex/Lexer.h"
15#include "llvm/ADT/StringRef.h"
20namespace ast_matchers {
23 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
25 internal::Matcher<Expr>, ArgMatcher,
26 internal::Matcher<ParmVarDecl>, ParamMatcher) {
27 BoundNodesTreeBuilder Result;
31 BoundNodesTreeBuilder Matches;
32 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
33 .matches(
Node, Finder, &Matches)
37 for (; ArgIndex <
Node.getNumArgs(); ++ArgIndex) {
38 BoundNodesTreeBuilder ArgMatches(*
Builder);
39 if (ArgMatcher.matches(*(
Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
41 BoundNodesTreeBuilder ParamMatches(ArgMatches);
42 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
43 hasParameter(ParamIndex, ParamMatcher)))),
44 callExpr(callee(functionDecl(
45 hasParameter(ParamIndex, ParamMatcher))))))
46 .matches(
Node, Finder, &ParamMatches)) {
47 Result.addMatch(ParamMatches);
58 const char *ExprName =
"__booleanContextExpr";
60 expr(expr().bind(ExprName),
62 mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),
63 hasParent(cxxConstructorDecl(
64 hasAnyConstructorInitializer(cxxCtorInitializer(
65 withInitializer(expr(equalsBoundNode(ExprName))),
66 forField(hasType(booleanType())))))),
68 explicitCastExpr(hasDestinationType(booleanType())),
69 mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,
71 .with(hasCondition(expr(equalsBoundNode(ExprName)))),
72 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
74 explicitCastExpr(hasDestinationType(booleanType())))),
75 returnStmt(forFunction(returns(booleanType()))),
76 cxxUnresolvedConstructExpr(hasType(booleanType())),
77 invocation(hasAnyArgumentWithParam(
78 expr(equalsBoundNode(ExprName)),
79 parmVarDecl(hasType(booleanType())))),
80 binaryOperator(hasAnyOperatorName(
"&&",
"||")),
81 unaryOperator(hasOperatorName(
"!")).bind(
"NegOnSize"))))))
83 Builder->removeBindings([ExprName](
const BoundNodesMap &Nodes) {
84 return Nodes.getNode(ExprName).getNodeKind().isNone();
90 return Node.getConstructor()->isDefaultConstructor();
94 return Node->isIntegralType(Finder->getASTContext());
98 clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
99 if (
const Expr *CookedLiteral =
Node.getCookedLiteral()) {
100 return InnerMatcher.matches(*CookedLiteral, Finder,
Builder);
106namespace tidy::readability {
113 ExcludedComparisonTypes(utils::options::parseStringList(
114 Options.get(
"ExcludedComparisonTypes",
"::std::array"))) {}
122 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
123 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
124 hasAnyName(
"size",
"length"),
125 returns(qualType(isIntegralType(),
126 unless(booleanType()))))
128 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
129 hasName(
"empty"), returns(booleanType()))
131 .bind(
"container")));
133 const auto ValidContainerNonTemplateType =
134 qualType(hasUnqualifiedDesugaredType(
135 recordType(hasDeclaration(ValidContainerRecord))));
136 const auto ValidContainerTemplateType =
137 qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
138 hasDeclaration(classTemplateDecl(has(ValidContainerRecord))))));
140 const auto ValidContainer = qualType(
141 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
143 const auto WrongUse =
144 anyOf(hasParent(binaryOperator(
145 isComparisonOperator(),
146 hasEitherOperand(anyOf(integerLiteral(equals(1)),
147 integerLiteral(equals(0)))))
148 .bind(
"SizeBinaryOp")),
149 usedInBooleanContext());
153 on(expr(anyOf(hasType(ValidContainer),
154 hasType(pointsTo(ValidContainer)),
155 hasType(references(ValidContainer))))
156 .bind(
"MemberCallObject")),
158 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
161 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
162 .bind(
"SizeCallExpr"),
166 callExpr(has(cxxDependentScopeMemberExpr(
168 expr(anyOf(hasType(ValidContainer),
169 hasType(pointsTo(ValidContainer)),
170 hasType(references(ValidContainer))))
171 .bind(
"MemberCallObject")),
172 anyOf(hasMemberName(
"size"), hasMemberName(
"length")))
173 .bind(
"DependentExpr")),
176 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
177 .bind(
"SizeCallExpr"),
181 const auto WrongComparend =
182 anyOf(stringLiteral(hasSize(0)),
183 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
184 cxxConstructExpr(isDefaultConstruction()),
185 cxxUnresolvedConstructExpr(argumentCountIs(0)));
189 hasOperatorName(
"*"),
191 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
192 expr(hasType(ValidContainer)).bind(
"STLObject"));
194 const auto ExcludedComparisonTypesMatcher = qualType(anyOf(
198 hasCanonicalType(hasDeclaration(
200 .bind(
"excluded")))));
201 const auto SameExcludedComparisonTypesMatcher =
202 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
203 hasCanonicalType(hasDeclaration(
204 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
208 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
209 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
210 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
212 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
218 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
219 const auto *MemberCallObject =
220 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
221 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
222 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
223 const auto *BinCmpRewritten =
224 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
225 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
226 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
230 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
232 std::string ReplacementText = std::string(
233 Lexer::getSourceText(CharSourceRange::getTokenRange(
E->getSourceRange()),
235 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(
E);
236 if (isBinaryOrTernary(
E) || isa<UnaryOperator>(
E) ||
237 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
238 ReplacementText =
"(" + ReplacementText +
")";
241 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
244 ReplacementText +=
"empty()";
245 }
else if (
E->getType()->isPointerType())
246 ReplacementText +=
"->empty()";
248 ReplacementText +=
".empty()";
251 if (BinCmp->getOperator() == OO_ExclaimEqual) {
252 ReplacementText =
"!" + ReplacementText;
255 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
256 }
else if (BinCmpTempl) {
257 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
258 ReplacementText =
"!" + ReplacementText;
260 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
262 }
else if (BinCmpRewritten) {
263 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {
264 ReplacementText =
"!" + ReplacementText;
266 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
268 }
else if (BinaryOp) {
269 const auto *LiteralLHS =
270 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
271 const auto *LiteralRHS =
272 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
273 const bool ContainerIsLHS = !LiteralLHS;
277 Value = LiteralLHS->getValue().getLimitedValue();
279 Value = LiteralRHS->getValue().getLimitedValue();
283 bool Negation =
false;
284 const auto OpCode = BinaryOp->getOpcode();
290 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
291 OpCode == BinaryOperatorKind::BO_NE))
295 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
296 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
301 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
302 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
304 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
305 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
309 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
311 if ((OpCode == BinaryOperatorKind::BO_GT ||
312 OpCode == BinaryOperatorKind::BO_GE) &&
315 if ((OpCode == BinaryOperatorKind::BO_LT ||
316 OpCode == BinaryOperatorKind::BO_LE) &&
321 ReplacementText =
"!" + ReplacementText;
322 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
328 if (
const auto *UnaryOp =
329 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
330 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
333 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
334 "!" + ReplacementText);
337 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
339 if (WarnLoc.isValid()) {
340 auto Diag =
diag(WarnLoc,
"the 'empty' method should be used to check "
341 "for emptiness instead of %0");
342 if (
const auto *SizeMethod =
343 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
345 else if (
const auto *DependentExpr =
346 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
348 Diag << DependentExpr->getMember();
350 Diag <<
"unknown method";
353 WarnLoc = BinCmpTempl
354 ? BinCmpTempl->getBeginLoc()
355 : (BinCmp ? BinCmp->getBeginLoc()
356 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
357 : SourceLocation{}));
358 diag(WarnLoc,
"the 'empty' method should be used to check "
359 "for emptiness instead of comparing to an empty object")
363 const auto *
Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
364 if (
const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(
Container)) {
369 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
370 Container = CTS->getSpecializedTemplate();
372 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
374 diag(Empty->getLocation(),
"method %0::empty() defined here",
CodeCompletionBuilder Builder
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
AST_MATCHER(Decl, declHasNoReturnAttr)
matches a Decl if it has a "no return" attribute of any kind
AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam, AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, CXXConstructExpr), internal::Matcher< Expr >, ArgMatcher, internal::Matcher< ParmVarDecl >, ParamMatcher)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
bool isBinaryOrTernary(const Expr *E)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::StringMap< ClangTidyValue > OptionMap