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 InvalidFix = false;
207     unsigned Index = Field->getFieldIndex();
208     const CXXCtorInitializer *LastInListInit = nullptr;
209     for (const CXXCtorInitializer *Init : Ctor->inits()) {
210       if (!Init->isWritten() || Init->isInClassMemberInitializer())
211         continue;
212       if (Init->getMember() == Field) {
213         HasInitAlready = true;
214         if (isa<ImplicitValueInitExpr>(Init->getInit()))
215           InsertPos = Init->getRParenLoc();
216         else {
217           ReplaceRange = Init->getInit()->getSourceRange();
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
283         Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
284     } else {
285       SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit,
286                                   AddComma ? "), " : ")"});
287       Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
288                                          FirstToCtorInits);
289       FirstToCtorInits = areDiagsSelfContained();
290     }
291     Diag << FixItHint::CreateRemoval(
292         CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
293   }
294 }
295 
296 } // namespace clang::tidy::cppcoreguidelines
297