2015-03-09 20:18:39 +08:00
|
|
|
//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
|
2015-01-15 23:46:58 +08:00
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
2015-03-09 20:18:39 +08:00
|
|
|
#include "ContainerSizeEmptyCheck.h"
|
2015-01-15 23:46:58 +08:00
|
|
|
#include "clang/AST/ASTContext.h"
|
|
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
|
|
#include "clang/Lex/Lexer.h"
|
2015-02-13 17:07:58 +08:00
|
|
|
#include "llvm/ADT/StringRef.h"
|
2015-01-15 23:46:58 +08:00
|
|
|
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
|
2015-04-17 21:52:08 +08:00
|
|
|
static bool isContainer(llvm::StringRef ClassName) {
|
2015-10-18 13:14:41 +08:00
|
|
|
static const char *const ContainerNames[] = {
|
2015-04-17 21:52:08 +08:00
|
|
|
"std::array",
|
|
|
|
"std::deque",
|
|
|
|
"std::forward_list",
|
|
|
|
"std::list",
|
|
|
|
"std::map",
|
|
|
|
"std::multimap",
|
|
|
|
"std::multiset",
|
|
|
|
"std::priority_queue",
|
|
|
|
"std::queue",
|
|
|
|
"std::set",
|
|
|
|
"std::stack",
|
|
|
|
"std::unordered_map",
|
|
|
|
"std::unordered_multimap",
|
|
|
|
"std::unordered_multiset",
|
|
|
|
"std::unordered_set",
|
|
|
|
"std::vector"
|
|
|
|
};
|
|
|
|
return std::binary_search(std::begin(ContainerNames),
|
|
|
|
std::end(ContainerNames), ClassName);
|
2015-01-15 23:46:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace clang {
|
2015-06-17 21:11:37 +08:00
|
|
|
namespace {
|
2015-01-22 20:27:09 +08:00
|
|
|
AST_MATCHER(QualType, isBoolType) { return Node->isBooleanType(); }
|
2015-01-15 23:46:58 +08:00
|
|
|
|
|
|
|
AST_MATCHER(NamedDecl, stlContainer) {
|
|
|
|
return isContainer(Node.getQualifiedNameAsString());
|
|
|
|
}
|
2015-06-17 21:11:37 +08:00
|
|
|
} // namespace
|
2015-01-15 23:46:58 +08:00
|
|
|
|
|
|
|
namespace tidy {
|
|
|
|
namespace readability {
|
|
|
|
|
|
|
|
ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
|
|
|
|
ClangTidyContext *Context)
|
|
|
|
: ClangTidyCheck(Name, Context) {}
|
|
|
|
|
|
|
|
void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
|
2015-09-03 00:05:21 +08:00
|
|
|
// Only register the matchers for C++; the functionality currently does not
|
|
|
|
// provide any benefit to other languages, despite being benign.
|
|
|
|
if (!getLangOpts().CPlusPlus)
|
|
|
|
return;
|
|
|
|
|
2015-01-15 23:46:58 +08:00
|
|
|
const auto WrongUse = anyOf(
|
|
|
|
hasParent(
|
|
|
|
binaryOperator(
|
|
|
|
anyOf(has(integerLiteral(equals(0))),
|
|
|
|
allOf(anyOf(hasOperatorName("<"), hasOperatorName(">="),
|
|
|
|
hasOperatorName(">"), hasOperatorName("<=")),
|
2015-01-23 22:43:06 +08:00
|
|
|
hasEitherOperand(integerLiteral(equals(1))))))
|
2015-01-15 23:46:58 +08:00
|
|
|
.bind("SizeBinaryOp")),
|
|
|
|
hasParent(implicitCastExpr(
|
2015-01-22 20:27:09 +08:00
|
|
|
hasImplicitDestinationType(isBoolType()),
|
2015-01-15 23:46:58 +08:00
|
|
|
anyOf(
|
|
|
|
hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
|
|
|
|
anything()))),
|
2015-01-22 20:27:09 +08:00
|
|
|
hasParent(explicitCastExpr(hasDestinationType(isBoolType()))));
|
2015-01-15 23:46:58 +08:00
|
|
|
|
|
|
|
Finder->addMatcher(
|
2015-09-17 21:31:25 +08:00
|
|
|
cxxMemberCallExpr(
|
2015-01-15 23:46:58 +08:00
|
|
|
on(expr(anyOf(hasType(namedDecl(stlContainer())),
|
2015-01-22 20:27:09 +08:00
|
|
|
hasType(pointsTo(namedDecl(stlContainer()))),
|
|
|
|
hasType(references(namedDecl(stlContainer())))))
|
|
|
|
.bind("STLObject")),
|
2015-09-17 21:31:25 +08:00
|
|
|
callee(cxxMethodDecl(hasName("size"))), WrongUse)
|
|
|
|
.bind("SizeCallExpr"),
|
2015-01-15 23:46:58 +08:00
|
|
|
this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
|
|
|
|
const auto *MemberCall =
|
|
|
|
Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
|
|
|
|
const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
|
|
|
|
const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
|
|
|
|
FixItHint Hint;
|
2015-01-22 20:40:47 +08:00
|
|
|
std::string ReplacementText = Lexer::getSourceText(
|
|
|
|
CharSourceRange::getTokenRange(E->getSourceRange()),
|
|
|
|
*Result.SourceManager, Result.Context->getLangOpts());
|
2015-01-15 23:46:58 +08:00
|
|
|
if (E->getType()->isPointerType())
|
|
|
|
ReplacementText += "->empty()";
|
|
|
|
else
|
|
|
|
ReplacementText += ".empty()";
|
|
|
|
|
|
|
|
if (BinaryOp) { // Determine the correct transformation.
|
|
|
|
bool Negation = false;
|
|
|
|
const bool ContainerIsLHS = !llvm::isa<IntegerLiteral>(BinaryOp->getLHS());
|
|
|
|
const auto OpCode = BinaryOp->getOpcode();
|
|
|
|
uint64_t Value = 0;
|
|
|
|
if (ContainerIsLHS) {
|
|
|
|
if (const auto *Literal =
|
|
|
|
llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()))
|
|
|
|
Value = Literal->getValue().getLimitedValue();
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
Value = llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS())
|
|
|
|
->getValue()
|
|
|
|
.getLimitedValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constant that is not handled.
|
|
|
|
if (Value > 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Always true, no warnings for that.
|
|
|
|
if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
|
|
|
|
(OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
|
|
|
|
Negation = true;
|
|
|
|
if ((OpCode == BinaryOperatorKind::BO_GT ||
|
|
|
|
OpCode == BinaryOperatorKind::BO_GE) &&
|
|
|
|
ContainerIsLHS)
|
|
|
|
Negation = true;
|
|
|
|
if ((OpCode == BinaryOperatorKind::BO_LT ||
|
|
|
|
OpCode == BinaryOperatorKind::BO_LE) &&
|
|
|
|
!ContainerIsLHS)
|
|
|
|
Negation = true;
|
|
|
|
|
|
|
|
if (Negation)
|
|
|
|
ReplacementText = "!" + ReplacementText;
|
|
|
|
Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
|
|
|
|
ReplacementText);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// If there is a conversion above the size call to bool, it is safe to just
|
|
|
|
// replace size with empty.
|
|
|
|
if (const auto *UnaryOp =
|
|
|
|
Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
|
|
|
|
Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
|
|
|
|
ReplacementText);
|
|
|
|
else
|
|
|
|
Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
|
|
|
|
"!" + ReplacementText);
|
|
|
|
}
|
|
|
|
diag(MemberCall->getLocStart(),
|
|
|
|
"The 'empty' method should be used to check for emptiness instead "
|
|
|
|
"of 'size'.")
|
|
|
|
<< Hint;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace readability
|
|
|
|
} // namespace tidy
|
|
|
|
} // namespace clang
|