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 17 using namespace clang::ast_matchers; 18 19 namespace clang { 20 namespace tidy { 21 namespace performance { 22 23 ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context) 24 : ClangTidyCheck(Name, Context), 25 WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", 0)), 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 LoopVar = varDecl( 40 hasType(qualType( 41 unless(anyOf(hasCanonicalType(anyOf(referenceType(), pointerType())), 42 hasDeclaration(namedDecl( 43 matchers::matchesAnyListedName(AllowedTypes))))))), 44 unless(hasInitializer(expr(hasDescendant(materializeTemporaryExpr()))))); 45 Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar"))) 46 .bind("forRange"), 47 this); 48 } 49 50 void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) { 51 const auto *Var = Result.Nodes.getNodeAs<VarDecl>("loopVar"); 52 53 // Ignore code in macros since we can't place the fixes correctly. 54 if (Var->getBeginLoc().isMacroID()) 55 return; 56 if (handleConstValueCopy(*Var, *Result.Context)) 57 return; 58 const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>("forRange"); 59 handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context); 60 } 61 62 bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar, 63 ASTContext &Context) { 64 if (WarnOnAllAutoCopies) { 65 // For aggressive check just test that loop variable has auto type. 66 if (!isa<AutoType>(LoopVar.getType())) 67 return false; 68 } else if (!LoopVar.getType().isConstQualified()) { 69 return false; 70 } 71 llvm::Optional<bool> Expensive = 72 utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); 73 if (!Expensive || !*Expensive) 74 return false; 75 auto Diagnostic = 76 diag(LoopVar.getLocation(), 77 "the loop variable's type is not a reference type; this creates a " 78 "copy in each iteration; consider making this a reference") 79 << utils::fixit::changeVarDeclToReference(LoopVar, Context); 80 if (!LoopVar.getType().isConstQualified()) 81 Diagnostic << utils::fixit::changeVarDeclToConst(LoopVar); 82 return true; 83 } 84 85 bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced( 86 const VarDecl &LoopVar, const CXXForRangeStmt &ForRange, 87 ASTContext &Context) { 88 llvm::Optional<bool> Expensive = 89 utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); 90 if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive) 91 return false; 92 // We omit the case where the loop variable is not used in the loop body. E.g. 93 // 94 // for (auto _ : benchmark_state) { 95 // } 96 // 97 // Because the fix (changing to `const auto &`) will introduce an unused 98 // compiler warning which can't be suppressed. 99 // Since this case is very rare, it is safe to ignore it. 100 if (!ExprMutationAnalyzer(*ForRange.getBody(), Context).isMutated(&LoopVar) && 101 !utils::decl_ref_expr::allDeclRefExprs(LoopVar, *ForRange.getBody(), 102 Context) 103 .empty()) { 104 diag(LoopVar.getLocation(), 105 "loop variable is copied but only used as const reference; consider " 106 "making it a const reference") 107 << utils::fixit::changeVarDeclToConst(LoopVar) 108 << utils::fixit::changeVarDeclToReference(LoopVar, Context); 109 return true; 110 } 111 return false; 112 } 113 114 } // namespace performance 115 } // namespace tidy 116 } // namespace clang 117