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 const UserDefinedLiteral::LiteralOperatorKind LOK =
100 Node.getLiteralOperatorKind();
101 if (LOK == UserDefinedLiteral::LOK_Template ||
102 LOK == UserDefinedLiteral::LOK_Raw)
105 if (
const Expr *CookedLiteral =
Node.getCookedLiteral())
106 return InnerMatcher.matches(*CookedLiteral, Finder,
Builder);
111namespace tidy::readability {
118 ExcludedComparisonTypes(utils::options::parseStringList(
119 Options.get(
"ExcludedComparisonTypes",
"::std::array"))) {}
127 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
128 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
129 hasAnyName(
"size",
"length"),
130 returns(qualType(isIntegralType(),
131 unless(booleanType()))))
133 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
134 hasName(
"empty"), returns(booleanType()))
136 .bind(
"container")));
138 const auto ValidContainerNonTemplateType =
139 qualType(hasUnqualifiedDesugaredType(
140 recordType(hasDeclaration(ValidContainerRecord))));
141 const auto ValidContainerTemplateType =
142 qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
143 hasDeclaration(classTemplateDecl(has(ValidContainerRecord))))));
145 const auto ValidContainer = qualType(
146 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
148 const auto WrongUse =
149 anyOf(hasParent(binaryOperator(
150 isComparisonOperator(),
151 hasEitherOperand(anyOf(integerLiteral(equals(1)),
152 integerLiteral(equals(0)))))
153 .bind(
"SizeBinaryOp")),
154 usedInBooleanContext());
159 on(expr(anyOf(hasType(ValidContainer),
160 hasType(pointsTo(ValidContainer)),
161 hasType(references(ValidContainer))))
162 .bind(
"MemberCallObject")),
164 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
167 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
168 .bind(
"SizeCallExpr"),
172 callExpr(argumentCountIs(0),
173 has(cxxDependentScopeMemberExpr(
175 expr(anyOf(hasType(ValidContainer),
176 hasType(pointsTo(ValidContainer)),
177 hasType(references(ValidContainer))))
178 .bind(
"MemberCallObject")),
179 anyOf(hasMemberName(
"size"), hasMemberName(
"length")))
180 .bind(
"DependentExpr")),
183 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
184 .bind(
"SizeCallExpr"),
188 const auto WrongComparend =
189 anyOf(stringLiteral(hasSize(0)),
190 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
191 cxxConstructExpr(isDefaultConstruction()),
192 cxxUnresolvedConstructExpr(argumentCountIs(0)));
196 hasOperatorName(
"*"),
198 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
199 expr(hasType(ValidContainer)).bind(
"STLObject"));
201 const auto ExcludedComparisonTypesMatcher = qualType(anyOf(
205 hasCanonicalType(hasDeclaration(
207 .bind(
"excluded")))));
208 const auto SameExcludedComparisonTypesMatcher =
209 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
210 hasCanonicalType(hasDeclaration(
211 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
215 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
216 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
217 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
219 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
225 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
226 const auto *MemberCallObject =
227 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
228 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
229 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
230 const auto *BinCmpRewritten =
231 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
232 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
233 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
237 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
239 std::string ReplacementText = std::string(
240 Lexer::getSourceText(CharSourceRange::getTokenRange(
E->getSourceRange()),
242 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(
E);
243 if (isBinaryOrTernary(
E) || isa<UnaryOperator>(
E) ||
244 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
245 ReplacementText =
"(" + ReplacementText +
")";
248 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
251 ReplacementText +=
"empty()";
252 }
else if (
E->getType()->isPointerType())
253 ReplacementText +=
"->empty()";
255 ReplacementText +=
".empty()";
258 if (BinCmp->getOperator() == OO_ExclaimEqual) {
259 ReplacementText =
"!" + ReplacementText;
262 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
263 }
else if (BinCmpTempl) {
264 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
265 ReplacementText =
"!" + ReplacementText;
267 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
269 }
else if (BinCmpRewritten) {
270 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {
271 ReplacementText =
"!" + ReplacementText;
273 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
275 }
else if (BinaryOp) {
276 const auto *LiteralLHS =
277 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
278 const auto *LiteralRHS =
279 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
280 const bool ContainerIsLHS = !LiteralLHS;
284 Value = LiteralLHS->getValue().getLimitedValue();
286 Value = LiteralRHS->getValue().getLimitedValue();
290 bool Negation =
false;
291 const auto OpCode = BinaryOp->getOpcode();
297 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
298 OpCode == BinaryOperatorKind::BO_NE))
303 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
304 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
305 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
306 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
312 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
313 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
315 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
316 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
322 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
324 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
325 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
327 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
329 ? BinaryOp->getLHS()->IgnoreImpCasts()
330 : BinaryOp->getRHS()->IgnoreImpCasts();
333 .getNonReferenceType()
334 ->isSignedIntegerType())
338 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
341 if ((OpCode == BinaryOperatorKind::BO_GT ||
342 OpCode == BinaryOperatorKind::BO_GE) &&
346 if ((OpCode == BinaryOperatorKind::BO_LT ||
347 OpCode == BinaryOperatorKind::BO_LE) &&
352 ReplacementText =
"!" + ReplacementText;
353 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
359 if (
const auto *UnaryOp =
360 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
361 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
364 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
365 "!" + ReplacementText);
368 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
370 if (WarnLoc.isValid()) {
371 auto Diag =
diag(WarnLoc,
"the 'empty' method should be used to check "
372 "for emptiness instead of %0");
373 if (
const auto *SizeMethod =
374 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
376 else if (
const auto *DependentExpr =
377 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
379 Diag << DependentExpr->getMember();
381 Diag <<
"unknown method";
384 WarnLoc = BinCmpTempl
385 ? BinCmpTempl->getBeginLoc()
386 : (BinCmp ? BinCmp->getBeginLoc()
387 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
388 : SourceLocation{}));
389 diag(WarnLoc,
"the 'empty' method should be used to check "
390 "for emptiness instead of comparing to an empty object")
394 const auto *
Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
395 if (
const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(
Container)) {
400 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
401 Container = CTS->getSpecializedTemplate();
403 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
405 diag(Empty->getLocation(),
"method %0::empty() defined here",
llvm::SmallString< 256U > Name
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