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/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang::tidy::cppcoreguidelines {
17 
18 static bool isControlStatement(const Stmt *S) {
19   return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
20              GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
21 }
22 
23 static bool isNoReturnCallStatement(const Stmt *S) {
24   const auto *Call = dyn_cast<CallExpr>(S);
25   if (!Call)
26     return false;
27 
28   const FunctionDecl *Func = Call->getDirectCallee();
29   if (!Func)
30     return false;
31 
32   return Func->isNoReturn();
33 }
34 
35 static bool isLiteral(const Expr *E) {
36   return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
37              CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
38 }
39 
40 static bool isUnaryExprOfLiteral(const Expr *E) {
41   if (const auto *UnOp = dyn_cast<UnaryOperator>(E))
42     return isLiteral(UnOp->getSubExpr());
43   return false;
44 }
45 
46 static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
47   if (isLiteral(Value) || isUnaryExprOfLiteral(Value))
48     return true;
49 
50   if (const auto *DRE = dyn_cast<DeclRefExpr>(Value))
51     return isa<EnumConstantDecl>(DRE->getDecl());
52 
53   return false;
54 }
55 
56 namespace {
57 AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {
58   return Node.getFieldIndex() >= Index;
59 }
60 } // namespace
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 this
66 // field, but is modified before the assignment.
67 static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init,
68                              const CXXConstructorDecl *Context) {
69 
70   auto MemberMatcher =
71       memberExpr(hasObjectExpression(cxxThisExpr()),
72                  member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));
73 
74   auto DeclMatcher = declRefExpr(
75       to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context)))));
76 
77   return match(expr(anyOf(MemberMatcher, DeclMatcher,
78                           hasDescendant(MemberMatcher),
79                           hasDescendant(DeclMatcher))),
80                *Init, Field->getASTContext())
81       .empty();
82 }
83 
84 static std::pair<const FieldDecl *, const Expr *>
85 isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,
86                        const CXXConstructorDecl *Ctor) {
87   if (const auto *BO = dyn_cast<BinaryOperator>(S)) {
88     if (BO->getOpcode() != BO_Assign)
89       return std::make_pair(nullptr, nullptr);
90 
91     const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());
92     if (!ME)
93       return std::make_pair(nullptr, nullptr);
94 
95     const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
96     if (!Field)
97       return std::make_pair(nullptr, nullptr);
98 
99     if (!isa<CXXThisExpr>(ME->getBase()))
100       return std::make_pair(nullptr, nullptr);
101     const Expr *Init = BO->getRHS()->IgnoreParenImpCasts();
102     if (isSafeAssignment(Field, Init, Ctor))
103       return std::make_pair(Field, Init);
104   } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
105     if (COCE->getOperator() != OO_Equal)
106       return std::make_pair(nullptr, nullptr);
107 
108     const auto *ME =
109         dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
110     if (!ME)
111       return std::make_pair(nullptr, nullptr);
112 
113     const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
114     if (!Field)
115       return std::make_pair(nullptr, nullptr);
116 
117     if (!isa<CXXThisExpr>(ME->getBase()))
118       return std::make_pair(nullptr, nullptr);
119     const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts();
120     if (isSafeAssignment(Field, Init, Ctor))
121       return std::make_pair(Field, Init);
122   }
123 
124   return std::make_pair(nullptr, nullptr);
125 }
126 
127 PreferMemberInitializerCheck::PreferMemberInitializerCheck(
128     StringRef Name, ClangTidyContext *Context)
129     : ClangTidyCheck(Name, Context),
130       IsUseDefaultMemberInitEnabled(
131           Context->isCheckEnabled("modernize-use-default-member-init")),
132       UseAssignment(
133           Options.get("UseAssignment",
134                       OptionsView("modernize-use-default-member-init",
135                                   Context->getOptions().CheckOptions, Context)
136                           .get("UseAssignment", false))) {}
137 
138 void PreferMemberInitializerCheck::storeOptions(
139     ClangTidyOptions::OptionMap &Opts) {
140   Options.store(Opts, "UseAssignment", UseAssignment);
141 }
142 
143 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {
144   Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()),
145                                         unless(isInstantiated()),
146                                         unless(isDelegatingConstructor()))
147                          .bind("ctor"),
148                      this);
149 }
150 
151 void PreferMemberInitializerCheck::check(
152     const MatchFinder::MatchResult &Result) {
153   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
154   const auto *Body = cast<CompoundStmt>(Ctor->getBody());
155 
156   const CXXRecordDecl *Class = Ctor->getParent();
157   bool FirstToCtorInits = true;
158 
159   for (const Stmt *S : Body->body()) {
160     if (S->getBeginLoc().isMacroID()) {
161       StringRef MacroName = Lexer::getImmediateMacroName(
162           S->getBeginLoc(), *Result.SourceManager, getLangOpts());
163       if (MacroName.contains_insensitive("assert"))
164         return;
165     }
166     if (isControlStatement(S))
167       return;
168 
169     if (isNoReturnCallStatement(S))
170       return;
171 
172     if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
173       if (isNoReturnCallStatement(CondOp->getLHS()) ||
174           isNoReturnCallStatement(CondOp->getRHS()))
175         return;
176     }
177 
178     const FieldDecl *Field = nullptr;
179     const Expr *InitValue = nullptr;
180     std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor);
181     if (!Field)
182       continue;
183     const bool IsInDefaultMemberInitializer =
184         IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
185         Ctor->isDefaultConstructor() &&
186         (getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
187         !Field->hasInClassInitializer() &&
188         (!isa<RecordDecl>(Class->getDeclContext()) ||
189          !cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
190         shouldBeDefaultMemberInitializer(InitValue);
191     if (IsInDefaultMemberInitializer) {
192       bool InvalidFix = false;
193       SourceLocation FieldEnd =
194           Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
195                                      *Result.SourceManager, getLangOpts());
196       InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID();
197       SourceLocation SemiColonEnd;
198       if (auto NextToken = Lexer::findNextToken(
199               S->getEndLoc(), *Result.SourceManager, getLangOpts()))
200         SemiColonEnd = NextToken->getEndLoc();
201       else
202         InvalidFix = true;
203       auto Diag =
204           diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
205                                  " default member initializer")
206           << Field;
207       if (InvalidFix)
208         continue;
209       CharSourceRange StmtRange =
210           CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
211 
212       SmallString<128> Insertion(
213           {UseAssignment ? " = " : "{",
214            Lexer::getSourceText(
215                CharSourceRange(InitValue->getSourceRange(), true),
216                *Result.SourceManager, getLangOpts()),
217            UseAssignment ? "" : "}"});
218 
219       Diag << FixItHint::CreateInsertion(FieldEnd, Insertion)
220            << FixItHint::CreateRemoval(StmtRange);
221 
222     } else {
223       StringRef InsertPrefix = "";
224       bool HasInitAlready = false;
225       SourceLocation InsertPos;
226       SourceRange ReplaceRange;
227       bool AddComma = false;
228       bool InvalidFix = false;
229       unsigned Index = Field->getFieldIndex();
230       const CXXCtorInitializer *LastInListInit = nullptr;
231       for (const CXXCtorInitializer *Init : Ctor->inits()) {
232         if (!Init->isWritten() || Init->isInClassMemberInitializer())
233           continue;
234         if (Init->getMember() == Field) {
235           HasInitAlready = true;
236           if (isa<ImplicitValueInitExpr>(Init->getInit()))
237             InsertPos = Init->getRParenLoc();
238           else {
239             ReplaceRange = Init->getInit()->getSourceRange();
240           }
241           break;
242         }
243         if (Init->isMemberInitializer() &&
244             Index < Init->getMember()->getFieldIndex()) {
245           InsertPos = Init->getSourceLocation();
246           // There are initializers after the one we are inserting, so add a
247           // comma after this insertion in order to not break anything.
248           AddComma = true;
249           break;
250         }
251         LastInListInit = Init;
252       }
253       if (HasInitAlready) {
254         if (InsertPos.isValid())
255           InvalidFix |= InsertPos.isMacroID();
256         else
257           InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
258                         ReplaceRange.getEnd().isMacroID();
259       } else {
260         if (InsertPos.isInvalid()) {
261           if (LastInListInit) {
262             InsertPos = Lexer::getLocForEndOfToken(
263                 LastInListInit->getRParenLoc(), 0, *Result.SourceManager,
264                 getLangOpts());
265             // Inserting after the last constructor initializer, so we need a
266             // comma.
267             InsertPrefix = ", ";
268           } else {
269             InsertPos = Lexer::getLocForEndOfToken(
270                 Ctor->getTypeSourceInfo()
271                     ->getTypeLoc()
272                     .getAs<clang::FunctionTypeLoc>()
273                     .getLocalRangeEnd(),
274                 0, *Result.SourceManager, getLangOpts());
275 
276             // If this is first time in the loop, there are no initializers so
277             // `:` declares member initialization list. If this is a
278             // subsequent pass then we have already inserted a `:` so continue
279             // with a comma.
280             InsertPrefix = FirstToCtorInits ? " : " : ", ";
281           }
282         }
283         InvalidFix |= InsertPos.isMacroID();
284       }
285 
286       SourceLocation SemiColonEnd;
287       if (auto NextToken = Lexer::findNextToken(
288               S->getEndLoc(), *Result.SourceManager, getLangOpts()))
289         SemiColonEnd = NextToken->getEndLoc();
290       else
291         InvalidFix = true;
292 
293       auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member"
294                                          " initializer of the constructor")
295                   << Field;
296       if (InvalidFix)
297         continue;
298       StringRef NewInit = Lexer::getSourceText(
299           CharSourceRange(InitValue->getSourceRange(), true),
300           *Result.SourceManager, getLangOpts());
301       if (HasInitAlready) {
302         if (InsertPos.isValid())
303           Diag << FixItHint::CreateInsertion(InsertPos, NewInit);
304         else
305           Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
306       } else {
307         SmallString<128> Insertion({InsertPrefix, Field->getName(), "(",
308                                     NewInit, AddComma ? "), " : ")"});
309         Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
310                                            FirstToCtorInits);
311         FirstToCtorInits = areDiagsSelfContained();
312       }
313       Diag << FixItHint::CreateRemoval(
314           CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
315     }
316   }
317 }
318 
319 } // namespace clang::tidy::cppcoreguidelines
320