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