1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
//===--- 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::tidy::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) {
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, 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, cxxMemberCallExpr(callee(cxxMethodDecl(hasName("end"))),
on(declRefExpr(hasDeclaration(
equalsBoundNode("IneffContObj")))))),
hasArgument(2, expr().bind("AlgParam")))
.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.contains("unordered");
const bool Maplike = IneffContName.contains("map");
// 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.contains("map") ? 2 : 1;
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 clang::tidy::performance
|