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/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(LocalName: "WarnOnAllAutoCopies", Default: false)),
25 AllowedTypes(
26 utils::options::parseStringList(Option: Options.get(LocalName: "AllowedTypes", Default: ""))) {}
27
28void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
29 Options.store(Options&: Opts, LocalName: "WarnOnAllAutoCopies", Value: WarnOnAllAutoCopies);
30 Options.store(Options&: Opts, LocalName: "AllowedTypes",
31 Value: utils::options::serializeStringList(Strings: 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(InnerMatcher: qualType(
39 unless(anyOf(hasCanonicalType(InnerMatcher: anyOf(referenceType(), pointerType())),
40 hasDeclaration(InnerMatcher: namedDecl(
41 matchers::matchesAnyListedName(NameList: AllowedTypes)))))));
42 auto IteratorReturnsValueType = cxxOperatorCallExpr(
43 hasOverloadedOperatorName(Name: "*"),
44 callee(
45 InnerMatcher: cxxMethodDecl(returns(InnerMatcher: unless(hasCanonicalType(InnerMatcher: referenceType()))))));
46 auto NotConstructedByCopy = cxxConstructExpr(
47 hasDeclaration(InnerMatcher: cxxConstructorDecl(unless(isCopyConstructor()))));
48 auto ConstructedByConversion = cxxMemberCallExpr(callee(InnerMatcher: cxxConversionDecl()));
49 auto LoopVar =
50 varDecl(HasReferenceOrPointerTypeOrIsAllowed,
51 unless(hasInitializer(InnerMatcher: expr(hasDescendant(expr(
52 anyOf(materializeTemporaryExpr(), IteratorReturnsValueType,
53 NotConstructedByCopy, ConstructedByConversion)))))));
54 Finder->addMatcher(
55 NodeMatch: traverse(TK: TK_AsIs,
56 InnerMatcher: cxxForRangeStmt(hasLoopVariable(InnerMatcher: LoopVar.bind(ID: "loopVar")))
57 .bind(ID: "forRange")),
58 Action: this);
59}
60
61void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
62 const auto *Var = Result.Nodes.getNodeAs<VarDecl>(ID: "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(LoopVar: *Var, Context&: *Result.Context))
68 return;
69 const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>(ID: "forRange");
70 handleCopyIsOnlyConstReferenced(LoopVar: *Var, ForRange: *ForRange, Context&: *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(Type: 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(Var: LoopVar, Context);
91 if (!LoopVar.getType().isConstQualified()) {
92 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl(
93 Var: LoopVar, Context, Qualifier: 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(Type: 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, Stmt: *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 Var: LoopVar, Context, Qualifier: Qualifiers::Const))
132 Diag << *Fix << utils::fixit::changeVarDeclToReference(Var: LoopVar, Context);
133
134 return true;
135 }
136 return false;
137}
138
139} // namespace clang::tidy::performance
140

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp