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 {
17 namespace tidy {
18 namespace 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 static bool isLiteral(const Expr *E) {
38   return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
39              CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
40 }
41 
42 static bool isUnaryExprOfLiteral(const Expr *E) {
43   if (const auto *UnOp = dyn_cast<UnaryOperator>(E))
44     return isLiteral(UnOp->getSubExpr());
45   return false;
46 }
47 
48 static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
49   if (isLiteral(Value) || isUnaryExprOfLiteral(Value))
50     return true;
51 
52   if (const auto *DRE = dyn_cast<DeclRefExpr>(Value))
53     return isa<EnumConstantDecl>(DRE->getDecl());
54 
55   return false;
56 }
57 
58 static const std::pair<const FieldDecl *, const Expr *>
59 isAssignmentToMemberOf(const RecordDecl *Rec, const Stmt *S) {
60   if (const auto *BO = dyn_cast<BinaryOperator>(S)) {
61     if (BO->getOpcode() != BO_Assign)
62       return std::make_pair(nullptr, nullptr);
63 
64     const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());
65     if (!ME)
66       return std::make_pair(nullptr, nullptr);
67 
68     const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
69     if (!Field)
70       return std::make_pair(nullptr, nullptr);
71 
72     if (isa<CXXThisExpr>(ME->getBase()))
73       return std::make_pair(Field, BO->getRHS()->IgnoreParenImpCasts());
74   } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
75     if (COCE->getOperator() != OO_Equal)
76       return std::make_pair(nullptr, nullptr);
77 
78     const auto *ME =
79         dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
80     if (!ME)
81       return std::make_pair(nullptr, nullptr);
82 
83     const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
84     if (!Field)
85       return std::make_pair(nullptr, nullptr);
86 
87     if (isa<CXXThisExpr>(ME->getBase()))
88       return std::make_pair(Field, COCE->getArg(1)->IgnoreParenImpCasts());
89   }
90 
91   return std::make_pair(nullptr, nullptr);
92 }
93 
94 PreferMemberInitializerCheck::PreferMemberInitializerCheck(
95     StringRef Name, ClangTidyContext *Context)
96     : ClangTidyCheck(Name, Context),
97       IsUseDefaultMemberInitEnabled(
98           Context->isCheckEnabled("modernize-use-default-member-init")),
99       UseAssignment(OptionsView("modernize-use-default-member-init",
100                                 Context->getOptions().CheckOptions, Context)
101                         .get("UseAssignment", false)) {}
102 
103 void PreferMemberInitializerCheck::storeOptions(
104     ClangTidyOptions::OptionMap &Opts) {
105   Options.store(Opts, "UseAssignment", UseAssignment);
106 }
107 
108 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {
109   Finder->addMatcher(
110       cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
111           .bind("ctor"),
112       this);
113 }
114 
115 void PreferMemberInitializerCheck::check(
116     const MatchFinder::MatchResult &Result) {
117   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
118   const auto *Body = cast<CompoundStmt>(Ctor->getBody());
119 
120   const CXXRecordDecl *Class = Ctor->getParent();
121   SourceLocation InsertPos;
122   bool FirstToCtorInits = true;
123 
124   for (const Stmt *S : Body->body()) {
125     if (S->getBeginLoc().isMacroID()) {
126       StringRef MacroName =
127         Lexer::getImmediateMacroName(S->getBeginLoc(), *Result.SourceManager,
128                                      getLangOpts());
129       if (MacroName.contains_lower("assert"))
130         return;
131     }
132     if (isControlStatement(S))
133       return;
134 
135     if (isNoReturnCallStatement(S))
136       return;
137 
138     if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
139       if (isNoReturnCallStatement(CondOp->getLHS()) ||
140           isNoReturnCallStatement(CondOp->getRHS()))
141         return;
142     }
143 
144     const FieldDecl *Field;
145     const Expr *InitValue;
146     std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S);
147     if (Field) {
148       if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
149           Ctor->isDefaultConstructor() &&
150           (getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
151           (!isa<RecordDecl>(Class->getDeclContext()) ||
152            !cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
153           shouldBeDefaultMemberInitializer(InitValue)) {
154 
155         SourceLocation FieldEnd =
156             Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
157                                        *Result.SourceManager, getLangOpts());
158         SmallString<128> Insertion(
159             {UseAssignment ? " = " : "{",
160              Lexer::getSourceText(
161                  CharSourceRange(InitValue->getSourceRange(), true),
162                  *Result.SourceManager, getLangOpts()),
163              UseAssignment ? "" : "}"});
164 
165         SourceLocation SemiColonEnd =
166             Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager,
167                                  getLangOpts())
168                 ->getEndLoc();
169         CharSourceRange StmtRange =
170             CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
171 
172         diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
173                                " default member initializer")
174             << Field << FixItHint::CreateInsertion(FieldEnd, Insertion)
175             << FixItHint::CreateRemoval(StmtRange);
176       } else {
177         SmallString<128> Insertion;
178         bool AddComma = false;
179         if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) {
180           SourceLocation BodyPos = Ctor->getBody()->getBeginLoc();
181           SourceLocation NextPos = Ctor->getBeginLoc();
182           do {
183             InsertPos = NextPos;
184             NextPos = Lexer::findNextToken(NextPos, *Result.SourceManager,
185                                            getLangOpts())
186                           ->getLocation();
187           } while (NextPos != BodyPos);
188           InsertPos = Lexer::getLocForEndOfToken(
189               InsertPos, 0, *Result.SourceManager, getLangOpts());
190 
191           Insertion = " : ";
192         } else {
193           bool Found = false;
194           unsigned Index = Field->getFieldIndex();
195           for (const auto *Init : Ctor->inits()) {
196             if (Init->isMemberInitializer()) {
197               if (Index < Init->getMember()->getFieldIndex()) {
198                 InsertPos = Init->getSourceLocation();
199                 Found = true;
200                 break;
201               }
202             }
203           }
204 
205           if (!Found) {
206             if (Ctor->getNumCtorInitializers()) {
207               InsertPos = Lexer::getLocForEndOfToken(
208                   (*Ctor->init_rbegin())->getSourceRange().getEnd(), 0,
209                   *Result.SourceManager, getLangOpts());
210             }
211             Insertion = ", ";
212           } else {
213             AddComma = true;
214           }
215         }
216         Insertion.append(
217             {Field->getName(), "(",
218              Lexer::getSourceText(
219                  CharSourceRange(InitValue->getSourceRange(), true),
220                  *Result.SourceManager, getLangOpts()),
221              AddComma ? "), " : ")"});
222 
223         SourceLocation SemiColonEnd =
224             Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager,
225                                  getLangOpts())
226                 ->getEndLoc();
227         CharSourceRange StmtRange =
228             CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
229 
230         diag(S->getBeginLoc(), "%0 should be initialized in a member"
231                                " initializer of the constructor")
232             << Field
233             << FixItHint::CreateInsertion(InsertPos, Insertion,
234                                           FirstToCtorInits)
235             << FixItHint::CreateRemoval(StmtRange);
236         FirstToCtorInits = false;
237       }
238     }
239   }
240 }
241 
242 } // namespace cppcoreguidelines
243 } // namespace tidy
244 } // namespace clang
245