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