brintos

brintos / llvm-project-archived public Read only

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