clang-tools 22.0.0git
ForRangeCopyCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ForRangeCopyCheck.h"
11#include "../utils/Matchers.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
21
23 : ClangTidyCheck(Name, Context),
24 WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", false)),
25 AllowedTypes(
26 utils::options::parseStringList(Options.get("AllowedTypes", ""))) {}
27
29 Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies);
30 Options.store(Opts, "AllowedTypes",
32}
33
34void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) {
35 // Match loop variables that are not references or pointers or are already
36 // initialized through MaterializeTemporaryExpr which indicates a type
37 // 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 unused
121 // 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::performance
static cl::opt< bool > Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
static bool isReferenced(const VarDecl &LoopVar, const Stmt &Stmt, ASTContext &Context)
FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context)
Creates fix to make VarDecl a reference by adding &.
std::optional< FixItHint > addQualifierToVarDecl(const VarDecl &Var, const ASTContext &Context, Qualifiers::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy)
Creates fix to qualify VarDecl with the specified Qualifier. Requires that Var is isolated in written...
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
std::optional< bool > isExpensiveToCopy(QualType Type, const ASTContext &Context)
Returns true if Type is expensive to copy.
llvm::StringMap< ClangTidyValue > OptionMap