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
isControlStatement(const Stmt * S)20 static bool isControlStatement(const Stmt *S) {
21 return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
22 GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
23 }
24
isNoReturnCallStatement(const Stmt * S)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
isLiteral(const Expr * E)37 static bool isLiteral(const Expr *E) {
38 return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
39 CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
40 }
41
isUnaryExprOfLiteral(const Expr * E)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
shouldBeDefaultMemberInitializer(const Expr * Value)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 *>
isAssignmentToMemberOf(const RecordDecl * Rec,const Stmt * S)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
PreferMemberInitializerCheck(StringRef Name,ClangTidyContext * Context)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
storeOptions(ClangTidyOptions::OptionMap & Opts)103 void PreferMemberInitializerCheck::storeOptions(
104 ClangTidyOptions::OptionMap &Opts) {
105 Options.store(Opts, "UseAssignment", UseAssignment);
106 }
107
registerMatchers(MatchFinder * Finder)108 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {
109 Finder->addMatcher(
110 cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
111 .bind("ctor"),
112 this);
113 }
114
check(const MatchFinder::MatchResult & Result)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 auto Diag =
155 diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
156 " default member initializer")
157 << Field;
158
159 SourceLocation FieldEnd =
160 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
161 *Result.SourceManager, getLangOpts());
162 Diag << FixItHint::CreateInsertion(FieldEnd,
163 UseAssignment ? " = " : "{")
164 << FixItHint::CreateInsertionFromRange(
165 FieldEnd,
166 CharSourceRange(InitValue->getSourceRange(), true))
167 << FixItHint::CreateInsertion(FieldEnd, UseAssignment ? "" : "}");
168
169 SourceLocation SemiColonEnd =
170 Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager,
171 getLangOpts())
172 ->getEndLoc();
173 CharSourceRange StmtRange =
174 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
175
176 Diag << FixItHint::CreateRemoval(StmtRange);
177 } else {
178 auto Diag =
179 diag(S->getBeginLoc(), "%0 should be initialized in a member"
180 " initializer of the constructor")
181 << Field;
182
183 bool AddComma = false;
184 if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) {
185 SourceLocation BodyPos = Ctor->getBody()->getBeginLoc();
186 SourceLocation NextPos = Ctor->getBeginLoc();
187 do {
188 InsertPos = NextPos;
189 NextPos = Lexer::findNextToken(NextPos, *Result.SourceManager,
190 getLangOpts())
191 ->getLocation();
192 } while (NextPos != BodyPos);
193 InsertPos = Lexer::getLocForEndOfToken(
194 InsertPos, 0, *Result.SourceManager, getLangOpts());
195
196 Diag << FixItHint::CreateInsertion(InsertPos, " : ");
197 } else {
198 bool Found = false;
199 for (const auto *Init : Ctor->inits()) {
200 if (Init->isMemberInitializer()) {
201 if (Result.SourceManager->isBeforeInTranslationUnit(
202 Field->getLocation(), Init->getMember()->getLocation())) {
203 InsertPos = Init->getSourceLocation();
204 Found = true;
205 break;
206 }
207 }
208 }
209
210 if (!Found) {
211 if (Ctor->getNumCtorInitializers()) {
212 InsertPos = Lexer::getLocForEndOfToken(
213 (*Ctor->init_rbegin())->getSourceRange().getEnd(), 0,
214 *Result.SourceManager, getLangOpts());
215 }
216 Diag << FixItHint::CreateInsertion(InsertPos, ", ");
217 } else {
218 AddComma = true;
219 }
220 }
221 Diag << FixItHint::CreateInsertion(InsertPos, Field->getName())
222 << FixItHint::CreateInsertion(InsertPos, "(")
223 << FixItHint::CreateInsertionFromRange(
224 InsertPos,
225 CharSourceRange(InitValue->getSourceRange(), true))
226 << FixItHint::CreateInsertion(InsertPos, ")");
227 if (AddComma)
228 Diag << FixItHint::CreateInsertion(InsertPos, ", ");
229
230 SourceLocation SemiColonEnd =
231 Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager,
232 getLangOpts())
233 ->getEndLoc();
234 CharSourceRange StmtRange =
235 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
236
237 Diag << FixItHint::CreateRemoval(StmtRange);
238 FirstToCtorInits = false;
239 }
240 }
241 }
242 }
243
244 } // namespace cppcoreguidelines
245 } // namespace tidy
246 } // namespace clang
247