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.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 98 struct AssignmentPair { 99 const FieldDecl *Field; 100 const Expr *Init; 101 }; 102 103 static std::optional<AssignmentPair> 104 isAssignmentToMemberOf(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 144 PreferMemberInitializerCheck::PreferMemberInitializerCheck( 145 StringRef Name, ClangTidyContext *Context) 146 : ClangTidyCheck(Name, Context) {} 147 148 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { 149 Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()), 150 unless(isInstantiated()), 151 unless(isDelegatingConstructor())) 152 .bind("ctor"), 153 this); 154 } 155 156 void 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 (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 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 const Expr *InitValue = AssignmentToMember->Init; 195 updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields); 196 if (!canAdvanceAssignment(AssignedFields[Field])) 197 continue; 198 199 StringRef InsertPrefix = ""; 200 bool HasInitAlready = false; 201 SourceLocation InsertPos; 202 SourceRange ReplaceRange; 203 bool AddComma = false; 204 bool AddBrace = false; 205 bool InvalidFix = false; 206 unsigned Index = Field->getFieldIndex(); 207 const CXXCtorInitializer *LastInListInit = nullptr; 208 for (const CXXCtorInitializer *Init : Ctor->inits()) { 209 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 210 continue; 211 if (Init->getMember() == Field) { 212 HasInitAlready = true; 213 if (isa<ImplicitValueInitExpr>(Init->getInit())) 214 InsertPos = Init->getRParenLoc(); 215 else { 216 ReplaceRange = Init->getInit()->getSourceRange(); 217 AddBrace = isa<InitListExpr>(Init->getInit()); 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 if (AddBrace) 283 Diag << FixItHint::CreateReplacement(ReplaceRange, 284 ("{" + NewInit + "}").str()); 285 else 286 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 287 } else { 288 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit, 289 AddComma ? "), " : ")"}); 290 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 291 FirstToCtorInits); 292 FirstToCtorInits = areDiagsSelfContained(); 293 } 294 Diag << FixItHint::CreateRemoval( 295 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 296 } 297 } 298 299 } // namespace clang::tidy::cppcoreguidelines 300