brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.4 KiB · fde9c73 Raw
363 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 "UseEqualsDefaultCheck.h"10#include "../utils/LexerUtils.h"11#include "../utils/Matchers.h"12#include "clang/AST/ASTContext.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/Lex/Lexer.h"15#include <optional>16 17using namespace clang::ast_matchers;18 19namespace clang::tidy::modernize {20 21static const char SpecialFunction[] = "SpecialFunction";22 23/// Finds all the named non-static fields of \p Record.24static std::set<const FieldDecl *>25getAllNamedFields(const CXXRecordDecl *Record) {26  std::set<const FieldDecl *> Result;27  for (const auto *Field : Record->fields()) {28    // Static data members are not in this range.29    if (Field->isUnnamedBitField())30      continue;31    Result.insert(Field);32  }33  return Result;34}35 36/// Returns the names of the direct bases of \p Record, both virtual and37/// non-virtual.38static std::set<const Type *> getAllDirectBases(const CXXRecordDecl *Record) {39  std::set<const Type *> Result;40  for (auto Base : Record->bases()) {41    // CXXBaseSpecifier.42    const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr();43    Result.insert(BaseType);44  }45  return Result;46}47 48/// Returns a matcher that matches member expressions where the base is49/// the variable declared as \p Var and the accessed member is the one declared50/// as \p Field.51static internal::Matcher<Expr> accessToFieldInVar(const FieldDecl *Field,52                                                  const ValueDecl *Var) {53  return ignoringImpCasts(54      memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))),55                 member(fieldDecl(equalsNode(Field)))));56}57 58/// Check that the given constructor has copy signature and that it59/// copy-initializes all its bases and members.60static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context,61                                               const CXXConstructorDecl *Ctor) {62  // An explicitly-defaulted constructor cannot have default arguments.63  if (Ctor->getMinRequiredArguments() != 1)64    return false;65 66  const auto *Record = Ctor->getParent();67  const auto *Param = Ctor->getParamDecl(0);68 69  // Base classes and members that have to be copied.70  auto BasesToInit = getAllDirectBases(Record);71  auto FieldsToInit = getAllNamedFields(Record);72 73  // Ensure that all the bases are copied.74  for (const auto *Base : BasesToInit) {75    // The initialization of a base class should be a call to a copy76    // constructor of the base.77    if (match(78            traverse(TK_AsIs,79                     cxxConstructorDecl(80                         forEachConstructorInitializer(cxxCtorInitializer(81                             isBaseInitializer(),82                             withInitializer(cxxConstructExpr(83                                 hasType(equalsNode(Base)),84                                 hasDeclaration(85                                     cxxConstructorDecl(isCopyConstructor())),86                                 argumentCountIs(1),87                                 hasArgument(0, declRefExpr(to(varDecl(88                                                    equalsNode(Param))))))))))),89            *Ctor, *Context)90            .empty())91      return false;92  }93 94  // Ensure that all the members are copied.95  for (const auto *Field : FieldsToInit) {96    auto AccessToFieldInParam = accessToFieldInVar(Field, Param);97    // The initialization is a CXXConstructExpr for class types.98    if (match(traverse(99                  TK_AsIs,100                  cxxConstructorDecl(101                      forEachConstructorInitializer(cxxCtorInitializer(102                          isMemberInitializer(), forField(equalsNode(Field)),103                          withInitializer(anyOf(104                              AccessToFieldInParam,105                              initListExpr(has(AccessToFieldInParam)),106                              cxxConstructExpr(107                                  hasDeclaration(108                                      cxxConstructorDecl(isCopyConstructor())),109                                  argumentCountIs(1),110                                  hasArgument(0, AccessToFieldInParam)))))))),111              *Ctor, *Context)112            .empty())113      return false;114  }115 116  // Ensure that we don't do anything else, like initializing an indirect base.117  return Ctor->getNumCtorInitializers() ==118         BasesToInit.size() + FieldsToInit.size();119}120 121/// Checks that the given method is an overloading of the assignment122/// operator, has copy signature, returns a reference to "*this" and copies123/// all its members and subobjects.124static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context,125                                              const CXXMethodDecl *Operator) {126  const auto *Record = Operator->getParent();127  const auto *Param = Operator->getParamDecl(0);128 129  // Base classes and members that have to be copied.130  auto BasesToInit = getAllDirectBases(Record);131  auto FieldsToInit = getAllNamedFields(Record);132 133  const auto *Compound = cast<CompoundStmt>(Operator->getBody());134 135  // The assignment operator definition has to end with the following return136  // statement:137  //   return *this;138  if (Compound->body_empty() ||139      match(traverse(140                TK_AsIs,141                returnStmt(has(ignoringParenImpCasts(unaryOperator(142                    hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())))))),143            *Compound->body_back(), *Context)144          .empty())145    return false;146 147  // Ensure that all the bases are copied.148  for (const auto *Base : BasesToInit) {149    // Assignment operator of a base class:150    //   Base::operator=(Other);151    //152    // Clang translates this into:153    //   ((Base*)this)->operator=((Base)Other);154    //155    // So we are looking for a member call that fulfills:156    if (match(traverse(157                  TK_AsIs,158                  compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(159                      // - The object is an implicit cast of 'this' to a160                      // pointer to161                      //   a base class.162                      onImplicitObjectArgument(implicitCastExpr(163                          hasImplicitDestinationType(hasCanonicalType(pointsTo(164                              type(equalsNode(Base->getCanonicalTypeInternal()165                                                  .getTypePtr()))))),166                          hasSourceExpression(cxxThisExpr()))),167                      // - The called method is the operator=.168                      callee(cxxMethodDecl(isCopyAssignmentOperator())),169                      // - The argument is (an implicit cast to a Base of)170                      // the argument taken by "Operator".171                      argumentCountIs(1),172                      hasArgument(173                          0, declRefExpr(to(varDecl(equalsNode(Param)))))))))),174              *Compound, *Context)175            .empty())176      return false;177  }178 179  // Ensure that all the members are copied.180  for (const auto *Field : FieldsToInit) {181    // The assignment of data members:182    //   Field = Other.Field;183    // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr184    // otherwise.185    auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()),186                          member(fieldDecl(equalsNode(Field))));187    auto RHS = accessToFieldInVar(Field, Param);188    if (match(traverse(TK_AsIs,189                       compoundStmt(has(ignoringParenImpCasts(binaryOperation(190                           hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)))))),191              *Compound, *Context)192            .empty())193      return false;194  }195 196  // Ensure that we don't do anything else.197  return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1;198}199 200/// Returns false if the body has any non-whitespace character.201static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {202  bool Invalid = false;203  const StringRef Text = Lexer::getSourceText(204      CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),205                                    Body->getRBracLoc()),206      Context->getSourceManager(), Context->getLangOpts(), &Invalid);207  return !Invalid && Text.ltrim(" \t\r\n").empty();208}209 210UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name,211                                             ClangTidyContext *Context)212    : ClangTidyCheck(Name, Context),213      IgnoreMacros(Options.get("IgnoreMacros", true)) {}214 215void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {216  Options.store(Opts, "IgnoreMacros", IgnoreMacros);217}218 219namespace {220AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }221} // namespace222 223void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) {224  // Skip unions/union-like classes since their constructors behave differently225  // when defaulted vs. empty.226  auto IsUnionLikeClass = recordDecl(227      anyOf(isUnion(),228            has(fieldDecl(isImplicit(), hasType(cxxRecordDecl(isUnion()))))));229 230  const LangOptions &LangOpts = getLangOpts();231  auto IsPublicOrOutOfLineUntilCPP20 =232      LangOpts.CPlusPlus20233          ? cxxConstructorDecl()234          : cxxConstructorDecl(anyOf(isOutOfLine(), isPublic()));235 236  // Destructor.237  Finder->addMatcher(238      cxxDestructorDecl(isDefinition(), unless(ofClass(IsUnionLikeClass)))239          .bind(SpecialFunction),240      this);241  // Constructor.242  Finder->addMatcher(243      cxxConstructorDecl(244          isDefinition(), unless(ofClass(IsUnionLikeClass)),245          unless(hasParent(functionTemplateDecl())),246          anyOf(247              // Default constructor.248              allOf(parameterCountIs(0),249                    unless(hasAnyConstructorInitializer(isWritten())),250                    unless(isVariadic()), IsPublicOrOutOfLineUntilCPP20),251              // Copy constructor.252              allOf(isCopyConstructor(),253                    // Discard constructors that can be used as a copy254                    // constructor because all the other arguments have255                    // default values.256                    parameterCountIs(1))))257          .bind(SpecialFunction),258      this);259  // Copy-assignment operator.260  Finder->addMatcher(261      cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(),262                    unless(ofClass(IsUnionLikeClass)),263                    unless(hasParent(functionTemplateDecl())),264                    // isCopyAssignmentOperator() allows the parameter to be265                    // passed by value, and in this case it cannot be266                    // defaulted.267                    hasParameter(0, hasType(lValueReferenceType())),268                    // isCopyAssignmentOperator() allows non lvalue reference269                    // return types, and in this case it cannot be defaulted.270                    returns(qualType(hasCanonicalType(271                        allOf(lValueReferenceType(pointee(type())),272                              unless(matchers::isReferenceToConst()))))))273          .bind(SpecialFunction),274      this);275}276 277void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {278  // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.279  const auto *SpecialFunctionDecl =280      Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction);281 282  if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID())283    return;284 285  // Discard explicitly deleted/defaulted special member functions and those286  // that are not user-provided (automatically generated).287  if (SpecialFunctionDecl->isDeleted() ||288      SpecialFunctionDecl->isExplicitlyDefaulted() ||289      SpecialFunctionDecl->isLateTemplateParsed() ||290      SpecialFunctionDecl->isTemplateInstantiation() ||291      !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody())292    return;293 294  const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());295  if (!Body)296    return;297 298  // If there is code inside the body, don't warn.299  if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty())300    return;301 302  // If body contain any preprocesor derictives, don't warn.303  if (IgnoreMacros && utils::lexer::rangeContainsExpansionsOrDirectives(304                          Body->getSourceRange(), *Result.SourceManager,305                          Result.Context->getLangOpts()))306    return;307 308  // If there are comments inside the body, don't do the change.309  const bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||310                        bodyEmpty(Result.Context, Body);311 312  std::vector<FixItHint> RemoveInitializers;313  unsigned MemberType = 0;314  if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {315    if (Ctor->getNumParams() == 0) {316      MemberType = 0;317    } else {318      if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))319        return;320      MemberType = 1;321      // If there are constructor initializers, they must be removed.322      for (const auto *Init : Ctor->inits()) {323        RemoveInitializers.emplace_back(324            FixItHint::CreateRemoval(Init->getSourceRange()));325      }326    }327  } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {328    MemberType = 2;329  } else {330    if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))331      return;332    MemberType = 3;333  }334 335  // The location of the body is more useful inside a macro as spelling and336  // expansion locations are reported.337  SourceLocation Location = SpecialFunctionDecl->getLocation();338  if (Location.isMacroID())339    Location = Body->getBeginLoc();340 341  auto Diag = diag(342      Location,343      "use '= default' to define a trivial %select{default constructor|copy "344      "constructor|destructor|copy-assignment operator}0");345  Diag << MemberType;346 347  if (ApplyFix) {348    const SourceLocation UnifiedEnd = utils::lexer::getUnifiedEndLoc(349        *Body, Result.Context->getSourceManager(),350        Result.Context->getLangOpts());351    // Skipping comments, check for a semicolon after Body->getSourceRange()352    std::optional<Token> Token = utils::lexer::findNextTokenSkippingComments(353        UnifiedEnd, Result.Context->getSourceManager(),354        Result.Context->getLangOpts());355    const StringRef Replacement =356        Token && Token->is(tok::semi) ? "= default" : "= default;";357    Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement)358         << RemoveInitializers;359  }360}361 362} // namespace clang::tidy::modernize363