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