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