brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.5 KiB · a257f53 Raw
339 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 "PassByValueCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/RecursiveASTVisitor.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "clang/Frontend/CompilerInstance.h"15#include "clang/Lex/Lexer.h"16#include "clang/Lex/Preprocessor.h"17 18using namespace clang::ast_matchers;19using namespace llvm;20 21namespace clang::tidy::modernize {22 23static bool isFirstFriendOfSecond(const CXXRecordDecl *Friend,24                                  const CXXRecordDecl *Class) {25  return llvm::any_of(26      Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {27        if (const TypeSourceInfo *FriendTypeSource =28                FriendDecl->getFriendType()) {29          const QualType FriendType = FriendTypeSource->getType();30          return FriendType->getAsCXXRecordDecl() == Friend;31        }32        return false;33      });34}35 36namespace {37/// Matches move-constructible classes whose constructor can be called inside38/// a CXXRecordDecl with a bound ID.39///40/// Given41/// \code42///   // POD types are trivially move constructible.43///   struct Foo { int a; };44///45///   struct Bar {46///     Bar(Bar &&) = deleted;47///     int a;48///   };49///50///   class Buz {51///     Buz(Buz &&);52///     int a;53///     friend class Outer;54///   };55///56///   class Outer {57///   };58/// \endcode59/// recordDecl(isMoveConstructibleInBoundCXXRecordDecl("Outer"))60///   matches "Foo", "Buz".61AST_MATCHER_P(CXXRecordDecl, isMoveConstructibleInBoundCXXRecordDecl, StringRef,62              RecordDeclID) {63  return Builder->removeBindings(64      [this,65       &Node](const ast_matchers::internal::BoundNodesMap &Nodes) -> bool {66        const auto *BoundClass =67            Nodes.getNode(this->RecordDeclID).get<CXXRecordDecl>();68        for (const CXXConstructorDecl *Ctor : Node.ctors()) {69          if (Ctor->isMoveConstructor() && !Ctor->isDeleted() &&70              (Ctor->getAccess() == AS_public ||71               (BoundClass && isFirstFriendOfSecond(BoundClass, &Node))))72            return false;73        }74        return true;75      });76}77} // namespace78 79static TypeMatcher notTemplateSpecConstRefType() {80  return lValueReferenceType(81      pointee(unless(templateSpecializationType()), isConstQualified()));82}83 84static TypeMatcher nonConstValueType() {85  return qualType(unless(anyOf(referenceType(), isConstQualified())));86}87 88/// Whether or not \p ParamDecl is used exactly one time in \p Ctor.89///90/// Checks both in the init-list and the body of the constructor.91static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor,92                                     const ParmVarDecl *ParamDecl) {93  /// \c clang::RecursiveASTVisitor that checks that the given94  /// \c ParmVarDecl is used exactly one time.95  ///96  /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()97  class ExactlyOneUsageVisitor98      : public RecursiveASTVisitor<ExactlyOneUsageVisitor> {99    friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>;100 101  public:102    ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl)103        : ParamDecl(ParamDecl) {}104 105    /// Whether or not the parameter variable is referred only once in106    /// the107    /// given constructor.108    bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) {109      Count = 0U;110      TraverseDecl(const_cast<CXXConstructorDecl *>(Ctor));111      return Count == 1U;112    }113 114  private:115    /// Counts the number of references to a variable.116    ///117    /// Stops the AST traversal if more than one usage is found.118    bool VisitDeclRefExpr(DeclRefExpr *D) {119      if (const ParmVarDecl *To = dyn_cast<ParmVarDecl>(D->getDecl())) {120        if (To == ParamDecl) {121          ++Count;122          if (Count > 1U) {123            // No need to look further, used more than once.124            return false;125          }126        }127      }128      return true;129    }130 131    const ParmVarDecl *ParamDecl;132    unsigned Count = 0U;133  };134 135  return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor);136}137 138/// Returns true if the given constructor is part of a lvalue/rvalue reference139/// pair, i.e. `Param` is of lvalue reference type, and there exists another140/// constructor such that:141///  - it has the same number of parameters as `Ctor`.142///  - the parameter at the same index as `Param` is an rvalue reference143///    of the same pointee type144///  - all other parameters have the same type as the corresponding parameter in145///    `Ctor` or are rvalue references with the same pointee type.146/// Examples:147///  A::A(const B& Param)148///  A::A(B&&)149///150///  A::A(const B& Param, const C&)151///  A::A(B&& Param, C&&)152///153///  A::A(const B&, const C& Param)154///  A::A(B&&, C&& Param)155///156///  A::A(const B&, const C& Param)157///  A::A(const B&, C&& Param)158///159///  A::A(const B& Param, int)160///  A::A(B&& Param, int)161static bool hasRValueOverload(const CXXConstructorDecl *Ctor,162                              const ParmVarDecl *Param) {163  if (!Param->getType().getCanonicalType()->isLValueReferenceType()) {164    // The parameter is passed by value.165    return false;166  }167  const int ParamIdx = Param->getFunctionScopeIndex();168  const CXXRecordDecl *Record = Ctor->getParent();169 170  // Check whether a ctor `C` forms a pair with `Ctor` under the aforementioned171  // rules.172  const auto IsRValueOverload = [&Ctor, ParamIdx](const CXXConstructorDecl *C) {173    if (C == Ctor || C->isDeleted() ||174        C->getNumParams() != Ctor->getNumParams())175      return false;176    for (int I = 0, E = C->getNumParams(); I < E; ++I) {177      const clang::QualType CandidateParamType =178          C->parameters()[I]->getType().getCanonicalType();179      const clang::QualType CtorParamType =180          Ctor->parameters()[I]->getType().getCanonicalType();181      const bool IsLValueRValuePair =182          CtorParamType->isLValueReferenceType() &&183          CandidateParamType->isRValueReferenceType() &&184          CandidateParamType->getPointeeType()->getUnqualifiedDesugaredType() ==185              CtorParamType->getPointeeType()->getUnqualifiedDesugaredType();186      if (I == ParamIdx) {187        // The parameter of interest must be paired.188        if (!IsLValueRValuePair)189          return false;190      } else {191        // All other parameters can be similar or paired.192        if (!(CandidateParamType == CtorParamType || IsLValueRValuePair))193          return false;194      }195    }196    return true;197  };198 199  for (const auto *Candidate : Record->ctors()) {200    if (IsRValueOverload(Candidate))201      return true;202  }203  return false;204}205 206/// Find all references to \p ParamDecl across all of the207/// redeclarations of \p Ctor.208static SmallVector<const ParmVarDecl *, 2>209collectParamDecls(const CXXConstructorDecl *Ctor,210                  const ParmVarDecl *ParamDecl) {211  SmallVector<const ParmVarDecl *, 2> Results;212  const unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();213 214  for (const FunctionDecl *Redecl : Ctor->redecls())215    Results.push_back(Redecl->getParamDecl(ParamIdx));216  return Results;217}218 219PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)220    : ClangTidyCheck(Name, Context),221      Inserter(Options.getLocalOrGlobal("IncludeStyle",222                                        utils::IncludeSorter::IS_LLVM),223               areDiagsSelfContained()),224      ValuesOnly(Options.get("ValuesOnly", false)) {}225 226void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {227  Options.store(Opts, "IncludeStyle", Inserter.getStyle());228  Options.store(Opts, "ValuesOnly", ValuesOnly);229}230 231void PassByValueCheck::registerMatchers(MatchFinder *Finder) {232  Finder->addMatcher(233      traverse(234          TK_AsIs,235          cxxConstructorDecl(236              ofClass(cxxRecordDecl().bind("outer")),237              forEachConstructorInitializer(238                  cxxCtorInitializer(239                      unless(isBaseInitializer()),240                      // Clang builds a CXXConstructExpr only when it knows241                      // which constructor will be called. In dependent contexts242                      // a ParenListExpr is generated instead of a243                      // CXXConstructExpr, filtering out templates automatically244                      // for us.245                      withInitializer(cxxConstructExpr(246                          has(ignoringParenImpCasts(declRefExpr(to(247                              parmVarDecl(248                                  hasType(qualType(249                                      // Match only const-ref or a non-const250                                      // value parameters. Rvalues,251                                      // TemplateSpecializationValues and252                                      // const-values shouldn't be modified.253                                      ValuesOnly254                                          ? nonConstValueType()255                                          : anyOf(notTemplateSpecConstRefType(),256                                                  nonConstValueType()))))257                                  .bind("Param"))))),258                          hasDeclaration(cxxConstructorDecl(259                              isCopyConstructor(), unless(isDeleted()),260                              hasDeclContext(cxxRecordDecl(261                                  isMoveConstructibleInBoundCXXRecordDecl(262                                      "outer"))))))))263                      .bind("Initializer")))264              .bind("Ctor")),265      this);266}267 268void PassByValueCheck::registerPPCallbacks(const SourceManager &SM,269                                           Preprocessor *PP,270                                           Preprocessor *ModuleExpanderPP) {271  Inserter.registerPreprocessor(PP);272}273 274void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {275  const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor");276  const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param");277  const auto *Initializer =278      Result.Nodes.getNodeAs<CXXCtorInitializer>("Initializer");279  const SourceManager &SM = *Result.SourceManager;280 281  // If the parameter is used or anything other than the copy, do not apply282  // the changes.283  if (!paramReferredExactlyOnce(Ctor, ParamDecl))284    return;285 286  // If the parameter is trivial to copy, don't move it. Moving a trivially287  // copyable type will cause a problem with performance-move-const-arg288  if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType(289          *Result.Context))290    return;291 292  // Do not trigger if we find a paired constructor with an rvalue.293  if (hasRValueOverload(Ctor, ParamDecl))294    return;295 296  auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");297 298  // If we received a `const&` type, we need to rewrite the function299  // declarations.300  if (ParamDecl->getType()->isLValueReferenceType()) {301    // Check if we can succesfully rewrite all declarations of the constructor.302    for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {303      const TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();304      auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();305      if (RefTL.isNull()) {306        // We cannot rewrite this instance. The type is probably hidden behind307        // some `typedef`. Do not offer a fix-it in this case.308        return;309      }310    }311    // Rewrite all declarations.312    for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {313      const TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();314      auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();315 316      const TypeLoc ValueTL = RefTL.getPointeeLoc();317      const CharSourceRange TypeRange = CharSourceRange::getTokenRange(318          ParmDecl->getBeginLoc(), ParamTL.getEndLoc());319      std::string ValueStr =320          Lexer::getSourceText(321              CharSourceRange::getTokenRange(ValueTL.getSourceRange()), SM,322              getLangOpts())323              .str();324      ValueStr += ' ';325      Diag << FixItHint::CreateReplacement(TypeRange, ValueStr);326    }327  }328 329  // Use std::move in the initialization list.330  Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")")331       << FixItHint::CreateInsertion(332              Initializer->getLParenLoc().getLocWithOffset(1), "std::move(")333       << Inserter.createIncludeInsertion(334              Result.SourceManager->getFileID(Initializer->getSourceLocation()),335              "<utility>");336}337 338} // namespace clang::tidy::modernize339