14fc0214aSAdam Balogh //===--- PreferMemberInitializerCheck.cpp - clang-tidy -------------------===// 24fc0214aSAdam Balogh // 34fc0214aSAdam Balogh // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 44fc0214aSAdam Balogh // See https://llvm.org/LICENSE.txt for license information. 54fc0214aSAdam Balogh // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 64fc0214aSAdam Balogh // 74fc0214aSAdam Balogh //===----------------------------------------------------------------------===// 84fc0214aSAdam Balogh 94fc0214aSAdam Balogh #include "PreferMemberInitializerCheck.h" 104fc0214aSAdam Balogh #include "clang/AST/ASTContext.h" 1126374675SCongcong Cai #include "clang/AST/Decl.h" 124fc0214aSAdam Balogh #include "clang/ASTMatchers/ASTMatchFinder.h" 134fc0214aSAdam Balogh #include "clang/Lex/Lexer.h" 1426374675SCongcong Cai #include "llvm/ADT/DenseMap.h" 154fc0214aSAdam Balogh 164fc0214aSAdam Balogh using namespace clang::ast_matchers; 174fc0214aSAdam Balogh 187d2ea6c4SCarlos Galvez namespace clang::tidy::cppcoreguidelines { 194fc0214aSAdam Balogh 204fc0214aSAdam Balogh static bool isControlStatement(const Stmt *S) { 214fc0214aSAdam Balogh return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt, 224fc0214aSAdam Balogh GotoStmt, CXXTryStmt, CXXThrowExpr>(S); 234fc0214aSAdam Balogh } 244fc0214aSAdam Balogh 254fc0214aSAdam Balogh static bool isNoReturnCallStatement(const Stmt *S) { 264fc0214aSAdam Balogh const auto *Call = dyn_cast<CallExpr>(S); 274fc0214aSAdam Balogh if (!Call) 284fc0214aSAdam Balogh return false; 294fc0214aSAdam Balogh 304fc0214aSAdam Balogh const FunctionDecl *Func = Call->getDirectCallee(); 314fc0214aSAdam Balogh if (!Func) 324fc0214aSAdam Balogh return false; 334fc0214aSAdam Balogh 344fc0214aSAdam Balogh return Func->isNoReturn(); 354fc0214aSAdam Balogh } 364fc0214aSAdam Balogh 37a2e15fa5SNathan James namespace { 3826374675SCongcong Cai 39a2e15fa5SNathan James AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) { 40a2e15fa5SNathan James return Node.getFieldIndex() >= Index; 41a2e15fa5SNathan James } 4226374675SCongcong Cai 4326374675SCongcong Cai enum class AssignedLevel { 4426374675SCongcong Cai // Field is not assigned. 4526374675SCongcong Cai None, 4626374675SCongcong Cai // Field is assigned. 4726374675SCongcong Cai Default, 4826374675SCongcong Cai // Assignment of field has side effect: 4926374675SCongcong Cai // - assign to reference. 5026374675SCongcong Cai // FIXME: support other side effect. 5126374675SCongcong Cai HasSideEffect, 5226374675SCongcong Cai // Assignment of field has data dependence. 5326374675SCongcong Cai HasDependence, 5426374675SCongcong Cai }; 5526374675SCongcong Cai 56a2e15fa5SNathan James } // namespace 57a2e15fa5SNathan James 5826374675SCongcong Cai static bool canAdvanceAssignment(AssignedLevel Level) { 5926374675SCongcong Cai return Level == AssignedLevel::None || Level == AssignedLevel::Default; 6026374675SCongcong Cai } 6126374675SCongcong Cai 62a2e15fa5SNathan James // Checks if Field is initialised using a field that will be initialised after 63a2e15fa5SNathan James // it. 64a2e15fa5SNathan James // TODO: Probably should guard against function calls that could have side 6526374675SCongcong Cai // effects or if they do reference another field that's initialized before 6626374675SCongcong Cai // this field, but is modified before the assignment. 6726374675SCongcong Cai static void updateAssignmentLevel( 6826374675SCongcong Cai const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor, 6926374675SCongcong Cai llvm::DenseMap<const FieldDecl *, AssignedLevel> &AssignedFields) { 7033ceb2ddSKazu Hirata auto It = AssignedFields.try_emplace(Field, AssignedLevel::None).first; 7126374675SCongcong Cai 7226374675SCongcong Cai if (!canAdvanceAssignment(It->second)) 7326374675SCongcong Cai // fast path for already decided field. 7426374675SCongcong Cai return; 7526374675SCongcong Cai 7626374675SCongcong Cai if (Field->getType().getCanonicalType()->isReferenceType()) { 7726374675SCongcong Cai // assign to reference type twice cannot be simplified to once. 7826374675SCongcong Cai It->second = AssignedLevel::HasSideEffect; 7926374675SCongcong Cai return; 8026374675SCongcong Cai } 81a2e15fa5SNathan James 82a2e15fa5SNathan James auto MemberMatcher = 83a2e15fa5SNathan James memberExpr(hasObjectExpression(cxxThisExpr()), 84a2e15fa5SNathan James member(fieldDecl(indexNotLessThan(Field->getFieldIndex())))); 85a2e15fa5SNathan James auto DeclMatcher = declRefExpr( 86*7deca859SCongcong Cai to(valueDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor))))); 8726374675SCongcong Cai const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher, 88a2e15fa5SNathan James hasDescendant(MemberMatcher), 89a2e15fa5SNathan James hasDescendant(DeclMatcher))), 90a2e15fa5SNathan James *Init, Field->getASTContext()) 91a2e15fa5SNathan James .empty(); 9226374675SCongcong Cai if (HasDependence) { 9326374675SCongcong Cai It->second = AssignedLevel::HasDependence; 9426374675SCongcong Cai return; 9526374675SCongcong Cai } 96a2e15fa5SNathan James } 97a2e15fa5SNathan James 9843e13fdcSCongcong Cai struct AssignmentPair { 9943e13fdcSCongcong Cai const FieldDecl *Field; 10043e13fdcSCongcong Cai const Expr *Init; 10143e13fdcSCongcong Cai }; 10243e13fdcSCongcong Cai 10343e13fdcSCongcong Cai static std::optional<AssignmentPair> 104a2e15fa5SNathan James isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, 105a2e15fa5SNathan James const CXXConstructorDecl *Ctor) { 1064fc0214aSAdam Balogh if (const auto *BO = dyn_cast<BinaryOperator>(S)) { 1074fc0214aSAdam Balogh if (BO->getOpcode() != BO_Assign) 10843e13fdcSCongcong Cai return {}; 1094fc0214aSAdam Balogh 1104fc0214aSAdam Balogh const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); 1114fc0214aSAdam Balogh if (!ME) 11243e13fdcSCongcong Cai return {}; 1134fc0214aSAdam Balogh 1144fc0214aSAdam Balogh const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 1154fc0214aSAdam Balogh if (!Field) 11643e13fdcSCongcong Cai return {}; 1174fc0214aSAdam Balogh 118a2e15fa5SNathan James if (!isa<CXXThisExpr>(ME->getBase())) 11943e13fdcSCongcong Cai return {}; 120a2e15fa5SNathan James const Expr *Init = BO->getRHS()->IgnoreParenImpCasts(); 12143e13fdcSCongcong Cai return AssignmentPair{Field, Init}; 12226374675SCongcong Cai } 12326374675SCongcong Cai if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { 1244fc0214aSAdam Balogh if (COCE->getOperator() != OO_Equal) 12543e13fdcSCongcong Cai return {}; 1264fc0214aSAdam Balogh 1274fc0214aSAdam Balogh const auto *ME = 1284fc0214aSAdam Balogh dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); 1294fc0214aSAdam Balogh if (!ME) 13043e13fdcSCongcong Cai return {}; 1314fc0214aSAdam Balogh 1324fc0214aSAdam Balogh const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 1334fc0214aSAdam Balogh if (!Field) 13443e13fdcSCongcong Cai return {}; 1354fc0214aSAdam Balogh 136a2e15fa5SNathan James if (!isa<CXXThisExpr>(ME->getBase())) 13743e13fdcSCongcong Cai return {}; 138a2e15fa5SNathan James const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts(); 13943e13fdcSCongcong Cai return AssignmentPair{Field, Init}; 1404fc0214aSAdam Balogh } 14143e13fdcSCongcong Cai return {}; 1424fc0214aSAdam Balogh } 1434fc0214aSAdam Balogh 1444fc0214aSAdam Balogh PreferMemberInitializerCheck::PreferMemberInitializerCheck( 1454fc0214aSAdam Balogh StringRef Name, ClangTidyContext *Context) 1466f32d6a4SCarlos Galvez : ClangTidyCheck(Name, Context) {} 1474fc0214aSAdam Balogh 1484fc0214aSAdam Balogh void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { 1497a4b12e3SPiotr Zegar Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()), 1507a4b12e3SPiotr Zegar unless(isInstantiated()), 1517a4b12e3SPiotr Zegar unless(isDelegatingConstructor())) 1524fc0214aSAdam Balogh .bind("ctor"), 1534fc0214aSAdam Balogh this); 1544fc0214aSAdam Balogh } 1554fc0214aSAdam Balogh 1564fc0214aSAdam Balogh void PreferMemberInitializerCheck::check( 1574fc0214aSAdam Balogh const MatchFinder::MatchResult &Result) { 1584fc0214aSAdam Balogh const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 1594fc0214aSAdam Balogh const auto *Body = cast<CompoundStmt>(Ctor->getBody()); 1604fc0214aSAdam Balogh 1614fc0214aSAdam Balogh const CXXRecordDecl *Class = Ctor->getParent(); 1624fc0214aSAdam Balogh bool FirstToCtorInits = true; 1634fc0214aSAdam Balogh 16426374675SCongcong Cai llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{}; 16526374675SCongcong Cai 16626374675SCongcong Cai for (const CXXCtorInitializer *Init : Ctor->inits()) 16726374675SCongcong Cai if (FieldDecl *Field = Init->getMember()) 16826374675SCongcong Cai updateAssignmentLevel(Field, Init->getInit(), Ctor, AssignedFields); 16926374675SCongcong Cai 1704fc0214aSAdam Balogh for (const Stmt *S : Body->body()) { 1714fc0214aSAdam Balogh if (S->getBeginLoc().isMacroID()) { 172a2e15fa5SNathan James StringRef MacroName = Lexer::getImmediateMacroName( 173a2e15fa5SNathan James S->getBeginLoc(), *Result.SourceManager, getLangOpts()); 17486029e4cSMartin Storsjö if (MacroName.contains_insensitive("assert")) 1754fc0214aSAdam Balogh return; 1764fc0214aSAdam Balogh } 1774fc0214aSAdam Balogh if (isControlStatement(S)) 1784fc0214aSAdam Balogh return; 1794fc0214aSAdam Balogh 1804fc0214aSAdam Balogh if (isNoReturnCallStatement(S)) 1814fc0214aSAdam Balogh return; 1824fc0214aSAdam Balogh 1834fc0214aSAdam Balogh if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) { 1844fc0214aSAdam Balogh if (isNoReturnCallStatement(CondOp->getLHS()) || 1854fc0214aSAdam Balogh isNoReturnCallStatement(CondOp->getRHS())) 1864fc0214aSAdam Balogh return; 1874fc0214aSAdam Balogh } 1884fc0214aSAdam Balogh 18943e13fdcSCongcong Cai std::optional<AssignmentPair> AssignmentToMember = 19043e13fdcSCongcong Cai isAssignmentToMemberOf(Class, S, Ctor); 19143e13fdcSCongcong Cai if (!AssignmentToMember) 192f5063bf7SCongcong Cai continue; 19343e13fdcSCongcong Cai const FieldDecl *Field = AssignmentToMember->Field; 19443e13fdcSCongcong Cai const Expr *InitValue = AssignmentToMember->Init; 19526374675SCongcong Cai updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields); 19626374675SCongcong Cai if (!canAdvanceAssignment(AssignedFields[Field])) 19726374675SCongcong Cai continue; 198a2e15fa5SNathan James 199a2e15fa5SNathan James StringRef InsertPrefix = ""; 200d0fcbb37SNathan James bool HasInitAlready = false; 201a2e15fa5SNathan James SourceLocation InsertPos; 202d0fcbb37SNathan James SourceRange ReplaceRange; 2034fc0214aSAdam Balogh bool AddComma = false; 204e9cec392SCongcong Cai bool AddBrace = false; 205a2e15fa5SNathan James bool InvalidFix = false; 206557d2adeSNathan James unsigned Index = Field->getFieldIndex(); 207a2e15fa5SNathan James const CXXCtorInitializer *LastInListInit = nullptr; 208a2e15fa5SNathan James for (const CXXCtorInitializer *Init : Ctor->inits()) { 209d0fcbb37SNathan James if (!Init->isWritten() || Init->isInClassMemberInitializer()) 210a2e15fa5SNathan James continue; 211d0fcbb37SNathan James if (Init->getMember() == Field) { 212d0fcbb37SNathan James HasInitAlready = true; 213d0fcbb37SNathan James if (isa<ImplicitValueInitExpr>(Init->getInit())) 214d0fcbb37SNathan James InsertPos = Init->getRParenLoc(); 215d0fcbb37SNathan James else { 216d0fcbb37SNathan James ReplaceRange = Init->getInit()->getSourceRange(); 217e9cec392SCongcong Cai AddBrace = isa<InitListExpr>(Init->getInit()); 218d0fcbb37SNathan James } 219d0fcbb37SNathan James break; 220d0fcbb37SNathan James } 221a2e15fa5SNathan James if (Init->isMemberInitializer() && 222a2e15fa5SNathan James Index < Init->getMember()->getFieldIndex()) { 2234fc0214aSAdam Balogh InsertPos = Init->getSourceLocation(); 224a2e15fa5SNathan James // There are initializers after the one we are inserting, so add a 225a2e15fa5SNathan James // comma after this insertion in order to not break anything. 226a2e15fa5SNathan James AddComma = true; 2274fc0214aSAdam Balogh break; 2284fc0214aSAdam Balogh } 229a2e15fa5SNathan James LastInListInit = Init; 2304fc0214aSAdam Balogh } 231d0fcbb37SNathan James if (HasInitAlready) { 232d0fcbb37SNathan James if (InsertPos.isValid()) 233d0fcbb37SNathan James InvalidFix |= InsertPos.isMacroID(); 234d0fcbb37SNathan James else 235d0fcbb37SNathan James InvalidFix |= ReplaceRange.getBegin().isMacroID() || 236d0fcbb37SNathan James ReplaceRange.getEnd().isMacroID(); 237d0fcbb37SNathan James } else { 238a2e15fa5SNathan James if (InsertPos.isInvalid()) { 239a2e15fa5SNathan James if (LastInListInit) { 2406f32d6a4SCarlos Galvez InsertPos = 2416f32d6a4SCarlos Galvez Lexer::getLocForEndOfToken(LastInListInit->getRParenLoc(), 0, 2426f32d6a4SCarlos Galvez *Result.SourceManager, getLangOpts()); 243a2e15fa5SNathan James // Inserting after the last constructor initializer, so we need a 244a2e15fa5SNathan James // comma. 245a2e15fa5SNathan James InsertPrefix = ", "; 2464fc0214aSAdam Balogh } else { 247a2e15fa5SNathan James InsertPos = Lexer::getLocForEndOfToken( 248a2e15fa5SNathan James Ctor->getTypeSourceInfo() 249a2e15fa5SNathan James ->getTypeLoc() 250a2e15fa5SNathan James .getAs<clang::FunctionTypeLoc>() 251a2e15fa5SNathan James .getLocalRangeEnd(), 252a2e15fa5SNathan James 0, *Result.SourceManager, getLangOpts()); 253a2e15fa5SNathan James 254a2e15fa5SNathan James // If this is first time in the loop, there are no initializers so 255d0fcbb37SNathan James // `:` declares member initialization list. If this is a 256d0fcbb37SNathan James // subsequent pass then we have already inserted a `:` so continue 257d0fcbb37SNathan James // with a comma. 258a2e15fa5SNathan James InsertPrefix = FirstToCtorInits ? " : " : ", "; 2594fc0214aSAdam Balogh } 2604fc0214aSAdam Balogh } 261a2e15fa5SNathan James InvalidFix |= InsertPos.isMacroID(); 262d0fcbb37SNathan James } 263a2e15fa5SNathan James 264a2e15fa5SNathan James SourceLocation SemiColonEnd; 265a2e15fa5SNathan James if (auto NextToken = Lexer::findNextToken( 266a2e15fa5SNathan James S->getEndLoc(), *Result.SourceManager, getLangOpts())) 267a2e15fa5SNathan James SemiColonEnd = NextToken->getEndLoc(); 268a2e15fa5SNathan James else 269a2e15fa5SNathan James InvalidFix = true; 270a2e15fa5SNathan James 271f5063bf7SCongcong Cai auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member" 272a2e15fa5SNathan James " initializer of the constructor") 273a2e15fa5SNathan James << Field; 274d0fcbb37SNathan James if (InvalidFix) 275d0fcbb37SNathan James continue; 276d0fcbb37SNathan James StringRef NewInit = Lexer::getSourceText( 2776a80e56aSPiotr Zegar Result.SourceManager->getExpansionRange(InitValue->getSourceRange()), 278d0fcbb37SNathan James *Result.SourceManager, getLangOpts()); 279d0fcbb37SNathan James if (HasInitAlready) { 280d0fcbb37SNathan James if (InsertPos.isValid()) 281d0fcbb37SNathan James Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 282e9cec392SCongcong Cai else if (AddBrace) 283e9cec392SCongcong Cai Diag << FixItHint::CreateReplacement(ReplaceRange, 284e9cec392SCongcong Cai ("{" + NewInit + "}").str()); 285d0fcbb37SNathan James else 286d0fcbb37SNathan James Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 287d0fcbb37SNathan James } else { 2886f32d6a4SCarlos Galvez SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit, 2896f32d6a4SCarlos Galvez AddComma ? "), " : ")"}); 290a2e15fa5SNathan James Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 291d0fcbb37SNathan James FirstToCtorInits); 292b859c39cSNathan James FirstToCtorInits = areDiagsSelfContained(); 2934fc0214aSAdam Balogh } 294d0fcbb37SNathan James Diag << FixItHint::CreateRemoval( 295d0fcbb37SNathan James CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 2964fc0214aSAdam Balogh } 2974fc0214aSAdam Balogh } 2984fc0214aSAdam Balogh 2997d2ea6c4SCarlos Galvez } // namespace clang::tidy::cppcoreguidelines 300