[clang-tidy] ForRangeCopyCheck that warns on and fixes unnecessary copies of loop variables.

Patch by Felix Berger!

Differential revision: http://reviews.llvm.org/D13849

llvm-svn: 259199
This commit is contained in:
Alexander Kornienko 2016-01-29 15:54:26 +00:00
parent bfee5f7352
commit 5aebfe2e4a
9 changed files with 525 additions and 5 deletions

View File

@ -214,8 +214,8 @@ void awesome_f2();
""" % {"check_name_dashes" : check_name_dashes})
# Recreates the list of checks in the docs/clang-tidy/checks directory.
def update_checks_list(module_path):
docs_dir = os.path.join(module_path, '../../docs/clang-tidy/checks')
def update_checks_list(clang_tidy_path):
docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
with open(filename, 'r') as f:
lines = f.readlines()
@ -262,9 +262,17 @@ FIXME: Describe what patterns does the check detect and why. Give examples.
"underline" : "=" * len(check_name_dashes)})
def main():
if len(sys.argv) == 2 and sys.argv[1] == '--update-docs':
update_checks_list(os.path.dirname(sys.argv[0]))
return
if len(sys.argv) != 3:
print 'Usage: add_new_check.py <module> <check>, e.g.\n'
print 'add_new_check.py misc awesome-functions\n'
print """\
Usage: add_new_check.py <module> <check>, e.g.
add_new_check.py misc awesome-functions
Alternatively, run 'add_new_check.py --update-docs' to just update the list of
documentation files."""
return
module = sys.argv[1]
@ -281,7 +289,7 @@ def main():
adapt_module(module_path, module, check_name, check_name_camel)
write_test(module_path, module, check_name)
write_docs(module_path, module, check_name)
update_checks_list(module_path)
update_checks_list(clang_tidy_path)
print('Done. Now it\'s your turn!')
if __name__ == '__main__':

View File

@ -1,6 +1,7 @@
set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangTidyPerformanceModule
ForRangeCopyCheck.cpp
ImplicitCastInLoopCheck.cpp
PerformanceTidyModule.cpp
UnnecessaryCopyInitialization.cpp

View File

@ -0,0 +1,177 @@
//===--- ForRangeCopyCheck.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 "ForRangeCopyCheck.h"
#include "../utils/LexerUtils.h"
#include "../utils/TypeTraits.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/SmallPtrSet.h"
namespace clang {
namespace tidy {
namespace performance {
using namespace ::clang::ast_matchers;
using llvm::SmallPtrSet;
namespace {
template <typename S> bool isSetDifferenceEmpty(const S &S1, const S &S2) {
for (const auto &E : S1)
if (S2.count(E) == 0)
return false;
return true;
}
// Extracts all Nodes keyed by ID from Matches and inserts them into Nodes.
template <typename Node>
void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
SmallPtrSet<const Node *, 16> &Nodes) {
for (const auto &Match : Matches)
Nodes.insert(Match.getNodeAs<Node>(ID));
}
// Finds all DeclRefExprs to VarDecl in Stmt.
SmallPtrSet<const DeclRefExpr *, 16>
declRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) {
auto Matches = match(
findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")),
Stmt, Context);
SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
return DeclRefs;
}
// Finds all DeclRefExprs where a const method is called on VarDecl or VarDecl
// is the a const reference or value argument to a CallExpr or CXXConstructExpr.
SmallPtrSet<const DeclRefExpr *, 16>
constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
ASTContext &Context) {
auto DeclRefToVar =
declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef");
auto ConstMethodCallee = callee(cxxMethodDecl(isConst()));
// Match method call expressions where the variable is referenced as the this
// implicit object argument and opertor call expression for member operators
// where the variable is the 0-th argument.
auto Matches = match(
findAll(expr(anyOf(cxxMemberCallExpr(ConstMethodCallee, on(DeclRefToVar)),
cxxOperatorCallExpr(ConstMethodCallee,
hasArgument(0, DeclRefToVar))))),
Stmt, Context);
SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
auto ConstReferenceOrValue =
qualType(anyOf(referenceType(pointee(qualType(isConstQualified()))),
unless(anyOf(referenceType(), pointerType()))));
auto UsedAsConstRefOrValueArg = forEachArgumentWithParam(
DeclRefToVar, parmVarDecl(hasType(ConstReferenceOrValue)));
Matches = match(findAll(callExpr(UsedAsConstRefOrValueArg)), Stmt, Context);
extractNodesByIdTo(Matches, "declRef", DeclRefs);
Matches =
match(findAll(cxxConstructExpr(UsedAsConstRefOrValueArg)), Stmt, Context);
extractNodesByIdTo(Matches, "declRef", DeclRefs);
return DeclRefs;
}
// Modifies VarDecl to be a reference.
FixItHint createAmpersandFix(const VarDecl &VarDecl, ASTContext &Context) {
SourceLocation AmpLocation = VarDecl.getLocation();
auto Token = lexer_utils::getPreviousNonCommentToken(Context, AmpLocation);
if (!Token.is(tok::unknown))
AmpLocation = Token.getLocation().getLocWithOffset(Token.getLength());
return FixItHint::CreateInsertion(AmpLocation, "&");
}
// Modifies VarDecl to be const.
FixItHint createConstFix(const VarDecl &VarDecl) {
return FixItHint::CreateInsertion(VarDecl.getTypeSpecStartLoc(), "const ");
}
} // namespace
ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", 0)) {}
void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies);
}
void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) {
// Match loop variables that are not references or pointers or are already
// initialized through MaterializeTemporaryExpr which indicates a type
// conversion.
auto LoopVar = varDecl(
hasType(hasCanonicalType(unless(anyOf(referenceType(), pointerType())))),
unless(hasInitializer(expr(hasDescendant(materializeTemporaryExpr())))));
Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar")))
.bind("forRange"),
this);
}
void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Var = Result.Nodes.getNodeAs<VarDecl>("loopVar");
// Ignore code in macros since we can't place the fixes correctly.
if (Var->getLocStart().isMacroID())
return;
if (handleConstValueCopy(*Var, *Result.Context))
return;
const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>("forRange");
handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context);
}
bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
ASTContext &Context) {
if (WarnOnAllAutoCopies) {
// For aggressive check just test that loop variable has auto type.
if (!isa<AutoType>(LoopVar.getType()))
return false;
} else if (!LoopVar.getType().isConstQualified()) {
return false;
}
if (!type_traits::isExpensiveToCopy(LoopVar.getType(), Context))
return false;
auto Diagnostic =
diag(LoopVar.getLocation(),
"the loop variable's type is not a reference type; this creates a "
"copy in each iteration; consider making this a reference")
<< createAmpersandFix(LoopVar, Context);
if (!LoopVar.getType().isConstQualified())
Diagnostic << createConstFix(LoopVar);
return true;
}
bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
ASTContext &Context) {
if (LoopVar.getType().isConstQualified() ||
!type_traits::isExpensiveToCopy(LoopVar.getType(), Context)) {
return false;
}
// Collect all DeclRefExprs to the loop variable and all CallExprs and
// CXXConstructExprs where the loop variable is used as argument to a const
// reference parameter.
// If the difference is empty it is safe for the loop variable to be a const
// reference.
auto AllDeclRefs = declRefExprs(LoopVar, *ForRange.getBody(), Context);
auto ConstReferenceDeclRefs =
constReferenceDeclRefExprs(LoopVar, *ForRange.getBody(), Context);
if (AllDeclRefs.empty() ||
!isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs))
return false;
diag(LoopVar.getLocation(),
"loop variable is copied but only used as const reference; consider "
"making it a const reference")
<< createConstFix(LoopVar) << createAmpersandFix(LoopVar, Context);
return true;
}
} // namespace performance
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,49 @@
//===--- ForRangeCopyCheck.h - clang-tidy------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H
#include "../ClangTidy.h"
namespace clang {
namespace tidy {
namespace performance {
/// A check that detects copied loop variables and suggests using const
/// references.
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/performance-for-range-copy.html
class ForRangeCopyCheck : public ClangTidyCheck {
public:
ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
// Checks if the loop variable is a const value and expensive to copy. If so
// suggests it be converted to a const reference.
bool handleConstValueCopy(const VarDecl &LoopVar, ASTContext &Context);
// Checks if the loop variable is a non-const value and whether only
// const methods are invoked on it or whether it is only used as a const
// reference argument. If so it suggests it be made a const reference.
bool handleCopyIsOnlyConstReferenced(const VarDecl &LoopVar,
const CXXForRangeStmt &ForRange,
ASTContext &Context);
const bool WarnOnAllAutoCopies;
};
} // namespace performance
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H

View File

@ -11,6 +11,7 @@
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "ForRangeCopyCheck.h"
#include "ImplicitCastInLoopCheck.h"
#include "UnnecessaryCopyInitialization.h"
@ -21,6 +22,8 @@ namespace performance {
class PerformanceModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<ForRangeCopyCheck>(
"performance-for-range-copy");
CheckFactories.registerCheck<ImplicitCastInLoopCheck>(
"performance-implicit-cast-in-loop");
CheckFactories.registerCheck<UnnecessaryCopyInitialization>(

View File

@ -76,6 +76,7 @@ Clang-Tidy Checks
modernize-use-default
modernize-use-nullptr
modernize-use-override
performance-for-range-copy
performance-implicit-cast-in-loop
readability-braces-around-statements
readability-container-size-empty

View File

@ -0,0 +1,19 @@
.. title:: clang-tidy - performance-for-range-copy
performance-for-range-copy
==========================
Finds C++11 for ranges where the loop variable is copied in each iteration but
it would suffice to obtain it by const reference.
The check is only applied to loop variables of types that are expensive to copy
which means they are not trivially copyable or have a non-trivial copy
constructor or destructor.
To ensure that it is safe to replace the copy with const reference the following
heuristic is employed:
1. The loop variable is const qualified.
2. The loop variable is not const, but only const methods or operators are
invoked on it, or it is used as const reference or value argument in
constructors or function calls.

View File

@ -0,0 +1,39 @@
// RUN: %check_clang_tidy %s performance-for-range-copy %t -config="{CheckOptions: [{key: "performance-for-range-copy.WarnOnAllAutoCopies", value: 1}]}" -- -std=c++11
template <typename T>
struct Iterator {
void operator++() {}
const T& operator*() {
static T* TT = new T();
return *TT;
}
bool operator!=(const Iterator &) { return false; }
};
template <typename T>
struct View {
T begin() { return T(); }
T begin() const { return T(); }
T end() { return T(); }
T end() const { return T(); }
};
struct S {
S();
S(const S &);
~S();
S &operator=(const S &);
};
void NegativeLoopVariableNotAuto() {
for (S S1 : View<Iterator<S>>()) {
S* S2 = &S1;
}
}
void PositiveTriggeredForAutoLoopVariable() {
for (auto S1 : View<Iterator<S>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: the loop variable's type is not a reference type; this creates a copy in each iteration; consider making this a reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& S1 : View<Iterator<S>>()) {
S* S2 = &S1;
}
}

View File

@ -0,0 +1,223 @@
// RUN: %check_clang_tidy %s performance-for-range-copy %t
namespace std {
template <typename _Tp>
struct remove_reference { typedef _Tp type; };
template <typename _Tp>
constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) {
return static_cast<typename std::remove_reference<_Tp>::type &&>(__t);
}
} // std
template <typename T>
struct Iterator {
void operator++() {}
const T& operator*() {
static T* TT = new T();
return *TT;
}
bool operator!=(const Iterator &) { return false; }
typedef const T& const_reference;
};
template <typename T>
struct View {
T begin() { return T(); }
T begin() const { return T(); }
T end() { return T(); }
T end() const { return T(); }
typedef typename T::const_reference const_reference;
};
struct ConstructorConvertible {
};
struct S {
S();
S(const S &);
S(const ConstructorConvertible&) {}
~S();
S &operator=(const S &);
};
struct Convertible {
operator S() const {
return S();
}
};
void negativeConstReference() {
for (const S &S1 : View<Iterator<S>>()) {
}
}
void negativeUserDefinedConversion() {
Convertible C[0];
for (const S &S1 : C) {
}
}
void negativeImplicitConstructorConversion() {
ConstructorConvertible C[0];
for (const S &S1 : C) {
}
}
template <typename T>
void uninstantiated() {
for (const S S1 : View<Iterator<S>>()) {}
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is not a reference type; this creates a copy in each iteration; consider making this a reference [performance-for-range-copy]
// CHECK-FIXES: {{^}} for (const S& S1 : View<Iterator<S>>()) {}
// Don't warn on dependent types.
for (const T t1 : View<Iterator<T>>()) {
}
}
template <typename T>
void instantiated() {
for (const S S2 : View<Iterator<S>>()) {}
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}}
// CHECK-FIXES: {{^}} for (const S& S2 : View<Iterator<S>>()) {}
for (const T T2 : View<Iterator<T>>()) {}
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}}
// CHECK-FIXES: {{^}} for (const T& T2 : View<Iterator<T>>()) {}
}
template <typename T>
void instantiatedNegativeTypedefConstReference() {
for (typename T::const_reference T2 : T()) {
S S1 = T2;
}
}
void f() {
instantiated<int>();
instantiated<S>();
instantiatedNegativeTypedefConstReference<View<Iterator<S>>>();
}
struct Mutable {
Mutable() {}
Mutable(const Mutable &) = default;
Mutable(const Mutable &, const Mutable &) {}
void setBool(bool B) {}
bool constMethod() const {
return true;
}
Mutable& operator[](int I) {
return *this;
}
bool operator==(const Mutable &Other) const {
return true;
}
~Mutable() {}
};
Mutable& operator<<(Mutable &Out, bool B) {
Out.setBool(B);
return Out;
}
bool operator!=(const Mutable& M1, const Mutable& M2) {
return false;
}
void use(const Mutable &M);
void useTwice(const Mutable &M1, const Mutable &M2);
void useByValue(Mutable M);
void useByConstValue(const Mutable M);
void mutate(Mutable *M);
void mutate(Mutable &M);
void onceConstOnceMutated(const Mutable &M1, Mutable &M2);
void negativeVariableIsMutated() {
for (auto M : View<Iterator<Mutable>>()) {
mutate(M);
}
for (auto M : View<Iterator<Mutable>>()) {
mutate(&M);
}
for (auto M : View<Iterator<Mutable>>()) {
M.setBool(true);
}
}
void negativeOnceConstOnceMutated() {
for (auto M : View<Iterator<Mutable>>()) {
onceConstOnceMutated(M, M);
}
}
void negativeVarIsMoved() {
for (auto M : View<Iterator<Mutable>>()) {
auto Moved = std::move(M);
}
}
void negativeNonConstOperatorIsInvoked() {
for (auto NonConstOperatorInvokee : View<Iterator<Mutable>>()) {
auto& N = NonConstOperatorInvokee[0];
}
}
void negativeNonConstNonMemberOperatorInvoked() {
for (auto NonConstOperatorInvokee : View<Iterator<Mutable>>()) {
NonConstOperatorInvokee << true;
}
}
void positiveOnlyConstMethodInvoked() {
for (auto M : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& M : View<Iterator<Mutable>>()) {
M.constMethod();
}
}
void positiveOnlyUsedAsConstArguments() {
for (auto UsedAsConst : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& UsedAsConst : View<Iterator<Mutable>>()) {
use(UsedAsConst);
useTwice(UsedAsConst, UsedAsConst);
useByValue(UsedAsConst);
useByConstValue(UsedAsConst);
}
}
void positiveOnlyUsedInCopyConstructor() {
for (auto A : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& A : View<Iterator<Mutable>>()) {
Mutable Copy = A;
Mutable Copy2(A);
}
}
void positiveTwoConstConstructorArgs() {
for (auto A : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& A : View<Iterator<Mutable>>()) {
Mutable Copy(A, A);
}
}
void PositiveConstMemberOperatorInvoked() {
for (auto ConstOperatorInvokee : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& ConstOperatorInvokee : View<Iterator<Mutable>>()) {
bool result = ConstOperatorInvokee == Mutable();
}
}
void PositiveConstNonMemberOperatorInvoked() {
for (auto ConstOperatorInvokee : View<Iterator<Mutable>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]
// CHECK-FIXES: for (const auto& ConstOperatorInvokee : View<Iterator<Mutable>>()) {
bool result = ConstOperatorInvokee != Mutable();
}
}