Index: clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tidy/misc/CMakeLists.txt +++ clang-tidy/misc/CMakeLists.txt @@ -5,6 +5,7 @@ AssertSideEffectCheck.cpp AssignOperatorSignatureCheck.cpp BoolPointerImplicitConversionCheck.cpp + ForRangeCopyCheck.cpp InaccurateEraseCheck.cpp InefficientAlgorithmCheck.cpp MacroParenthesesCheck.cpp Index: clang-tidy/misc/ForRangeCopyCheck.h =================================================================== --- /dev/null +++ clang-tidy/misc/ForRangeCopyCheck.h @@ -0,0 +1,44 @@ +//===--- 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_MISC_FORRANGECOPYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORRANGECOPYCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +// A check that detects copied loop variables and suggests using const +// references. +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 tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORRANGECOPYCHECK_H Index: clang-tidy/misc/ForRangeCopyCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/misc/ForRangeCopyCheck.cpp @@ -0,0 +1,190 @@ +//===--- 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/TypeTraits.h" +#include "clang/Lex/Lexer.h" + +#include + +namespace clang { +namespace tidy { + +using namespace ::clang::ast_matchers; + +namespace { + +Token getPreviousNonCommentToken(const ASTContext &Context, + SourceLocation Location) { + const auto &SourceManager = Context.getSourceManager(); + Token Token; + Token.setKind(tok::unknown); + Location = Location.getLocWithOffset(-1); + auto StartOfFile = + SourceManager.getLocForStartOfFile(SourceManager.getFileID(Location)); + while (Location != StartOfFile) { + Location = Lexer::GetBeginningOfToken(Location, SourceManager, + Context.getLangOpts()); + if (!Lexer::getRawToken(Location, Token, SourceManager, + Context.getLangOpts()) && + !Token.is(tok::comment)) { + break; + } + Location = Location.getLocWithOffset(-1); + } + return Token; +} + +template void insertAll(const S &S1, S &S2) { + S2.insert(S1.begin(), S1.end()); +} + +template bool isSetDifferenceEmpty(const S &S1, const S &S2) { + for (const auto &E : S1) { + if (S2.find(E) == S2.end()) { + return false; + } + } + return true; +} + +// Extracts all Nodes keyed by ID from Matches and returns them as +// set for comparison. +template +std::set +extractNodesById(const SmallVector &Matches, StringRef ID) { + std::set Nodes; + for (const auto &Match : Matches) { + Nodes.insert(Match.getNodeAs(ID)); + } + return Nodes; +} + +// Finds all DeclRefExprs to VarDecl in Stmt. +std::set +declRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) { + auto Matches = match( + findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")), + Stmt, Context); + return extractNodesById(Matches, "declRef"); +} + +// 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. +std::set constReferenceDeclRefExprs(const VarDecl &VarDecl, + const Stmt &Stmt, + ASTContext &Context) { + auto Matches = match( + findAll(cxxMemberCallExpr( + callee(cxxMethodDecl(isConst())), + on(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")))), + Stmt, Context); + auto DeclRefs = extractNodesById(Matches, "declRef"); + auto ConstReferenceOrValue = + qualType(anyOf(referenceType(pointee(qualType(isConstQualified()))), + unless(anyOf(referenceType(), pointerType())))); + auto UsedAsConstRefOrValueArg = forEachArgumentWithParam( + declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"), + parmVarDecl(hasType(ConstReferenceOrValue))); + Matches = match(findAll(callExpr(UsedAsConstRefOrValueArg)), Stmt, Context); + insertAll(extractNodesById(Matches, "declRef"), DeclRefs); + Matches = + match(findAll(cxxConstructExpr(UsedAsConstRefOrValueArg)), Stmt, Context); + insertAll(extractNodesById(Matches, "declRef"), DeclRefs); + return DeclRefs; +} + +// Modifies VarDecl to be a reference. +FixItHint createAmpersandFix(const VarDecl &VarDecl, ASTContext &Context) { + SourceLocation AmpLocation = VarDecl.getLocation(); + auto Token = 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) { + auto LoopVar = + varDecl(hasType(qualType(unless(anyOf(referenceType(), pointerType()))))); + Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar"))) + .bind("forRange"), + this); +} + +void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) { + const VarDecl *Var = Result.Nodes.getNodeAs("loopVar"); + const auto *ForRange = Result.Nodes.getNodeAs("forRange"); + if (handleConstValueCopy(*Var, *Result.Context)) + return; + 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(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 tidy +} // namespace clang Index: clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tidy/misc/MiscTidyModule.cpp +++ clang-tidy/misc/MiscTidyModule.cpp @@ -14,6 +14,7 @@ #include "AssertSideEffectCheck.h" #include "AssignOperatorSignatureCheck.h" #include "BoolPointerImplicitConversionCheck.h" +#include "ForRangeCopyCheck.h" #include "InaccurateEraseCheck.h" #include "InefficientAlgorithmCheck.h" #include "MacroParenthesesCheck.h" @@ -48,6 +49,7 @@ "misc-assign-operator-signature"); CheckFactories.registerCheck( "misc-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck("misc-for-range-copy"); CheckFactories.registerCheck( "misc-inaccurate-erase"); CheckFactories.registerCheck( Index: test/clang-tidy/misc-for-range-copy-warn-on-all-auto-copies.cpp =================================================================== --- /dev/null +++ test/clang-tidy/misc-for-range-copy-warn-on-all-auto-copies.cpp @@ -0,0 +1,36 @@ +// RUN: %python %S/check_clang_tidy.py %s misc-for-range-copy %t -config="{CheckOptions: [{key: "misc-for-range-copy.WarnOnAllAutoCopies", value: 1}]}" -- -std=c++11 + +template +struct Iterator { + void operator++() {} + T operator*() { return T(); } + bool operator!=(const Iterator &) { return false; } +}; +template +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>()) { + S* S2 = &S1; + } +} + +void PositiveTriggeredForAutoLoopVariable() { + for (auto S1 : View>()) { + // 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 [misc-for-range-copy] + // CHECK-FIXES: for (const auto& S1 : View>()) { + S* S2 = &S1; + } +} Index: test/clang-tidy/misc-for-range-copy.cpp =================================================================== --- /dev/null +++ test/clang-tidy/misc-for-range-copy.cpp @@ -0,0 +1,145 @@ +// RUN: %python %S/check_clang_tidy.py %s misc-for-range-copy %t + +namespace std { + +template +struct remove_reference { typedef _Tp type; }; + +template +constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) { + return static_cast::type &&>(__t); +} + +} // std + +template +struct Iterator { + void operator++() {} + T operator*() { return T(); } + bool operator!=(const Iterator &) { return false; } +}; +template +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 NegativeConstReference() { + for (const S &S1 : View>()) { + } +} + +template +void uninstantiated() { + for (const S S1 : View>()) {} + // 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 [misc-for-range-copy] + // CHECK-FIXES: {{^}} for (const S& S1 : View>()) {} + + // Don't warn on dependent types. + for (const T t1 : View>()) { + } +} + +template +void instantiated() { + for (const S S2 : View>()) {} + // CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}} + // CHECK-FIXES: {{^}} for (const S& S2 : View>()) {} + + for (const T T2 : View>()) {} + // CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}} + // CHECK-FIXES: {{^}} for (const T& T2 : View>()) {} +} + +void f() { + instantiated(); + instantiated(); +} + +struct Mutable { + Mutable() {} + Mutable(const Mutable &) = default; + Mutable(const Mutable &, const Mutable &) {} + void setBool(bool B) {} + bool constMethod() const { + return true; + } + ~Mutable() {} +}; + +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>()) { + mutate(M); + } + for (auto M : View>()) { + mutate(&M); + } + for (auto M : View>()) { + M.setBool(true); + } +} + +void negativeOnceConstOnceMutated() { + for (auto M : View>()) { + onceConstOnceMutated(M, M); + } +} + +void negativeVarIsMoved() { + for (auto M : View>()) { + auto Moved = std::move(M); + } +} + +void positiveOnlyConstMethodInvoked() { + for (auto M : View>()) { + // CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [misc-for-range-copy] + // CHECK-FIXES: for (const auto& M : View>()) { + M.constMethod(); + } +} + +void positiveOnlyUsedAsConstArguments() { + for (auto UsedAsConst : View>()) { + // CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [misc-for-range-copy] + // CHECK-FIXES: for (const auto& UsedAsConst : View>()) { + use(UsedAsConst); + useTwice(UsedAsConst, UsedAsConst); + useByValue(UsedAsConst); + useByConstValue(UsedAsConst); + } +} + +void positiveOnlyUsedInCopyConstructor() { + for (auto A : View>()) { + // CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [misc-for-range-copy] + // CHECK-FIXES: for (const auto& A : View>()) { + Mutable Copy = A; + Mutable Copy2(A); + } +} + +void positiveTwoConstConstructorArgs() { + for (auto A : View>()) { + // CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [misc-for-range-copy] + // CHECK-FIXES: for (const auto& A : View>()) { + Mutable Copy(A, A); + } +}