brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.2 KiB · 51a1468 Raw
303 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 "PreferMemberInitializerCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/Decl.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Lex/Lexer.h"14#include "llvm/ADT/DenseMap.h"15 16using namespace clang::ast_matchers;17 18namespace clang::tidy::cppcoreguidelines {19 20static bool isControlStatement(const Stmt *S) {21  return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,22             GotoStmt, CXXTryStmt, CXXThrowExpr>(S);23}24 25static bool isNoReturnCallStatement(const Stmt *S) {26  const auto *Call = dyn_cast<CallExpr>(S);27  if (!Call)28    return false;29 30  const FunctionDecl *Func = Call->getDirectCallee();31  if (!Func)32    return false;33 34  return Func->isNoReturn();35}36 37namespace {38 39AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {40  return Node.getFieldIndex() >= Index;41}42 43enum class AssignedLevel {44  // Field is not assigned.45  None,46  // Field is assigned.47  Default,48  // Assignment of field has side effect:49  // - assign to reference.50  // FIXME: support other side effect.51  HasSideEffect,52  // Assignment of field has data dependence.53  HasDependence,54};55 56} // namespace57 58static bool canAdvanceAssignment(AssignedLevel Level) {59  return Level == AssignedLevel::None || Level == AssignedLevel::Default;60}61 62// Checks if Field is initialised using a field that will be initialised after63// it.64// TODO: Probably should guard against function calls that could have side65// effects or if they do reference another field that's initialized before66// this field, but is modified before the assignment.67static void updateAssignmentLevel(68    const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor,69    llvm::DenseMap<const FieldDecl *, AssignedLevel> &AssignedFields) {70  auto It = AssignedFields.try_emplace(Field, AssignedLevel::None).first;71 72  if (!canAdvanceAssignment(It->second))73    // fast path for already decided field.74    return;75 76  if (Field->getType().getCanonicalType()->isReferenceType()) {77    // assign to reference type twice cannot be simplified to once.78    It->second = AssignedLevel::HasSideEffect;79    return;80  }81 82  auto MemberMatcher =83      memberExpr(hasObjectExpression(cxxThisExpr()),84                 member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));85  auto DeclMatcher = declRefExpr(86      to(valueDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor)))));87  const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher,88                                               hasDescendant(MemberMatcher),89                                               hasDescendant(DeclMatcher))),90                                    *Init, Field->getASTContext())91                                  .empty();92  if (HasDependence) {93    It->second = AssignedLevel::HasDependence;94    return;95  }96}97 98struct AssignmentPair {99  const FieldDecl *Field;100  const Expr *Init;101};102 103static std::optional<AssignmentPair>104isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,105                       const CXXConstructorDecl *Ctor) {106  if (const auto *BO = dyn_cast<BinaryOperator>(S)) {107    if (BO->getOpcode() != BO_Assign)108      return {};109 110    const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());111    if (!ME)112      return {};113 114    const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());115    if (!Field)116      return {};117 118    if (!isa<CXXThisExpr>(ME->getBase()))119      return {};120    const Expr *Init = BO->getRHS()->IgnoreParenImpCasts();121    return AssignmentPair{Field, Init};122  }123  if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {124    if (COCE->getOperator() != OO_Equal)125      return {};126 127    const auto *ME =128        dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());129    if (!ME)130      return {};131 132    const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());133    if (!Field)134      return {};135 136    if (!isa<CXXThisExpr>(ME->getBase()))137      return {};138    const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts();139    return AssignmentPair{Field, Init};140  }141  return {};142}143 144PreferMemberInitializerCheck::PreferMemberInitializerCheck(145    StringRef Name, ClangTidyContext *Context)146    : ClangTidyCheck(Name, Context) {}147 148void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {149  Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()),150                                        unless(isInstantiated()),151                                        unless(isDelegatingConstructor()))152                         .bind("ctor"),153                     this);154}155 156void PreferMemberInitializerCheck::check(157    const MatchFinder::MatchResult &Result) {158  const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");159  const auto *Body = cast<CompoundStmt>(Ctor->getBody());160 161  const CXXRecordDecl *Class = Ctor->getParent();162  bool FirstToCtorInits = true;163 164  llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{};165 166  for (const CXXCtorInitializer *Init : Ctor->inits())167    if (const FieldDecl *Field = Init->getMember())168      updateAssignmentLevel(Field, Init->getInit(), Ctor, AssignedFields);169 170  for (const Stmt *S : Body->body()) {171    if (S->getBeginLoc().isMacroID()) {172      const StringRef MacroName = Lexer::getImmediateMacroName(173          S->getBeginLoc(), *Result.SourceManager, getLangOpts());174      if (MacroName.contains_insensitive("assert"))175        return;176    }177    if (isControlStatement(S))178      return;179 180    if (isNoReturnCallStatement(S))181      return;182 183    if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {184      if (isNoReturnCallStatement(CondOp->getLHS()) ||185          isNoReturnCallStatement(CondOp->getRHS()))186        return;187    }188 189    std::optional<AssignmentPair> AssignmentToMember =190        isAssignmentToMemberOf(Class, S, Ctor);191    if (!AssignmentToMember)192      continue;193    const FieldDecl *Field = AssignmentToMember->Field;194    // Skip if the field is inherited from a base class.195    if (Field->getParent() != Class)196      continue;197    const Expr *InitValue = AssignmentToMember->Init;198    updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields);199    if (!canAdvanceAssignment(AssignedFields[Field]))200      continue;201 202    StringRef InsertPrefix = "";203    bool HasInitAlready = false;204    SourceLocation InsertPos;205    SourceRange ReplaceRange;206    bool AddComma = false;207    bool AddBrace = false;208    bool InvalidFix = false;209    const unsigned Index = Field->getFieldIndex();210    const CXXCtorInitializer *LastInListInit = nullptr;211    for (const CXXCtorInitializer *Init : Ctor->inits()) {212      if (!Init->isWritten() || Init->isInClassMemberInitializer())213        continue;214      if (Init->getMember() == Field) {215        HasInitAlready = true;216        if (isa<ImplicitValueInitExpr>(Init->getInit()))217          InsertPos = Init->getRParenLoc();218        else {219          ReplaceRange = Init->getInit()->getSourceRange();220          AddBrace = isa<InitListExpr>(Init->getInit());221        }222        break;223      }224      if (Init->isMemberInitializer() &&225          Index < Init->getMember()->getFieldIndex()) {226        InsertPos = Init->getSourceLocation();227        // There are initializers after the one we are inserting, so add a228        // comma after this insertion in order to not break anything.229        AddComma = true;230        break;231      }232      LastInListInit = Init;233    }234    if (HasInitAlready) {235      if (InsertPos.isValid())236        InvalidFix |= InsertPos.isMacroID();237      else238        InvalidFix |= ReplaceRange.getBegin().isMacroID() ||239                      ReplaceRange.getEnd().isMacroID();240    } else {241      if (InsertPos.isInvalid()) {242        if (LastInListInit) {243          InsertPos =244              Lexer::getLocForEndOfToken(LastInListInit->getRParenLoc(), 0,245                                         *Result.SourceManager, getLangOpts());246          // Inserting after the last constructor initializer, so we need a247          // comma.248          InsertPrefix = ", ";249        } else {250          InsertPos = Lexer::getLocForEndOfToken(251              Ctor->getTypeSourceInfo()252                  ->getTypeLoc()253                  .getAs<clang::FunctionTypeLoc>()254                  .getLocalRangeEnd(),255              0, *Result.SourceManager, getLangOpts());256 257          // If this is first time in the loop, there are no initializers so258          // `:` declares member initialization list. If this is a259          // subsequent pass then we have already inserted a `:` so continue260          // with a comma.261          InsertPrefix = FirstToCtorInits ? " : " : ", ";262        }263      }264      InvalidFix |= InsertPos.isMacroID();265    }266 267    SourceLocation SemiColonEnd;268    if (auto NextToken = Lexer::findNextToken(269            S->getEndLoc(), *Result.SourceManager, getLangOpts()))270      SemiColonEnd = NextToken->getEndLoc();271    else272      InvalidFix = true;273 274    auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member"275                                       " initializer of the constructor")276                << Field;277    if (InvalidFix)278      continue;279    const StringRef NewInit = Lexer::getSourceText(280        Result.SourceManager->getExpansionRange(InitValue->getSourceRange()),281        *Result.SourceManager, getLangOpts());282    if (HasInitAlready) {283      if (InsertPos.isValid())284        Diag << FixItHint::CreateInsertion(InsertPos, NewInit);285      else if (AddBrace)286        Diag << FixItHint::CreateReplacement(ReplaceRange,287                                             ("{" + NewInit + "}").str());288      else289        Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);290    } else {291      const SmallString<128> Insertion({InsertPrefix, Field->getName(), "(",292                                        NewInit, AddComma ? "), " : ")"});293      Diag << FixItHint::CreateInsertion(InsertPos, Insertion,294                                         FirstToCtorInits);295      FirstToCtorInits = areDiagsSelfContained();296    }297    Diag << FixItHint::CreateRemoval(298        CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));299  }300}301 302} // namespace clang::tidy::cppcoreguidelines303