clang-tools 22.0.0git
UnnecessaryValueParamCheck.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
12#include "../utils/Matchers.h"
14#include "../utils/TypeTraits.h"
15#include "clang/Frontend/CompilerInstance.h"
16#include "clang/Lex/Lexer.h"
17#include "clang/Lex/Preprocessor.h"
18#include <optional>
19
20using namespace clang::ast_matchers;
21
23
24static std::string paramNameOrIndex(StringRef Name, size_t Index) {
25 return (Name.empty() ? llvm::Twine('#') + llvm::Twine(Index + 1)
26 : llvm::Twine('\'') + Name + llvm::Twine('\''))
27 .str();
28}
29
30static bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl,
31 ASTContext &Context) {
32 auto Matches = match(
33 traverse(TK_AsIs,
34 decl(forEachDescendant(declRefExpr(
35 equalsNode(&DeclRef),
36 unless(hasAncestor(stmt(anyOf(forStmt(), cxxForRangeStmt(),
37 whileStmt(), doStmt())))))))),
38 Decl, Context);
39 return Matches.empty();
40}
41
43 StringRef Name, ClangTidyContext *Context)
44 : ClangTidyCheck(Name, Context),
45 Inserter(Options.getLocalOrGlobal("IncludeStyle",
46 utils::IncludeSorter::IS_LLVM),
47 areDiagsSelfContained()),
48 AllowedTypes(
49 utils::options::parseStringList(Options.get("AllowedTypes", ""))),
50 IgnoreCoroutines(Options.get("IgnoreCoroutines", true)) {}
51
53 const auto ExpensiveValueParamDecl = parmVarDecl(
54 hasType(qualType(
55 hasCanonicalType(matchers::isExpensiveToCopy()),
56 unless(anyOf(hasCanonicalType(referenceType()),
57 hasDeclaration(namedDecl(
58 matchers::matchesAnyListedName(AllowedTypes))))))),
59 decl().bind("param"));
60 Finder->addMatcher(
61 traverse(TK_AsIs,
62 functionDecl(
63 hasBody(IgnoreCoroutines ? stmt(unless(coroutineBodyStmt()))
64 : stmt()),
65 isDefinition(), unless(isImplicit()),
66 unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),
67 has(typeLoc(forEach(ExpensiveValueParamDecl))),
68 decl().bind("functionDecl"))),
69 this);
70}
71
72void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) {
73 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
74 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
75
76 TraversalKindScope RAII(*Result.Context, TK_AsIs);
77
78 FunctionParmMutationAnalyzer *Analyzer =
79 FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(
80 *Function, *Result.Context, MutationAnalyzerCache);
81 if (Analyzer->isMutated(Param))
82 return;
83
84 const bool IsConstQualified =
85 Param->getType().getCanonicalType().isConstQualified();
86
87 // If the parameter is non-const, check if it has a move constructor and is
88 // only referenced once to copy-construct another object or whether it has a
89 // move assignment operator and is only referenced once when copy-assigned.
90 // In this case wrap DeclRefExpr with std::move() to avoid the unnecessary
91 // copy.
92 if (!IsConstQualified) {
93 auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs(
94 *Param, *Function, *Result.Context);
95 if (AllDeclRefExprs.size() == 1) {
96 auto CanonicalType = Param->getType().getCanonicalType();
97 const auto &DeclRefExpr = **AllDeclRefExprs.begin();
98
99 if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) &&
102 DeclRefExpr, *Function, *Result.Context)) ||
105 DeclRefExpr, *Function, *Result.Context)))) {
106 handleMoveFix(*Param, DeclRefExpr, *Result.Context);
107 return;
108 }
109 }
110 }
111
112 handleConstRefFix(*Function, *Param, *Result.Context);
113}
114
116 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
117 Inserter.registerPreprocessor(PP);
118}
119
122 Options.store(Opts, "IncludeStyle", Inserter.getStyle());
123 Options.store(Opts, "AllowedTypes",
125 Options.store(Opts, "IgnoreCoroutines", IgnoreCoroutines);
126}
127
129 MutationAnalyzerCache.clear();
130}
131
132void UnnecessaryValueParamCheck::handleConstRefFix(const FunctionDecl &Function,
133 const ParmVarDecl &Param,
134 ASTContext &Context) {
135 const size_t Index =
136 llvm::find(Function.parameters(), &Param) - Function.parameters().begin();
137 const bool IsConstQualified =
138 Param.getType().getCanonicalType().isConstQualified();
139
140 auto Diag =
141 diag(Param.getLocation(),
142 "the %select{|const qualified }0parameter %1 of type %2 is copied "
143 "for each "
144 "invocation%select{ but only used as a const reference|}0; consider "
145 "making it a %select{const |}0reference")
146 << IsConstQualified << paramNameOrIndex(Param.getName(), Index)
147 << Param.getType();
148 // Do not propose fixes when:
149 // 1. the ParmVarDecl is in a macro, since we cannot place them correctly
150 // 2. the function is virtual as it might break overrides
151 // 3. the function is an explicit template/ specialization.
152 const auto *Method = llvm::dyn_cast<CXXMethodDecl>(&Function);
153 if (Param.getBeginLoc().isMacroID() || (Method && Method->isVirtual()) ||
154 Function.getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
155 return;
156 for (const auto *FunctionDecl = &Function; FunctionDecl != nullptr;
157 FunctionDecl = FunctionDecl->getPreviousDecl()) {
158 const auto &CurrentParam = *FunctionDecl->getParamDecl(Index);
159 Diag << utils::fixit::changeVarDeclToReference(CurrentParam, Context);
160 // The parameter of each declaration needs to be checked individually as to
161 // whether it is const or not as constness can differ between definition and
162 // declaration.
163 if (!CurrentParam.getType().getCanonicalType().isConstQualified()) {
164 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl(
165 CurrentParam, Context, Qualifiers::Const))
166 Diag << *Fix;
167 }
168 }
169}
170
171void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Param,
172 const DeclRefExpr &CopyArgument,
173 ASTContext &Context) {
174 auto Diag =
175 diag(CopyArgument.getBeginLoc(),
176 "parameter %0 of type %1 is passed by value and only copied once; "
177 "consider moving it to avoid unnecessary copies")
178 << &Param << Param.getType();
179 // Do not propose fixes in macros since we cannot place them correctly.
180 if (CopyArgument.getBeginLoc().isMacroID())
181 return;
182 const auto &SM = Context.getSourceManager();
183 auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM,
184 Context.getLangOpts());
185 Diag << FixItHint::CreateInsertion(CopyArgument.getBeginLoc(), "std::move(")
186 << FixItHint::CreateInsertion(EndLoc, ")")
187 << Inserter.createIncludeInsertion(
188 SM.getFileID(CopyArgument.getBeginLoc()), "<utility>");
189}
190
191} // 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 storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
UnnecessaryValueParamCheck(StringRef Name, ClangTidyContext *Context)
virtual void handleMoveFix(const ParmVarDecl &Param, const DeclRefExpr &CopyArgument, ASTContext &Context)
virtual void handleConstRefFix(const FunctionDecl &Function, const ParmVarDecl &Param, ASTContext &Context)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
static bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, ASTContext &Context)
static std::string paramNameOrIndex(StringRef Name, size_t Index)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, ASTContext &Context)
Returns true if DeclRefExpr is the argument of a copy-constructor call expression within Decl.
bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, ASTContext &Context)
Returns true if DeclRefExpr is the argument of a copy-assignment operator CallExpr within Decl.
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.
bool hasNonTrivialMoveAssignment(QualType Type)
Return true if Type has a non-trivial move assignment operator.
bool hasNonTrivialMoveConstructor(QualType Type)
Returns true if Type has a non-trivial move constructor.
llvm::StringMap< ClangTidyValue > OptionMap