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 AddBrace = false; 207 bool InvalidFix = false; 208 unsigned Index = Field->getFieldIndex(); 209 const CXXCtorInitializer *LastInListInit = nullptr; 210 for (const CXXCtorInitializer *Init : Ctor->inits()) { 211 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 212 continue; 213 if (Init->getMember() == Field) { 214 HasInitAlready = true; 215 if (isa<ImplicitValueInitExpr>(Init->getInit())) 216 InsertPos = Init->getRParenLoc(); 217 else { 218 ReplaceRange = Init->getInit()->getSourceRange(); 219 AddBrace = isa<InitListExpr>(Init->getInit()); 220 } 221 break; 222 } 223 if (Init->isMemberInitializer() && 224 Index < Init->getMember()->getFieldIndex()) { 225 InsertPos = Init->getSourceLocation(); 226 // There are initializers after the one we are inserting, so add a 227 // comma after this insertion in order to not break anything. 228 AddComma = true; 229 break; 230 } 231 LastInListInit = Init; 232 } 233 if (HasInitAlready) { 234 if (InsertPos.isValid()) 235 InvalidFix |= InsertPos.isMacroID(); 236 else 237 InvalidFix |= ReplaceRange.getBegin().isMacroID() || 238 ReplaceRange.getEnd().isMacroID(); 239 } else { 240 if (InsertPos.isInvalid()) { 241 if (LastInListInit) { 242 InsertPos = 243 Lexer::getLocForEndOfToken(LastInListInit->getRParenLoc(), 0, 244 *Result.SourceManager, getLangOpts()); 245 // Inserting after the last constructor initializer, so we need a 246 // comma. 247 InsertPrefix = ", "; 248 } else { 249 InsertPos = Lexer::getLocForEndOfToken( 250 Ctor->getTypeSourceInfo() 251 ->getTypeLoc() 252 .getAs<clang::FunctionTypeLoc>() 253 .getLocalRangeEnd(), 254 0, *Result.SourceManager, getLangOpts()); 255 256 // If this is first time in the loop, there are no initializers so 257 // `:` declares member initialization list. If this is a 258 // subsequent pass then we have already inserted a `:` so continue 259 // with a comma. 260 InsertPrefix = FirstToCtorInits ? " : " : ", "; 261 } 262 } 263 InvalidFix |= InsertPos.isMacroID(); 264 } 265 266 SourceLocation SemiColonEnd; 267 if (auto NextToken = Lexer::findNextToken( 268 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 269 SemiColonEnd = NextToken->getEndLoc(); 270 else 271 InvalidFix = true; 272 273 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member" 274 " initializer of the constructor") 275 << Field; 276 if (InvalidFix) 277 continue; 278 StringRef NewInit = Lexer::getSourceText( 279 Result.SourceManager->getExpansionRange(InitValue->getSourceRange()), 280 *Result.SourceManager, getLangOpts()); 281 if (HasInitAlready) { 282 if (InsertPos.isValid()) 283 Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 284 else if (AddBrace) 285 Diag << FixItHint::CreateReplacement(ReplaceRange, 286 ("{" + NewInit + "}").str()); 287 else 288 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 289 } else { 290 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit, 291 AddComma ? "), " : ")"}); 292 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 293 FirstToCtorInits); 294 FirstToCtorInits = areDiagsSelfContained(); 295 } 296 Diag << FixItHint::CreateRemoval( 297 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 298 } 299 } 300 301 } // namespace clang::tidy::cppcoreguidelines 302