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(
145       cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
146           .bind("ctor"),
147       this);
148 }
149 
150 void PreferMemberInitializerCheck::check(
151     const MatchFinder::MatchResult &Result) {
152   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
153   const auto *Body = cast<CompoundStmt>(Ctor->getBody());
154 
155   const CXXRecordDecl *Class = Ctor->getParent();
156   bool FirstToCtorInits = true;
157 
158   for (const Stmt *S : Body->body()) {
159     if (S->getBeginLoc().isMacroID()) {
160       StringRef MacroName = Lexer::getImmediateMacroName(
161           S->getBeginLoc(), *Result.SourceManager, getLangOpts());
162       if (MacroName.contains_insensitive("assert"))
163         return;
164     }
165     if (isControlStatement(S))
166       return;
167 
168     if (isNoReturnCallStatement(S))
169       return;
170 
171     if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
172       if (isNoReturnCallStatement(CondOp->getLHS()) ||
173           isNoReturnCallStatement(CondOp->getRHS()))
174         return;
175     }
176 
177     const FieldDecl *Field;
178     const Expr *InitValue;
179     std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor);
180     if (Field) {
181       if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
182           Ctor->isDefaultConstructor() &&
183           (getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
184           !Field->hasInClassInitializer() &&
185           (!isa<RecordDecl>(Class->getDeclContext()) ||
186            !cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
187           shouldBeDefaultMemberInitializer(InitValue)) {
188 
189         bool InvalidFix = false;
190         SourceLocation FieldEnd =
191             Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
192                                        *Result.SourceManager, getLangOpts());
193         InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID();
194         SourceLocation SemiColonEnd;
195         if (auto NextToken = Lexer::findNextToken(
196                 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
197           SemiColonEnd = NextToken->getEndLoc();
198         else
199           InvalidFix = true;
200         auto Diag =
201             diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
202                                    " default member initializer")
203             << Field;
204         if (InvalidFix)
205           continue;
206         CharSourceRange StmtRange =
207             CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
208 
209         SmallString<128> Insertion(
210             {UseAssignment ? " = " : "{",
211              Lexer::getSourceText(
212                  CharSourceRange(InitValue->getSourceRange(), true),
213                  *Result.SourceManager, getLangOpts()),
214              UseAssignment ? "" : "}"});
215 
216         Diag << FixItHint::CreateInsertion(FieldEnd, Insertion)
217              << FixItHint::CreateRemoval(StmtRange);
218 
219       } else {
220         StringRef InsertPrefix = "";
221         bool HasInitAlready = false;
222         SourceLocation InsertPos;
223         SourceRange ReplaceRange;
224         bool AddComma = false;
225         bool InvalidFix = false;
226         unsigned Index = Field->getFieldIndex();
227         const CXXCtorInitializer *LastInListInit = nullptr;
228         for (const CXXCtorInitializer *Init : Ctor->inits()) {
229           if (!Init->isWritten() || Init->isInClassMemberInitializer())
230             continue;
231           if (Init->getMember() == Field) {
232             HasInitAlready = true;
233             if (isa<ImplicitValueInitExpr>(Init->getInit()))
234               InsertPos = Init->getRParenLoc();
235             else {
236               ReplaceRange = Init->getInit()->getSourceRange();
237             }
238             break;
239           }
240           if (Init->isMemberInitializer() &&
241               Index < Init->getMember()->getFieldIndex()) {
242             InsertPos = Init->getSourceLocation();
243             // There are initializers after the one we are inserting, so add a
244             // comma after this insertion in order to not break anything.
245             AddComma = true;
246             break;
247           }
248           LastInListInit = Init;
249         }
250         if (HasInitAlready) {
251           if (InsertPos.isValid())
252             InvalidFix |= InsertPos.isMacroID();
253           else
254             InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
255                           ReplaceRange.getEnd().isMacroID();
256         } else {
257           if (InsertPos.isInvalid()) {
258             if (LastInListInit) {
259               InsertPos = Lexer::getLocForEndOfToken(
260                   LastInListInit->getRParenLoc(), 0, *Result.SourceManager,
261                   getLangOpts());
262               // Inserting after the last constructor initializer, so we need a
263               // comma.
264               InsertPrefix = ", ";
265             } else {
266               InsertPos = Lexer::getLocForEndOfToken(
267                   Ctor->getTypeSourceInfo()
268                       ->getTypeLoc()
269                       .getAs<clang::FunctionTypeLoc>()
270                       .getLocalRangeEnd(),
271                   0, *Result.SourceManager, getLangOpts());
272 
273               // If this is first time in the loop, there are no initializers so
274               // `:` declares member initialization list. If this is a
275               // subsequent pass then we have already inserted a `:` so continue
276               // with a comma.
277               InsertPrefix = FirstToCtorInits ? " : " : ", ";
278             }
279           }
280           InvalidFix |= InsertPos.isMacroID();
281         }
282 
283         SourceLocation SemiColonEnd;
284         if (auto NextToken = Lexer::findNextToken(
285                 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
286           SemiColonEnd = NextToken->getEndLoc();
287         else
288           InvalidFix = true;
289 
290         auto Diag =
291             diag(S->getBeginLoc(), "%0 should be initialized in a member"
292                                    " initializer of the constructor")
293             << Field;
294         if (InvalidFix)
295           continue;
296         StringRef NewInit = Lexer::getSourceText(
297             CharSourceRange(InitValue->getSourceRange(), true),
298             *Result.SourceManager, getLangOpts());
299         if (HasInitAlready) {
300           if (InsertPos.isValid())
301             Diag << FixItHint::CreateInsertion(InsertPos, NewInit);
302           else
303             Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
304         } else {
305           SmallString<128> Insertion({InsertPrefix, Field->getName(), "(",
306                                       NewInit, AddComma ? "), " : ")"});
307           Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
308                                              FirstToCtorInits);
309           FirstToCtorInits = areDiagsSelfContained();
310         }
311         Diag << FixItHint::CreateRemoval(
312             CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
313       }
314     }
315   }
316 }
317 
318 } // namespace clang::tidy::cppcoreguidelines
319