1 //===--- PreferMemberInitializerCheck.cpp - clang-tidy -------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "PreferMemberInitializerCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::cppcoreguidelines
{
18 static bool isControlStatement(const Stmt
*S
) {
19 return isa
<IfStmt
, SwitchStmt
, ForStmt
, WhileStmt
, DoStmt
, ReturnStmt
,
20 GotoStmt
, CXXTryStmt
, CXXThrowExpr
>(S
);
23 static bool isNoReturnCallStatement(const Stmt
*S
) {
24 const auto *Call
= dyn_cast
<CallExpr
>(S
);
28 const FunctionDecl
*Func
= Call
->getDirectCallee();
32 return Func
->isNoReturn();
35 static bool isLiteral(const Expr
*E
) {
36 return isa
<StringLiteral
, CharacterLiteral
, IntegerLiteral
, FloatingLiteral
,
37 CXXBoolLiteralExpr
, CXXNullPtrLiteralExpr
>(E
);
40 static bool isUnaryExprOfLiteral(const Expr
*E
) {
41 if (const auto *UnOp
= dyn_cast
<UnaryOperator
>(E
))
42 return isLiteral(UnOp
->getSubExpr());
46 static bool shouldBeDefaultMemberInitializer(const Expr
*Value
) {
47 if (isLiteral(Value
) || isUnaryExprOfLiteral(Value
))
50 if (const auto *DRE
= dyn_cast
<DeclRefExpr
>(Value
))
51 return isa
<EnumConstantDecl
>(DRE
->getDecl());
57 AST_MATCHER_P(FieldDecl
, indexNotLessThan
, unsigned, Index
) {
58 return Node
.getFieldIndex() >= Index
;
62 // Checks if Field is initialised using a field that will be initialised after
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
) {
71 memberExpr(hasObjectExpression(cxxThisExpr()),
72 member(fieldDecl(indexNotLessThan(Field
->getFieldIndex()))));
74 auto DeclMatcher
= declRefExpr(
75 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context
)))));
77 return match(expr(anyOf(MemberMatcher
, DeclMatcher
,
78 hasDescendant(MemberMatcher
),
79 hasDescendant(DeclMatcher
))),
80 *Init
, Field
->getASTContext())
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);
91 const auto *ME
= dyn_cast
<MemberExpr
>(BO
->getLHS()->IgnoreParenImpCasts());
93 return std::make_pair(nullptr, nullptr);
95 const auto *Field
= dyn_cast
<FieldDecl
>(ME
->getMemberDecl());
97 return std::make_pair(nullptr, nullptr);
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);
109 dyn_cast
<MemberExpr
>(COCE
->getArg(0)->IgnoreParenImpCasts());
111 return std::make_pair(nullptr, nullptr);
113 const auto *Field
= dyn_cast
<FieldDecl
>(ME
->getMemberDecl());
115 return std::make_pair(nullptr, nullptr);
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
);
124 return std::make_pair(nullptr, nullptr);
127 PreferMemberInitializerCheck::PreferMemberInitializerCheck(
128 StringRef Name
, ClangTidyContext
*Context
)
129 : ClangTidyCheck(Name
, Context
),
130 IsUseDefaultMemberInitEnabled(
131 Context
->isCheckEnabled("modernize-use-default-member-init")),
133 Options
.get("UseAssignment",
134 OptionsView("modernize-use-default-member-init",
135 Context
->getOptions().CheckOptions
, Context
)
136 .get("UseAssignment", false))) {}
138 void PreferMemberInitializerCheck::storeOptions(
139 ClangTidyOptions::OptionMap
&Opts
) {
140 Options
.store(Opts
, "UseAssignment", UseAssignment
);
143 void PreferMemberInitializerCheck::registerMatchers(MatchFinder
*Finder
) {
145 cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
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());
155 const CXXRecordDecl
*Class
= Ctor
->getParent();
156 bool FirstToCtorInits
= true;
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"))
165 if (isControlStatement(S
))
168 if (isNoReturnCallStatement(S
))
171 if (const auto *CondOp
= dyn_cast
<ConditionalOperator
>(S
)) {
172 if (isNoReturnCallStatement(CondOp
->getLHS()) ||
173 isNoReturnCallStatement(CondOp
->getRHS()))
177 const FieldDecl
*Field
;
178 const Expr
*InitValue
;
179 std::tie(Field
, InitValue
) = isAssignmentToMemberOf(Class
, S
, Ctor
);
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
)) {
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();
201 diag(S
->getBeginLoc(), "%0 should be initialized in an in-class"
202 " default member initializer")
206 CharSourceRange StmtRange
=
207 CharSourceRange::getCharRange(S
->getBeginLoc(), SemiColonEnd
);
209 SmallString
<128> Insertion(
210 {UseAssignment
? " = " : "{",
211 Lexer::getSourceText(
212 CharSourceRange(InitValue
->getSourceRange(), true),
213 *Result
.SourceManager
, getLangOpts()),
214 UseAssignment
? "" : "}"});
216 Diag
<< FixItHint::CreateInsertion(FieldEnd
, Insertion
)
217 << FixItHint::CreateRemoval(StmtRange
);
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())
231 if (Init
->getMember() == Field
) {
232 HasInitAlready
= true;
233 if (isa
<ImplicitValueInitExpr
>(Init
->getInit()))
234 InsertPos
= Init
->getRParenLoc();
236 ReplaceRange
= Init
->getInit()->getSourceRange();
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.
248 LastInListInit
= Init
;
250 if (HasInitAlready
) {
251 if (InsertPos
.isValid())
252 InvalidFix
|= InsertPos
.isMacroID();
254 InvalidFix
|= ReplaceRange
.getBegin().isMacroID() ||
255 ReplaceRange
.getEnd().isMacroID();
257 if (InsertPos
.isInvalid()) {
258 if (LastInListInit
) {
259 InsertPos
= Lexer::getLocForEndOfToken(
260 LastInListInit
->getRParenLoc(), 0, *Result
.SourceManager
,
262 // Inserting after the last constructor initializer, so we need a
266 InsertPos
= Lexer::getLocForEndOfToken(
267 Ctor
->getTypeSourceInfo()
269 .getAs
<clang::FunctionTypeLoc
>()
271 0, *Result
.SourceManager
, getLangOpts());
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
277 InsertPrefix
= FirstToCtorInits
? " : " : ", ";
280 InvalidFix
|= InsertPos
.isMacroID();
283 SourceLocation SemiColonEnd
;
284 if (auto NextToken
= Lexer::findNextToken(
285 S
->getEndLoc(), *Result
.SourceManager
, getLangOpts()))
286 SemiColonEnd
= NextToken
->getEndLoc();
291 diag(S
->getBeginLoc(), "%0 should be initialized in a member"
292 " initializer of the constructor")
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
);
303 Diag
<< FixItHint::CreateReplacement(ReplaceRange
, NewInit
);
305 SmallString
<128> Insertion({InsertPrefix
, Field
->getName(), "(",
306 NewInit
, AddComma
? "), " : ")"});
307 Diag
<< FixItHint::CreateInsertion(InsertPos
, Insertion
,
309 FirstToCtorInits
= areDiagsSelfContained();
311 Diag
<< FixItHint::CreateRemoval(
312 CharSourceRange::getCharRange(S
->getBeginLoc(), SemiColonEnd
));
318 } // namespace clang::tidy::cppcoreguidelines