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