clang-tools 18.0.0git
UnnecessaryCopyInitialization.cpp
Go to the documentation of this file.
1//===--- UnnecessaryCopyInitialization.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
10#include "../utils/DeclRefExprUtils.h"
11#include "../utils/FixItHintUtils.h"
12#include "../utils/LexerUtils.h"
13#include "../utils/Matchers.h"
14#include "../utils/OptionsUtils.h"
15#include "clang/AST/Decl.h"
16#include "clang/Basic/Diagnostic.h"
17#include <optional>
18
20namespace {
21
22using namespace ::clang::ast_matchers;
23using llvm::StringRef;
26
27static constexpr StringRef ObjectArgId = "objectArg";
28static constexpr StringRef InitFunctionCallId = "initFunctionCall";
29static constexpr StringRef MethodDeclId = "methodDecl";
30static constexpr StringRef FunctionDeclId = "functionDecl";
31static constexpr StringRef OldVarDeclId = "oldVarDecl";
32
33void recordFixes(const VarDecl &Var, ASTContext &Context,
34 DiagnosticBuilder &Diagnostic) {
36 if (!Var.getType().isLocalConstQualified()) {
37 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl(
38 Var, Context, DeclSpec::TQ::TQ_const))
39 Diagnostic << *Fix;
40 }
41}
42
43std::optional<SourceLocation> firstLocAfterNewLine(SourceLocation Loc,
44 SourceManager &SM) {
45 bool Invalid = false;
46 const char *TextAfter = SM.getCharacterData(Loc, &Invalid);
47 if (Invalid) {
48 return std::nullopt;
49 }
50 size_t Offset = std::strcspn(TextAfter, "\n");
51 return Loc.getLocWithOffset(TextAfter[Offset] == '\0' ? Offset : Offset + 1);
52}
53
54void recordRemoval(const DeclStmt &Stmt, ASTContext &Context,
55 DiagnosticBuilder &Diagnostic) {
56 auto &SM = Context.getSourceManager();
57 // Attempt to remove trailing comments as well.
58 auto Tok = utils::lexer::findNextTokenSkippingComments(Stmt.getEndLoc(), SM,
59 Context.getLangOpts());
60 std::optional<SourceLocation> PastNewLine =
61 firstLocAfterNewLine(Stmt.getEndLoc(), SM);
62 if (Tok && PastNewLine) {
63 auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1);
64 // Remove until the end of the line or the end of a trailing comment which
65 // ever comes first.
66 auto End =
67 SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment)
68 ? *PastNewLine
69 : BeforeFirstTokenAfterComment;
70 Diagnostic << FixItHint::CreateRemoval(
71 SourceRange(Stmt.getBeginLoc(), End));
72 } else {
73 Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange());
74 }
75}
76
77AST_MATCHER_FUNCTION_P(StatementMatcher, isConstRefReturningMethodCall,
78 std::vector<StringRef>, ExcludedContainerTypes) {
79 // Match method call expressions where the `this` argument is only used as
80 // const, this will be checked in `check()` part. This returned const
81 // reference is highly likely to outlive the local const reference of the
82 // variable being declared. The assumption is that the const reference being
83 // returned either points to a global static variable or to a member of the
84 // called object.
85 const auto MethodDecl =
86 cxxMethodDecl(returns(hasCanonicalType(matchers::isReferenceToConst())))
87 .bind(MethodDeclId);
88 const auto ReceiverExpr = declRefExpr(to(varDecl().bind(ObjectArgId)));
89 const auto ReceiverType =
90 hasCanonicalType(recordType(hasDeclaration(namedDecl(
91 unless(matchers::matchesAnyListedName(ExcludedContainerTypes))))));
92
93 return expr(anyOf(
94 cxxMemberCallExpr(callee(MethodDecl), on(ReceiverExpr),
95 thisPointerType(ReceiverType)),
96 cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, ReceiverExpr),
97 hasArgument(0, hasType(ReceiverType)))));
98}
99
100AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) {
101 // Only allow initialization of a const reference from a free function if it
102 // has no arguments. Otherwise it could return an alias to one of its
103 // arguments and the arguments need to be checked for const use as well.
104 return callExpr(callee(functionDecl(returns(hasCanonicalType(
105 matchers::isReferenceToConst())))
106 .bind(FunctionDeclId)),
107 argumentCountIs(0), unless(callee(cxxMethodDecl())))
108 .bind(InitFunctionCallId);
109}
110
111AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst,
112 std::vector<StringRef>, ExcludedContainerTypes) {
113 auto OldVarDeclRef =
114 declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId)));
115 return expr(
116 anyOf(isConstRefReturningFunctionCall(),
117 isConstRefReturningMethodCall(ExcludedContainerTypes),
118 ignoringImpCasts(OldVarDeclRef),
119 ignoringImpCasts(unaryOperator(hasOperatorName("&"),
120 hasUnaryOperand(OldVarDeclRef)))));
121}
122
123// This checks that the variable itself is only used as const, and also makes
124// sure that it does not reference another variable that could be modified in
125// the BlockStmt. It does this by checking the following:
126// 1. If the variable is neither a reference nor a pointer then the
127// isOnlyUsedAsConst() check is sufficient.
128// 2. If the (reference or pointer) variable is not initialized in a DeclStmt in
129// the BlockStmt. In this case its pointee is likely not modified (unless it
130// is passed as an alias into the method as well).
131// 3. If the reference is initialized from a reference to const. This is
132// the same set of criteria we apply when identifying the unnecessary copied
133// variable in this check to begin with. In this case we check whether the
134// object arg or variable that is referenced is immutable as well.
135static bool isInitializingVariableImmutable(
136 const VarDecl &InitializingVar, const Stmt &BlockStmt, ASTContext &Context,
137 const std::vector<StringRef> &ExcludedContainerTypes) {
138 if (!isOnlyUsedAsConst(InitializingVar, BlockStmt, Context))
139 return false;
140
141 QualType T = InitializingVar.getType().getCanonicalType();
142 // The variable is a value type and we know it is only used as const. Safe
143 // to reference it and avoid the copy.
144 if (!isa<ReferenceType, PointerType>(T))
145 return true;
146
147 // The reference or pointer is not declared and hence not initialized anywhere
148 // in the function. We assume its pointee is not modified then.
149 if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit()) {
150 return true;
151 }
152
153 auto Matches =
154 match(initializerReturnsReferenceToConst(ExcludedContainerTypes),
155 *InitializingVar.getInit(), Context);
156 // The reference is initialized from a free function without arguments
157 // returning a const reference. This is a global immutable object.
158 if (selectFirst<CallExpr>(InitFunctionCallId, Matches) != nullptr)
159 return true;
160 // Check that the object argument is immutable as well.
161 if (const auto *OrigVar = selectFirst<VarDecl>(ObjectArgId, Matches))
162 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context,
163 ExcludedContainerTypes);
164 // Check that the old variable we reference is immutable as well.
165 if (const auto *OrigVar = selectFirst<VarDecl>(OldVarDeclId, Matches))
166 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context,
167 ExcludedContainerTypes);
168
169 return false;
170}
171
172bool isVariableUnused(const VarDecl &Var, const Stmt &BlockStmt,
173 ASTContext &Context) {
174 return allDeclRefExprs(Var, BlockStmt, Context).empty();
175}
176
177const SubstTemplateTypeParmType *getSubstitutedType(const QualType &Type,
178 ASTContext &Context) {
179 auto Matches = match(
180 qualType(anyOf(substTemplateTypeParmType().bind("subst"),
181 hasDescendant(substTemplateTypeParmType().bind("subst")))),
182 Type, Context);
183 return selectFirst<SubstTemplateTypeParmType>("subst", Matches);
184}
185
186bool differentReplacedTemplateParams(const QualType &VarType,
187 const QualType &InitializerType,
188 ASTContext &Context) {
189 if (const SubstTemplateTypeParmType *VarTmplType =
190 getSubstitutedType(VarType, Context)) {
191 if (const SubstTemplateTypeParmType *InitializerTmplType =
192 getSubstitutedType(InitializerType, Context)) {
193 const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter();
194 const TemplateTypeParmDecl *InitTTP =
195 InitializerTmplType->getReplacedParameter();
196 return (VarTTP->getDepth() != InitTTP->getDepth() ||
197 VarTTP->getIndex() != InitTTP->getIndex() ||
198 VarTTP->isParameterPack() != InitTTP->isParameterPack());
199 }
200 }
201 return false;
202}
203
204QualType constructorArgumentType(const VarDecl *OldVar,
205 const BoundNodes &Nodes) {
206 if (OldVar) {
207 return OldVar->getType();
208 }
209 if (const auto *FuncDecl = Nodes.getNodeAs<FunctionDecl>(FunctionDeclId)) {
210 return FuncDecl->getReturnType();
211 }
212 const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(MethodDeclId);
213 return MethodDecl->getReturnType();
214}
215
216} // namespace
217
219 StringRef Name, ClangTidyContext *Context)
220 : ClangTidyCheck(Name, Context),
221 AllowedTypes(
222 utils::options::parseStringList(Options.get("AllowedTypes", ""))),
223 ExcludedContainerTypes(utils::options::parseStringList(
224 Options.get("ExcludedContainerTypes", ""))) {}
225
227 auto LocalVarCopiedFrom = [this](const internal::Matcher<Expr> &CopyCtorArg) {
228 return compoundStmt(
229 forEachDescendant(
230 declStmt(
231 unless(has(decompositionDecl())),
232 has(varDecl(hasLocalStorage(),
233 hasType(qualType(
234 hasCanonicalType(allOf(
235 matchers::isExpensiveToCopy(),
236 unless(hasDeclaration(namedDecl(
237 hasName("::std::function")))))),
238 unless(hasDeclaration(namedDecl(
240 AllowedTypes)))))),
241 unless(isImplicit()),
242 hasInitializer(traverse(
243 TK_AsIs,
244 cxxConstructExpr(
245 hasDeclaration(cxxConstructorDecl(
246 isCopyConstructor())),
247 hasArgument(0, CopyCtorArg))
248 .bind("ctorCall"))))
249 .bind("newVarDecl")))
250 .bind("declStmt")))
251 .bind("blockStmt");
252 };
253
254 Finder->addMatcher(LocalVarCopiedFrom(anyOf(isConstRefReturningFunctionCall(),
255 isConstRefReturningMethodCall(
256 ExcludedContainerTypes))),
257 this);
258
259 Finder->addMatcher(LocalVarCopiedFrom(declRefExpr(
260 to(varDecl(hasLocalStorage()).bind(OldVarDeclId)))),
261 this);
262}
263
265 const MatchFinder::MatchResult &Result) {
266 const auto *NewVar = Result.Nodes.getNodeAs<VarDecl>("newVarDecl");
267 const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId);
268 const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId);
269 const auto *BlockStmt = Result.Nodes.getNodeAs<Stmt>("blockStmt");
270 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctorCall");
271 const auto *Stmt = Result.Nodes.getNodeAs<DeclStmt>("declStmt");
272
273 TraversalKindScope RAII(*Result.Context, TK_AsIs);
274
275 // Do not propose fixes if the DeclStmt has multiple VarDecls or in macros
276 // since we cannot place them correctly.
277 bool IssueFix = Stmt->isSingleDecl() && !NewVar->getLocation().isMacroID();
278
279 // A constructor that looks like T(const T& t, bool arg = false) counts as a
280 // copy only when it is called with default arguments for the arguments after
281 // the first.
282 for (unsigned int I = 1; I < CtorCall->getNumArgs(); ++I)
283 if (!CtorCall->getArg(I)->isDefaultArgument())
284 return;
285
286 // Don't apply the check if the variable and its initializer have different
287 // replaced template parameter types. In this case the check triggers for a
288 // template instantiation where the substituted types are the same, but
289 // instantiations where the types differ and rely on implicit conversion would
290 // no longer compile if we switched to a reference.
291 if (differentReplacedTemplateParams(
292 NewVar->getType(), constructorArgumentType(OldVar, Result.Nodes),
293 *Result.Context))
294 return;
295
296 if (OldVar == nullptr) {
297 handleCopyFromMethodReturn(*NewVar, *BlockStmt, *Stmt, IssueFix, ObjectArg,
298 *Result.Context);
299 } else {
300 handleCopyFromLocalVar(*NewVar, *OldVar, *BlockStmt, *Stmt, IssueFix,
301 *Result.Context);
302 }
303}
304
305void UnnecessaryCopyInitialization::handleCopyFromMethodReturn(
306 const VarDecl &Var, const Stmt &BlockStmt, const DeclStmt &Stmt,
307 bool IssueFix, const VarDecl *ObjectArg, ASTContext &Context) {
308 bool IsConstQualified = Var.getType().isConstQualified();
309 if (!IsConstQualified && !isOnlyUsedAsConst(Var, BlockStmt, Context))
310 return;
311 if (ObjectArg != nullptr &&
312 !isInitializingVariableImmutable(*ObjectArg, BlockStmt, Context,
313 ExcludedContainerTypes))
314 return;
315 if (isVariableUnused(Var, BlockStmt, Context)) {
316 auto Diagnostic =
317 diag(Var.getLocation(),
318 "the %select{|const qualified }0variable %1 is copy-constructed "
319 "from a const reference but is never used; consider "
320 "removing the statement")
321 << IsConstQualified << &Var;
322 if (IssueFix)
323 recordRemoval(Stmt, Context, Diagnostic);
324 } else {
325 auto Diagnostic =
326 diag(Var.getLocation(),
327 "the %select{|const qualified }0variable %1 is copy-constructed "
328 "from a const reference%select{ but is only used as const "
329 "reference|}0; consider making it a const reference")
330 << IsConstQualified << &Var;
331 if (IssueFix)
332 recordFixes(Var, Context, Diagnostic);
333 }
334}
335
336void UnnecessaryCopyInitialization::handleCopyFromLocalVar(
337 const VarDecl &NewVar, const VarDecl &OldVar, const Stmt &BlockStmt,
338 const DeclStmt &Stmt, bool IssueFix, ASTContext &Context) {
339 if (!isOnlyUsedAsConst(NewVar, BlockStmt, Context) ||
340 !isInitializingVariableImmutable(OldVar, BlockStmt, Context,
341 ExcludedContainerTypes))
342 return;
343
344 if (isVariableUnused(NewVar, BlockStmt, Context)) {
345 auto Diagnostic = diag(NewVar.getLocation(),
346 "local copy %0 of the variable %1 is never modified "
347 "and never used; "
348 "consider removing the statement")
349 << &NewVar << &OldVar;
350 if (IssueFix)
351 recordRemoval(Stmt, Context, Diagnostic);
352 } else {
353 auto Diagnostic =
354 diag(NewVar.getLocation(),
355 "local copy %0 of the variable %1 is never modified; "
356 "consider avoiding the copy")
357 << &NewVar << &OldVar;
358 if (IssueFix)
359 recordFixes(NewVar, Context, Diagnostic);
360 }
361}
362
365 Options.store(Opts, "AllowedTypes",
367 Options.store(Opts, "ExcludedContainerTypes",
368 utils::options::serializeStringList(ExcludedContainerTypes));
369}
370
371} // 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))
size_t Offset
DiagnosticCallback Diagnostic
NodeType Type
llvm::StringRef Name
SourceLocation Loc
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
UnnecessaryCopyInitialization(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:134
AST_MATCHER_FUNCTION_P(ast_matchers::internal::Matcher< Stmt >, comparisonOperatorWithCallee, ast_matchers::internal::Matcher< Decl >, funcDecl)
AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher< FunctionDecl >, DurationConversionFunction)
@ Invalid
Sentinel bit pattern. DO NOT USE!
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, ASTContext &Context)
Returns true if all DeclRefExpr to the variable within Stmt do not modify it.
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, DeclSpec::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy)
Creates fix to qualify VarDecl with the specified Qualifier.
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:111
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap