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