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       if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
183           Ctor->isDefaultConstructor() &&
184           (getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
185           !Field->hasInClassInitializer() &&
186           (!isa<RecordDecl>(Class->getDeclContext()) ||
187            !cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
188           shouldBeDefaultMemberInitializer(InitValue)) {
189 
190         bool InvalidFix = false;
191         SourceLocation FieldEnd =
192             Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
193                                        *Result.SourceManager, getLangOpts());
194         InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID();
195         SourceLocation SemiColonEnd;
196         if (auto NextToken = Lexer::findNextToken(
197                 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
198           SemiColonEnd = NextToken->getEndLoc();
199         else
200           InvalidFix = true;
201         auto Diag =
202             diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
203                                    " default member initializer")
204             << Field;
205         if (InvalidFix)
206           continue;
207         CharSourceRange StmtRange =
208             CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
209 
210         SmallString<128> Insertion(
211             {UseAssignment ? " = " : "{",
212              Lexer::getSourceText(
213                  CharSourceRange(InitValue->getSourceRange(), true),
214                  *Result.SourceManager, getLangOpts()),
215              UseAssignment ? "" : "}"});
216 
217         Diag << FixItHint::CreateInsertion(FieldEnd, Insertion)
218              << FixItHint::CreateRemoval(StmtRange);
219 
220       } else {
221         StringRef InsertPrefix = "";
222         bool HasInitAlready = false;
223         SourceLocation InsertPos;
224         SourceRange ReplaceRange;
225         bool AddComma = false;
226         bool InvalidFix = false;
227         unsigned Index = Field->getFieldIndex();
228         const CXXCtorInitializer *LastInListInit = nullptr;
229         for (const CXXCtorInitializer *Init : Ctor->inits()) {
230           if (!Init->isWritten() || Init->isInClassMemberInitializer())
231             continue;
232           if (Init->getMember() == Field) {
233             HasInitAlready = true;
234             if (isa<ImplicitValueInitExpr>(Init->getInit()))
235               InsertPos = Init->getRParenLoc();
236             else {
237               ReplaceRange = Init->getInit()->getSourceRange();
238             }
239             break;
240           }
241           if (Init->isMemberInitializer() &&
242               Index < Init->getMember()->getFieldIndex()) {
243             InsertPos = Init->getSourceLocation();
244             // There are initializers after the one we are inserting, so add a
245             // comma after this insertion in order to not break anything.
246             AddComma = true;
247             break;
248           }
249           LastInListInit = Init;
250         }
251         if (HasInitAlready) {
252           if (InsertPos.isValid())
253             InvalidFix |= InsertPos.isMacroID();
254           else
255             InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
256                           ReplaceRange.getEnd().isMacroID();
257         } else {
258           if (InsertPos.isInvalid()) {
259             if (LastInListInit) {
260               InsertPos = Lexer::getLocForEndOfToken(
261                   LastInListInit->getRParenLoc(), 0, *Result.SourceManager,
262                   getLangOpts());
263               // Inserting after the last constructor initializer, so we need a
264               // comma.
265               InsertPrefix = ", ";
266             } else {
267               InsertPos = Lexer::getLocForEndOfToken(
268                   Ctor->getTypeSourceInfo()
269                       ->getTypeLoc()
270                       .getAs<clang::FunctionTypeLoc>()
271                       .getLocalRangeEnd(),
272                   0, *Result.SourceManager, getLangOpts());
273 
274               // If this is first time in the loop, there are no initializers so
275               // `:` declares member initialization list. If this is a
276               // subsequent pass then we have already inserted a `:` so continue
277               // with a comma.
278               InsertPrefix = FirstToCtorInits ? " : " : ", ";
279             }
280           }
281           InvalidFix |= InsertPos.isMacroID();
282         }
283 
284         SourceLocation SemiColonEnd;
285         if (auto NextToken = Lexer::findNextToken(
286                 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
287           SemiColonEnd = NextToken->getEndLoc();
288         else
289           InvalidFix = true;
290 
291         auto Diag =
292             diag(S->getBeginLoc(), "%0 should be initialized in a member"
293                                    " initializer of the constructor")
294             << Field;
295         if (InvalidFix)
296           continue;
297         StringRef NewInit = Lexer::getSourceText(
298             CharSourceRange(InitValue->getSourceRange(), true),
299             *Result.SourceManager, getLangOpts());
300         if (HasInitAlready) {
301           if (InsertPos.isValid())
302             Diag << FixItHint::CreateInsertion(InsertPos, NewInit);
303           else
304             Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
305         } else {
306           SmallString<128> Insertion({InsertPrefix, Field->getName(), "(",
307                                       NewInit, AddComma ? "), " : ")"});
308           Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
309                                              FirstToCtorInits);
310           FirstToCtorInits = areDiagsSelfContained();
311         }
312         Diag << FixItHint::CreateRemoval(
313             CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
314       }
315     }
316   }
317 }
318 
319 } // namespace clang::tidy::cppcoreguidelines
320