ForRangeCopyCheck.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. //===--- ForRangeCopyCheck.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 "ForRangeCopyCheck.h"
  9. #include "../utils/DeclRefExprUtils.h"
  10. #include "../utils/FixItHintUtils.h"
  11. #include "../utils/Matchers.h"
  12. #include "../utils/OptionsUtils.h"
  13. #include "../utils/TypeTraits.h"
  14. #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
  15. #include "clang/Basic/Diagnostic.h"
  16. #include <optional>
  17. using namespace clang::ast_matchers;
  18. namespace clang::tidy::performance {
  19. ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context)
  20. : ClangTidyCheck(Name, Context),
  21. WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", false)),
  22. AllowedTypes(
  23. utils::options::parseStringList(Options.get("AllowedTypes", ""))) {}
  24. void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  25. Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies);
  26. Options.store(Opts, "AllowedTypes",
  27. utils::options::serializeStringList(AllowedTypes));
  28. }
  29. void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) {
  30. // Match loop variables that are not references or pointers or are already
  31. // initialized through MaterializeTemporaryExpr which indicates a type
  32. // conversion.
  33. auto HasReferenceOrPointerTypeOrIsAllowed = hasType(qualType(
  34. unless(anyOf(hasCanonicalType(anyOf(referenceType(), pointerType())),
  35. hasDeclaration(namedDecl(
  36. matchers::matchesAnyListedName(AllowedTypes)))))));
  37. auto IteratorReturnsValueType = cxxOperatorCallExpr(
  38. hasOverloadedOperatorName("*"),
  39. callee(
  40. cxxMethodDecl(returns(unless(hasCanonicalType(referenceType()))))));
  41. auto NotConstructedByCopy = cxxConstructExpr(
  42. hasDeclaration(cxxConstructorDecl(unless(isCopyConstructor()))));
  43. auto ConstructedByConversion = cxxMemberCallExpr(callee(cxxConversionDecl()));
  44. auto LoopVar =
  45. varDecl(HasReferenceOrPointerTypeOrIsAllowed,
  46. unless(hasInitializer(expr(hasDescendant(expr(
  47. anyOf(materializeTemporaryExpr(), IteratorReturnsValueType,
  48. NotConstructedByCopy, ConstructedByConversion)))))));
  49. Finder->addMatcher(
  50. traverse(TK_AsIs,
  51. cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar")))
  52. .bind("forRange")),
  53. this);
  54. }
  55. void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
  56. const auto *Var = Result.Nodes.getNodeAs<VarDecl>("loopVar");
  57. // Ignore code in macros since we can't place the fixes correctly.
  58. if (Var->getBeginLoc().isMacroID())
  59. return;
  60. if (handleConstValueCopy(*Var, *Result.Context))
  61. return;
  62. const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>("forRange");
  63. handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context);
  64. }
  65. bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
  66. ASTContext &Context) {
  67. if (WarnOnAllAutoCopies) {
  68. // For aggressive check just test that loop variable has auto type.
  69. if (!isa<AutoType>(LoopVar.getType()))
  70. return false;
  71. } else if (!LoopVar.getType().isConstQualified()) {
  72. return false;
  73. }
  74. std::optional<bool> Expensive =
  75. utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
  76. if (!Expensive || !*Expensive)
  77. return false;
  78. auto Diagnostic =
  79. diag(LoopVar.getLocation(),
  80. "the loop variable's type is not a reference type; this creates a "
  81. "copy in each iteration; consider making this a reference")
  82. << utils::fixit::changeVarDeclToReference(LoopVar, Context);
  83. if (!LoopVar.getType().isConstQualified()) {
  84. if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl(
  85. LoopVar, Context, DeclSpec::TQ::TQ_const))
  86. Diagnostic << *Fix;
  87. }
  88. return true;
  89. }
  90. bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
  91. const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
  92. ASTContext &Context) {
  93. std::optional<bool> Expensive =
  94. utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
  95. if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive)
  96. return false;
  97. // We omit the case where the loop variable is not used in the loop body. E.g.
  98. //
  99. // for (auto _ : benchmark_state) {
  100. // }
  101. //
  102. // Because the fix (changing to `const auto &`) will introduce an unused
  103. // compiler warning which can't be suppressed.
  104. // Since this case is very rare, it is safe to ignore it.
  105. if (!ExprMutationAnalyzer(*ForRange.getBody(), Context).isMutated(&LoopVar) &&
  106. !utils::decl_ref_expr::allDeclRefExprs(LoopVar, *ForRange.getBody(),
  107. Context)
  108. .empty()) {
  109. auto Diag = diag(
  110. LoopVar.getLocation(),
  111. "loop variable is copied but only used as const reference; consider "
  112. "making it a const reference");
  113. if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl(
  114. LoopVar, Context, DeclSpec::TQ::TQ_const))
  115. Diag << *Fix << utils::fixit::changeVarDeclToReference(LoopVar, Context);
  116. return true;
  117. }
  118. return false;
  119. }
  120. } // namespace clang::tidy::performance