UnnecessaryCopyInitialization.cpp 16 KB

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