blob: bb4cd6cfa4aa5ca0fb5acef69eda55dce923ba2e [file] [log] [blame]
//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ContainerSizeEmptyCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/StringRef.h"
using namespace clang::ast_matchers;
static bool isContainer(llvm::StringRef ClassName) {
static const char *const ContainerNames[] = {"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);
}
namespace clang {
namespace {
AST_MATCHER(QualType, isBoolType) { return Node->isBooleanType(); }
AST_MATCHER(NamedDecl, stlContainer) {
return isContainer(Node.getQualifiedNameAsString());
}
} // namespace
namespace tidy {
namespace readability {
ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
// Only register the matchers for C++; the functionality currently does not
// provide any benefit to other languages, despite being benign.
if (!getLangOpts().CPlusPlus)
return;
const auto WrongUse = anyOf(
hasParent(
binaryOperator(
anyOf(has(integerLiteral(equals(0))),
allOf(anyOf(hasOperatorName("<"), hasOperatorName(">="),
hasOperatorName(">"), hasOperatorName("<=")),
hasEitherOperand(
ignoringImpCasts(integerLiteral(equals(1)))))))
.bind("SizeBinaryOp")),
hasParent(implicitCastExpr(
hasImplicitDestinationType(isBoolType()),
anyOf(
hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
anything()))),
hasParent(explicitCastExpr(hasDestinationType(isBoolType()))));
Finder->addMatcher(
cxxMemberCallExpr(
on(expr(anyOf(hasType(namedDecl(stlContainer())),
hasType(pointsTo(namedDecl(stlContainer()))),
hasType(references(namedDecl(stlContainer())))))
.bind("STLObject")),
callee(cxxMethodDecl(hasName("size"))), WrongUse)
.bind("SizeCallExpr"),
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;
std::string ReplacementText = Lexer::getSourceText(
CharSourceRange::getTokenRange(E->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());
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()->IgnoreImpCasts());
const auto OpCode = BinaryOp->getOpcode();
uint64_t Value = 0;
if (ContainerIsLHS) {
if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
BinaryOp->getRHS()->IgnoreImpCasts()))
Value = Literal->getValue().getLimitedValue();
else
return;
} else {
Value =
llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
->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;
// Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
if (Value == 1) {
if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
(OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
return;
if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
(OpCode == BinaryOperatorKind::BO_GE && !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