1 //===--- UnnecessaryCopyInitialization.cpp - clang-tidy--------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "UnnecessaryCopyInitialization.h"
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"
20 namespace clang::tidy::performance
{
23 using namespace ::clang::ast_matchers
;
24 using llvm::StringRef
;
25 using utils::decl_ref_expr::allDeclRefExprs
;
26 using utils::decl_ref_expr::isOnlyUsedAsConst
;
28 static constexpr StringRef ObjectArgId
= "objectArg";
29 static constexpr StringRef InitFunctionCallId
= "initFunctionCall";
30 static constexpr StringRef MethodDeclId
= "methodDecl";
31 static constexpr StringRef FunctionDeclId
= "functionDecl";
32 static constexpr StringRef OldVarDeclId
= "oldVarDecl";
34 void recordFixes(const VarDecl
&Var
, ASTContext
&Context
,
35 DiagnosticBuilder
&Diagnostic
) {
36 Diagnostic
<< utils::fixit::changeVarDeclToReference(Var
, Context
);
37 if (!Var
.getType().isLocalConstQualified()) {
38 if (std::optional
<FixItHint
> Fix
= utils::fixit::addQualifierToVarDecl(
39 Var
, Context
, Qualifiers::Const
))
44 std::optional
<SourceLocation
> firstLocAfterNewLine(SourceLocation Loc
,
47 const char *TextAfter
= SM
.getCharacterData(Loc
, &Invalid
);
51 size_t Offset
= std::strcspn(TextAfter
, "\n");
52 return Loc
.getLocWithOffset(TextAfter
[Offset
] == '\0' ? Offset
: Offset
+ 1);
55 void recordRemoval(const DeclStmt
&Stmt
, ASTContext
&Context
,
56 DiagnosticBuilder
&Diagnostic
) {
57 auto &SM
= Context
.getSourceManager();
58 // Attempt to remove trailing comments as well.
59 auto Tok
= utils::lexer::findNextTokenSkippingComments(Stmt
.getEndLoc(), SM
,
60 Context
.getLangOpts());
61 std::optional
<SourceLocation
> PastNewLine
=
62 firstLocAfterNewLine(Stmt
.getEndLoc(), SM
);
63 if (Tok
&& PastNewLine
) {
64 auto BeforeFirstTokenAfterComment
= Tok
->getLocation().getLocWithOffset(-1);
65 // Remove until the end of the line or the end of a trailing comment which
68 SM
.isBeforeInTranslationUnit(*PastNewLine
, BeforeFirstTokenAfterComment
)
70 : BeforeFirstTokenAfterComment
;
71 Diagnostic
<< FixItHint::CreateRemoval(
72 SourceRange(Stmt
.getBeginLoc(), End
));
74 Diagnostic
<< FixItHint::CreateRemoval(Stmt
.getSourceRange());
78 AST_MATCHER_FUNCTION_P(StatementMatcher
,
79 isRefReturningMethodCallWithConstOverloads
,
80 std::vector
<StringRef
>, ExcludedContainerTypes
) {
81 // Match method call expressions where the `this` argument is only used as
82 // const, this will be checked in `check()` part. This returned reference is
83 // highly likely to outlive the local const reference of the variable being
84 // declared. The assumption is that the reference being returned either points
85 // to a global static variable or to a member of the called object.
86 const auto MethodDecl
=
87 cxxMethodDecl(returns(hasCanonicalType(referenceType())))
89 const auto ReceiverExpr
=
90 ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ObjectArgId
))));
91 const auto OnExpr
= anyOf(
92 // Direct reference to `*this`: `a.f()` or `a->f()`.
94 // Access through dereference, typically used for `operator[]`: `(*a)[3]`.
95 unaryOperator(hasOperatorName("*"), hasUnaryOperand(ReceiverExpr
)));
96 const auto ReceiverType
=
97 hasCanonicalType(recordType(hasDeclaration(namedDecl(
98 unless(matchers::matchesAnyListedName(ExcludedContainerTypes
))))));
101 anyOf(cxxMemberCallExpr(callee(MethodDecl
), on(OnExpr
),
102 thisPointerType(ReceiverType
)),
103 cxxOperatorCallExpr(callee(MethodDecl
), hasArgument(0, OnExpr
),
104 hasArgument(0, hasType(ReceiverType
)))));
107 AST_MATCHER_FUNCTION(StatementMatcher
, isConstRefReturningFunctionCall
) {
108 // Only allow initialization of a const reference from a free function if it
109 // has no arguments. Otherwise it could return an alias to one of its
110 // arguments and the arguments need to be checked for const use as well.
111 return callExpr(callee(functionDecl(returns(hasCanonicalType(
112 matchers::isReferenceToConst())))
113 .bind(FunctionDeclId
)),
114 argumentCountIs(0), unless(callee(cxxMethodDecl())))
115 .bind(InitFunctionCallId
);
118 AST_MATCHER_FUNCTION_P(StatementMatcher
, initializerReturnsReferenceToConst
,
119 std::vector
<StringRef
>, ExcludedContainerTypes
) {
121 declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId
)));
123 anyOf(isConstRefReturningFunctionCall(),
124 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes
),
125 ignoringImpCasts(OldVarDeclRef
),
126 ignoringImpCasts(unaryOperator(hasOperatorName("&"),
127 hasUnaryOperand(OldVarDeclRef
)))));
130 // This checks that the variable itself is only used as const, and also makes
131 // sure that it does not reference another variable that could be modified in
132 // the BlockStmt. It does this by checking the following:
133 // 1. If the variable is neither a reference nor a pointer then the
134 // isOnlyUsedAsConst() check is sufficient.
135 // 2. If the (reference or pointer) variable is not initialized in a DeclStmt in
136 // the BlockStmt. In this case its pointee is likely not modified (unless it
137 // is passed as an alias into the method as well).
138 // 3. If the reference is initialized from a reference to const. This is
139 // the same set of criteria we apply when identifying the unnecessary copied
140 // variable in this check to begin with. In this case we check whether the
141 // object arg or variable that is referenced is immutable as well.
142 static bool isInitializingVariableImmutable(
143 const VarDecl
&InitializingVar
, const Stmt
&BlockStmt
, ASTContext
&Context
,
144 const std::vector
<StringRef
> &ExcludedContainerTypes
) {
145 QualType T
= InitializingVar
.getType().getCanonicalType();
146 if (!isOnlyUsedAsConst(InitializingVar
, BlockStmt
, Context
,
147 T
->isPointerType() ? 1 : 0))
150 // The variable is a value type and we know it is only used as const. Safe
151 // to reference it and avoid the copy.
152 if (!isa
<ReferenceType
, PointerType
>(T
))
155 // The reference or pointer is not declared and hence not initialized anywhere
156 // in the function. We assume its pointee is not modified then.
157 if (!InitializingVar
.isLocalVarDecl() || !InitializingVar
.hasInit()) {
162 match(initializerReturnsReferenceToConst(ExcludedContainerTypes
),
163 *InitializingVar
.getInit(), Context
);
164 // The reference is initialized from a free function without arguments
165 // returning a const reference. This is a global immutable object.
166 if (selectFirst
<CallExpr
>(InitFunctionCallId
, Matches
) != nullptr)
168 // Check that the object argument is immutable as well.
169 if (const auto *OrigVar
= selectFirst
<VarDecl
>(ObjectArgId
, Matches
))
170 return isInitializingVariableImmutable(*OrigVar
, BlockStmt
, Context
,
171 ExcludedContainerTypes
);
172 // Check that the old variable we reference is immutable as well.
173 if (const auto *OrigVar
= selectFirst
<VarDecl
>(OldVarDeclId
, Matches
))
174 return isInitializingVariableImmutable(*OrigVar
, BlockStmt
, Context
,
175 ExcludedContainerTypes
);
180 bool isVariableUnused(const VarDecl
&Var
, const Stmt
&BlockStmt
,
181 ASTContext
&Context
) {
182 return allDeclRefExprs(Var
, BlockStmt
, Context
).empty();
185 const SubstTemplateTypeParmType
*getSubstitutedType(const QualType
&Type
,
186 ASTContext
&Context
) {
187 auto Matches
= match(
188 qualType(anyOf(substTemplateTypeParmType().bind("subst"),
189 hasDescendant(substTemplateTypeParmType().bind("subst")))),
191 return selectFirst
<SubstTemplateTypeParmType
>("subst", Matches
);
194 bool differentReplacedTemplateParams(const QualType
&VarType
,
195 const QualType
&InitializerType
,
196 ASTContext
&Context
) {
197 if (const SubstTemplateTypeParmType
*VarTmplType
=
198 getSubstitutedType(VarType
, Context
)) {
199 if (const SubstTemplateTypeParmType
*InitializerTmplType
=
200 getSubstitutedType(InitializerType
, Context
)) {
201 const TemplateTypeParmDecl
*VarTTP
= VarTmplType
->getReplacedParameter();
202 const TemplateTypeParmDecl
*InitTTP
=
203 InitializerTmplType
->getReplacedParameter();
204 return (VarTTP
->getDepth() != InitTTP
->getDepth() ||
205 VarTTP
->getIndex() != InitTTP
->getIndex() ||
206 VarTTP
->isParameterPack() != InitTTP
->isParameterPack());
212 QualType
constructorArgumentType(const VarDecl
*OldVar
,
213 const BoundNodes
&Nodes
) {
215 return OldVar
->getType();
217 if (const auto *FuncDecl
= Nodes
.getNodeAs
<FunctionDecl
>(FunctionDeclId
)) {
218 return FuncDecl
->getReturnType();
220 const auto *MethodDecl
= Nodes
.getNodeAs
<CXXMethodDecl
>(MethodDeclId
);
221 return MethodDecl
->getReturnType();
226 UnnecessaryCopyInitialization::UnnecessaryCopyInitialization(
227 StringRef Name
, ClangTidyContext
*Context
)
228 : ClangTidyCheck(Name
, Context
),
230 utils::options::parseStringList(Options
.get("AllowedTypes", ""))),
231 ExcludedContainerTypes(utils::options::parseStringList(
232 Options
.get("ExcludedContainerTypes", ""))) {}
234 void UnnecessaryCopyInitialization::registerMatchers(MatchFinder
*Finder
) {
235 auto LocalVarCopiedFrom
= [this](const internal::Matcher
<Expr
> &CopyCtorArg
) {
239 unless(has(decompositionDecl())),
240 has(varDecl(hasLocalStorage(),
242 hasCanonicalType(allOf(
243 matchers::isExpensiveToCopy(),
244 unless(hasDeclaration(namedDecl(
245 hasName("::std::function")))))),
246 unless(hasDeclaration(namedDecl(
247 matchers::matchesAnyListedName(
249 unless(isImplicit()),
250 hasInitializer(traverse(
253 hasDeclaration(cxxConstructorDecl(
254 isCopyConstructor())),
255 hasArgument(0, CopyCtorArg
))
257 .bind("newVarDecl")))
263 LocalVarCopiedFrom(anyOf(
264 isConstRefReturningFunctionCall(),
265 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes
))),
268 Finder
->addMatcher(LocalVarCopiedFrom(declRefExpr(
269 to(varDecl(hasLocalStorage()).bind(OldVarDeclId
)))),
273 void UnnecessaryCopyInitialization::check(
274 const MatchFinder::MatchResult
&Result
) {
275 const auto &NewVar
= *Result
.Nodes
.getNodeAs
<VarDecl
>("newVarDecl");
276 const auto &BlockStmt
= *Result
.Nodes
.getNodeAs
<Stmt
>("blockStmt");
277 const auto &VarDeclStmt
= *Result
.Nodes
.getNodeAs
<DeclStmt
>("declStmt");
278 // Do not propose fixes if the DeclStmt has multiple VarDecls or in
279 // macros since we cannot place them correctly.
280 const bool IssueFix
=
281 VarDeclStmt
.isSingleDecl() && !NewVar
.getLocation().isMacroID();
282 const bool IsVarUnused
= isVariableUnused(NewVar
, BlockStmt
, *Result
.Context
);
283 const bool IsVarOnlyUsedAsConst
=
284 isOnlyUsedAsConst(NewVar
, BlockStmt
, *Result
.Context
,
285 // `NewVar` is always of non-pointer type.
287 const CheckContext Context
{
288 NewVar
, BlockStmt
, VarDeclStmt
, *Result
.Context
,
289 IssueFix
, IsVarUnused
, IsVarOnlyUsedAsConst
};
290 const auto *OldVar
= Result
.Nodes
.getNodeAs
<VarDecl
>(OldVarDeclId
);
291 const auto *ObjectArg
= Result
.Nodes
.getNodeAs
<VarDecl
>(ObjectArgId
);
292 const auto *CtorCall
= Result
.Nodes
.getNodeAs
<CXXConstructExpr
>("ctorCall");
294 TraversalKindScope
RAII(*Result
.Context
, TK_AsIs
);
296 // A constructor that looks like T(const T& t, bool arg = false) counts as a
297 // copy only when it is called with default arguments for the arguments after
299 for (unsigned int I
= 1; I
< CtorCall
->getNumArgs(); ++I
)
300 if (!CtorCall
->getArg(I
)->isDefaultArgument())
303 // Don't apply the check if the variable and its initializer have different
304 // replaced template parameter types. In this case the check triggers for a
305 // template instantiation where the substituted types are the same, but
306 // instantiations where the types differ and rely on implicit conversion would
307 // no longer compile if we switched to a reference.
308 if (differentReplacedTemplateParams(
309 Context
.Var
.getType(), constructorArgumentType(OldVar
, Result
.Nodes
),
313 if (OldVar
== nullptr) {
314 // `auto NewVar = functionCall();`
315 handleCopyFromMethodReturn(Context
, ObjectArg
);
317 // `auto NewVar = OldVar;`
318 handleCopyFromLocalVar(Context
, *OldVar
);
322 void UnnecessaryCopyInitialization::handleCopyFromMethodReturn(
323 const CheckContext
&Ctx
, const VarDecl
*ObjectArg
) {
324 bool IsConstQualified
= Ctx
.Var
.getType().isConstQualified();
325 if (!IsConstQualified
&& !Ctx
.IsVarOnlyUsedAsConst
)
327 if (ObjectArg
!= nullptr &&
328 !isInitializingVariableImmutable(*ObjectArg
, Ctx
.BlockStmt
, Ctx
.ASTCtx
,
329 ExcludedContainerTypes
))
331 diagnoseCopyFromMethodReturn(Ctx
);
334 void UnnecessaryCopyInitialization::handleCopyFromLocalVar(
335 const CheckContext
&Ctx
, const VarDecl
&OldVar
) {
336 if (!Ctx
.IsVarOnlyUsedAsConst
||
337 !isInitializingVariableImmutable(OldVar
, Ctx
.BlockStmt
, Ctx
.ASTCtx
,
338 ExcludedContainerTypes
))
340 diagnoseCopyFromLocalVar(Ctx
, OldVar
);
343 void UnnecessaryCopyInitialization::diagnoseCopyFromMethodReturn(
344 const CheckContext
&Ctx
) {
346 diag(Ctx
.Var
.getLocation(),
347 "the %select{|const qualified }0variable %1 is "
349 "from a const reference%select{%select{ but is only used as const "
350 "reference|}0| but is never used}2; consider "
351 "%select{making it a const reference|removing the statement}2")
352 << Ctx
.Var
.getType().isConstQualified() << &Ctx
.Var
<< Ctx
.IsVarUnused
;
353 maybeIssueFixes(Ctx
, Diagnostic
);
356 void UnnecessaryCopyInitialization::diagnoseCopyFromLocalVar(
357 const CheckContext
&Ctx
, const VarDecl
&OldVar
) {
359 diag(Ctx
.Var
.getLocation(),
360 "local copy %1 of the variable %0 is never modified%select{"
361 "| and never used}2; consider %select{avoiding the copy|removing "
363 << &OldVar
<< &Ctx
.Var
<< Ctx
.IsVarUnused
;
364 maybeIssueFixes(Ctx
, Diagnostic
);
367 void UnnecessaryCopyInitialization::maybeIssueFixes(
368 const CheckContext
&Ctx
, DiagnosticBuilder
&Diagnostic
) {
371 recordRemoval(Ctx
.VarDeclStmt
, Ctx
.ASTCtx
, Diagnostic
);
373 recordFixes(Ctx
.Var
, Ctx
.ASTCtx
, Diagnostic
);
377 void UnnecessaryCopyInitialization::storeOptions(
378 ClangTidyOptions::OptionMap
&Opts
) {
379 Options
.store(Opts
, "AllowedTypes",
380 utils::options::serializeStringList(AllowedTypes
));
381 Options
.store(Opts
, "ExcludedContainerTypes",
382 utils::options::serializeStringList(ExcludedContainerTypes
));
385 } // namespace clang::tidy::performance