teak-llvm/clang-tools-extra/clang-tidy/performance/InefficientAlgorithmCheck.cpp
Chandler Carruth 2946cd7010 Update the file headers across all of the LLVM projects in the monorepo
to reflect the new license.

We understand that people may be surprised that we're moving the header
entirely to discuss the new license. We checked this carefully with the
Foundation's lawyer and we believe this is the correct approach.

Essentially, all code in the project is now made available by the LLVM
project under our new license, so you will see that the license headers
include that license only. Some of our contributors have contributed
code under our old license, and accordingly, we have retained a copy of
our old license notice in the top-level files in each project and
repository.

llvm-svn: 351636
2019-01-19 08:50:56 +00:00

163 lines
6.4 KiB
C++

//===--- InefficientAlgorithmCheck.cpp - clang-tidy------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "InefficientAlgorithmCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace performance {
static bool areTypesCompatible(QualType Left, QualType Right) {
if (const auto *LeftRefType = Left->getAs<ReferenceType>())
Left = LeftRefType->getPointeeType();
if (const auto *RightRefType = Right->getAs<ReferenceType>())
Right = RightRefType->getPointeeType();
return Left->getCanonicalTypeUnqualified() ==
Right->getCanonicalTypeUnqualified();
}
void InefficientAlgorithmCheck::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 Algorithms =
hasAnyName("::std::find", "::std::count", "::std::equal_range",
"::std::lower_bound", "::std::upper_bound");
const auto ContainerMatcher = classTemplateSpecializationDecl(hasAnyName(
"::std::set", "::std::map", "::std::multiset", "::std::multimap",
"::std::unordered_set", "::std::unordered_map",
"::std::unordered_multiset", "::std::unordered_multimap"));
const auto Matcher =
callExpr(
callee(functionDecl(Algorithms)),
hasArgument(
0, cxxConstructExpr(has(ignoringParenImpCasts(cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("begin"))),
on(declRefExpr(
hasDeclaration(decl().bind("IneffContObj")),
anyOf(hasType(ContainerMatcher.bind("IneffCont")),
hasType(pointsTo(
ContainerMatcher.bind("IneffContPtr")))))
.bind("IneffContExpr"))))))),
hasArgument(
1, cxxConstructExpr(has(ignoringParenImpCasts(cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("end"))),
on(declRefExpr(
hasDeclaration(equalsBoundNode("IneffContObj"))))))))),
hasArgument(2, expr().bind("AlgParam")),
unless(isInTemplateInstantiation()))
.bind("IneffAlg");
Finder->addMatcher(Matcher, this);
}
void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) {
const auto *AlgCall = Result.Nodes.getNodeAs<CallExpr>("IneffAlg");
const auto *IneffCont =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("IneffCont");
bool PtrToContainer = false;
if (!IneffCont) {
IneffCont =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("IneffContPtr");
PtrToContainer = true;
}
const llvm::StringRef IneffContName = IneffCont->getName();
const bool Unordered =
IneffContName.find("unordered") != llvm::StringRef::npos;
const bool Maplike = IneffContName.find("map") != llvm::StringRef::npos;
// Store if the key type of the container is compatible with the value
// that is searched for.
QualType ValueType = AlgCall->getArg(2)->getType();
QualType KeyType =
IneffCont->getTemplateArgs()[0].getAsType().getCanonicalType();
const bool CompatibleTypes = areTypesCompatible(KeyType, ValueType);
// Check if the comparison type for the algorithm and the container matches.
if (AlgCall->getNumArgs() == 4 && !Unordered) {
const Expr *Arg = AlgCall->getArg(3);
const QualType AlgCmp =
Arg->getType().getUnqualifiedType().getCanonicalType();
const unsigned CmpPosition =
(IneffContName.find("map") == llvm::StringRef::npos) ? 1 : 2;
const QualType ContainerCmp = IneffCont->getTemplateArgs()[CmpPosition]
.getAsType()
.getUnqualifiedType()
.getCanonicalType();
if (AlgCmp != ContainerCmp) {
diag(Arg->getBeginLoc(),
"different comparers used in the algorithm and the container");
return;
}
}
const auto *AlgDecl = AlgCall->getDirectCallee();
if (!AlgDecl)
return;
if (Unordered && AlgDecl->getName().find("bound") != llvm::StringRef::npos)
return;
const auto *AlgParam = Result.Nodes.getNodeAs<Expr>("AlgParam");
const auto *IneffContExpr = Result.Nodes.getNodeAs<Expr>("IneffContExpr");
FixItHint Hint;
SourceManager &SM = *Result.SourceManager;
LangOptions LangOpts = getLangOpts();
CharSourceRange CallRange =
CharSourceRange::getTokenRange(AlgCall->getSourceRange());
// FIXME: Create a common utility to extract a file range that the given token
// sequence is exactly spelled at (without macro argument expansions etc.).
// We can't use Lexer::makeFileCharRange here, because for
//
// #define F(x) x
// x(a b c);
//
// it will return "x(a b c)", when given the range "a"-"c". It makes sense for
// removals, but not for replacements.
//
// This code is over-simplified, but works for many real cases.
if (SM.isMacroArgExpansion(CallRange.getBegin()) &&
SM.isMacroArgExpansion(CallRange.getEnd())) {
CallRange.setBegin(SM.getSpellingLoc(CallRange.getBegin()));
CallRange.setEnd(SM.getSpellingLoc(CallRange.getEnd()));
}
if (!CallRange.getBegin().isMacroID() && !Maplike && CompatibleTypes) {
StringRef ContainerText = Lexer::getSourceText(
CharSourceRange::getTokenRange(IneffContExpr->getSourceRange()), SM,
LangOpts);
StringRef ParamText = Lexer::getSourceText(
CharSourceRange::getTokenRange(AlgParam->getSourceRange()), SM,
LangOpts);
std::string ReplacementText =
(llvm::Twine(ContainerText) + (PtrToContainer ? "->" : ".") +
AlgDecl->getName() + "(" + ParamText + ")")
.str();
Hint = FixItHint::CreateReplacement(CallRange, ReplacementText);
}
diag(AlgCall->getBeginLoc(),
"this STL algorithm call should be replaced with a container method")
<< Hint;
}
} // namespace performance
} // namespace tidy
} // namespace clang