1 //===--- ForRangeCopyCheck.cpp - clang-tidy--------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ForRangeCopyCheck.h" 10 #include "../utils/DeclRefExprUtils.h" 11 #include "../utils/FixItHintUtils.h" 12 #include "../utils/Matchers.h" 13 #include "../utils/OptionsUtils.h" 14 #include "../utils/TypeTraits.h" 15 #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" 16 #include "clang/Basic/Diagnostic.h" 17 #include <optional> 18 19 using namespace clang::ast_matchers; 20 21 namespace clang::tidy::performance { 22 23 ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context) 24 : ClangTidyCheck(Name, Context), 25 WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", false)), 26 AllowedTypes( 27 utils::options::parseStringList(Options.get("AllowedTypes", ""))) {} 28 29 void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 30 Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies); 31 Options.store(Opts, "AllowedTypes", 32 utils::options::serializeStringList(AllowedTypes)); 33 } 34 35 void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) { 36 // Match loop variables that are not references or pointers or are already 37 // initialized through MaterializeTemporaryExpr which indicates a type 38 // conversion. 39 auto HasReferenceOrPointerTypeOrIsAllowed = hasType(qualType( 40 unless(anyOf(hasCanonicalType(anyOf(referenceType(), pointerType())), 41 hasDeclaration(namedDecl( 42 matchers::matchesAnyListedName(AllowedTypes))))))); 43 auto IteratorReturnsValueType = cxxOperatorCallExpr( 44 hasOverloadedOperatorName("*"), 45 callee( 46 cxxMethodDecl(returns(unless(hasCanonicalType(referenceType())))))); 47 auto NotConstructedByCopy = cxxConstructExpr( 48 hasDeclaration(cxxConstructorDecl(unless(isCopyConstructor())))); 49 auto ConstructedByConversion = cxxMemberCallExpr(callee(cxxConversionDecl())); 50 auto LoopVar = 51 varDecl(HasReferenceOrPointerTypeOrIsAllowed, 52 unless(hasInitializer(expr(hasDescendant(expr( 53 anyOf(materializeTemporaryExpr(), IteratorReturnsValueType, 54 NotConstructedByCopy, ConstructedByConversion))))))); 55 Finder->addMatcher( 56 traverse(TK_AsIs, 57 cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar"))) 58 .bind("forRange")), 59 this); 60 } 61 62 void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) { 63 const auto *Var = Result.Nodes.getNodeAs<VarDecl>("loopVar"); 64 65 // Ignore code in macros since we can't place the fixes correctly. 66 if (Var->getBeginLoc().isMacroID()) 67 return; 68 if (handleConstValueCopy(*Var, *Result.Context)) 69 return; 70 const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>("forRange"); 71 handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context); 72 } 73 74 bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar, 75 ASTContext &Context) { 76 if (WarnOnAllAutoCopies) { 77 // For aggressive check just test that loop variable has auto type. 78 if (!isa<AutoType>(LoopVar.getType())) 79 return false; 80 } else if (!LoopVar.getType().isConstQualified()) { 81 return false; 82 } 83 std::optional<bool> Expensive = 84 utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); 85 if (!Expensive || !*Expensive) 86 return false; 87 auto Diagnostic = 88 diag(LoopVar.getLocation(), 89 "the loop variable's type is not a reference type; this creates a " 90 "copy in each iteration; consider making this a reference") 91 << utils::fixit::changeVarDeclToReference(LoopVar, Context); 92 if (!LoopVar.getType().isConstQualified()) { 93 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl( 94 LoopVar, Context, Qualifiers::Const)) 95 Diagnostic << *Fix; 96 } 97 return true; 98 } 99 100 static bool isReferenced(const VarDecl &LoopVar, const Stmt &Stmt, 101 ASTContext &Context) { 102 const auto IsLoopVar = varDecl(equalsNode(&LoopVar)); 103 return !match(stmt(hasDescendant(declRefExpr(to(valueDecl(anyOf( 104 IsLoopVar, bindingDecl(forDecomposition(IsLoopVar)))))))), 105 Stmt, Context) 106 .empty(); 107 } 108 109 bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced( 110 const VarDecl &LoopVar, const CXXForRangeStmt &ForRange, 111 ASTContext &Context) { 112 std::optional<bool> Expensive = 113 utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); 114 if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive) 115 return false; 116 // We omit the case where the loop variable is not used in the loop body. E.g. 117 // 118 // for (auto _ : benchmark_state) { 119 // } 120 // 121 // Because the fix (changing to `const auto &`) will introduce an unused 122 // compiler warning which can't be suppressed. 123 // Since this case is very rare, it is safe to ignore it. 124 if (!ExprMutationAnalyzer(*ForRange.getBody(), Context).isMutated(&LoopVar) && 125 isReferenced(LoopVar, *ForRange.getBody(), Context)) { 126 auto Diag = diag( 127 LoopVar.getLocation(), 128 "loop variable is copied but only used as const reference; consider " 129 "making it a const reference"); 130 131 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl( 132 LoopVar, Context, Qualifiers::Const)) 133 Diag << *Fix << utils::fixit::changeVarDeclToReference(LoopVar, Context); 134 135 return true; 136 } 137 return false; 138 } 139 140 } // namespace clang::tidy::performance 141