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