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/AST/Decl.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 #include "llvm/ADT/DenseMap.h"
16 using namespace clang::ast_matchers
;
18 namespace clang::tidy::cppcoreguidelines
{
20 static bool isControlStatement(const Stmt
*S
) {
21 return isa
<IfStmt
, SwitchStmt
, ForStmt
, WhileStmt
, DoStmt
, ReturnStmt
,
22 GotoStmt
, CXXTryStmt
, CXXThrowExpr
>(S
);
25 static bool isNoReturnCallStatement(const Stmt
*S
) {
26 const auto *Call
= dyn_cast
<CallExpr
>(S
);
30 const FunctionDecl
*Func
= Call
->getDirectCallee();
34 return Func
->isNoReturn();
37 static bool isLiteral(const Expr
*E
) {
38 return isa
<StringLiteral
, CharacterLiteral
, IntegerLiteral
, FloatingLiteral
,
39 CXXBoolLiteralExpr
, CXXNullPtrLiteralExpr
>(E
);
42 static bool isUnaryExprOfLiteral(const Expr
*E
) {
43 if (const auto *UnOp
= dyn_cast
<UnaryOperator
>(E
))
44 return isLiteral(UnOp
->getSubExpr());
48 static bool shouldBeDefaultMemberInitializer(const Expr
*Value
) {
49 if (isLiteral(Value
) || isUnaryExprOfLiteral(Value
))
52 if (const auto *DRE
= dyn_cast
<DeclRefExpr
>(Value
))
53 return isa
<EnumConstantDecl
>(DRE
->getDecl());
60 AST_MATCHER_P(FieldDecl
, indexNotLessThan
, unsigned, Index
) {
61 return Node
.getFieldIndex() >= Index
;
64 enum class AssignedLevel
{
65 // Field is not assigned.
69 // Assignment of field has side effect:
70 // - assign to reference.
71 // FIXME: support other side effect.
73 // Assignment of field has data dependence.
79 static bool canAdvanceAssignment(AssignedLevel Level
) {
80 return Level
== AssignedLevel::None
|| Level
== AssignedLevel::Default
;
83 // Checks if Field is initialised using a field that will be initialised after
85 // TODO: Probably should guard against function calls that could have side
86 // effects or if they do reference another field that's initialized before
87 // this field, but is modified before the assignment.
88 static void updateAssignmentLevel(
89 const FieldDecl
*Field
, const Expr
*Init
, const CXXConstructorDecl
*Ctor
,
90 llvm::DenseMap
<const FieldDecl
*, AssignedLevel
> &AssignedFields
) {
91 auto It
= AssignedFields
.find(Field
);
92 if (It
== AssignedFields
.end())
93 It
= AssignedFields
.insert({Field
, AssignedLevel::None
}).first
;
95 if (!canAdvanceAssignment(It
->second
))
96 // fast path for already decided field.
99 if (Field
->getType().getCanonicalType()->isReferenceType()) {
100 // assign to reference type twice cannot be simplified to once.
101 It
->second
= AssignedLevel::HasSideEffect
;
106 memberExpr(hasObjectExpression(cxxThisExpr()),
107 member(fieldDecl(indexNotLessThan(Field
->getFieldIndex()))));
108 auto DeclMatcher
= declRefExpr(
109 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor
)))));
110 const bool HasDependence
= !match(expr(anyOf(MemberMatcher
, DeclMatcher
,
111 hasDescendant(MemberMatcher
),
112 hasDescendant(DeclMatcher
))),
113 *Init
, Field
->getASTContext())
116 It
->second
= AssignedLevel::HasDependence
;
121 static std::pair
<const FieldDecl
*, const Expr
*>
122 isAssignmentToMemberOf(const CXXRecordDecl
*Rec
, const Stmt
*S
,
123 const CXXConstructorDecl
*Ctor
) {
124 if (const auto *BO
= dyn_cast
<BinaryOperator
>(S
)) {
125 if (BO
->getOpcode() != BO_Assign
)
126 return std::make_pair(nullptr, nullptr);
128 const auto *ME
= dyn_cast
<MemberExpr
>(BO
->getLHS()->IgnoreParenImpCasts());
130 return std::make_pair(nullptr, nullptr);
132 const auto *Field
= dyn_cast
<FieldDecl
>(ME
->getMemberDecl());
134 return std::make_pair(nullptr, nullptr);
136 if (!isa
<CXXThisExpr
>(ME
->getBase()))
137 return std::make_pair(nullptr, nullptr);
138 const Expr
*Init
= BO
->getRHS()->IgnoreParenImpCasts();
139 return std::make_pair(Field
, Init
);
141 if (const auto *COCE
= dyn_cast
<CXXOperatorCallExpr
>(S
)) {
142 if (COCE
->getOperator() != OO_Equal
)
143 return std::make_pair(nullptr, nullptr);
146 dyn_cast
<MemberExpr
>(COCE
->getArg(0)->IgnoreParenImpCasts());
148 return std::make_pair(nullptr, nullptr);
150 const auto *Field
= dyn_cast
<FieldDecl
>(ME
->getMemberDecl());
152 return std::make_pair(nullptr, nullptr);
154 if (!isa
<CXXThisExpr
>(ME
->getBase()))
155 return std::make_pair(nullptr, nullptr);
156 const Expr
*Init
= COCE
->getArg(1)->IgnoreParenImpCasts();
157 return std::make_pair(Field
, Init
);
159 return std::make_pair(nullptr, nullptr);
162 PreferMemberInitializerCheck::PreferMemberInitializerCheck(
163 StringRef Name
, ClangTidyContext
*Context
)
164 : ClangTidyCheck(Name
, Context
),
165 IsUseDefaultMemberInitEnabled(
166 Context
->isCheckEnabled("modernize-use-default-member-init")),
168 Options
.get("UseAssignment",
169 OptionsView("modernize-use-default-member-init",
170 Context
->getOptions().CheckOptions
, Context
)
171 .get("UseAssignment", false))) {}
173 void PreferMemberInitializerCheck::storeOptions(
174 ClangTidyOptions::OptionMap
&Opts
) {
175 Options
.store(Opts
, "UseAssignment", UseAssignment
);
178 void PreferMemberInitializerCheck::registerMatchers(MatchFinder
*Finder
) {
179 Finder
->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()),
180 unless(isInstantiated()),
181 unless(isDelegatingConstructor()))
186 void PreferMemberInitializerCheck::check(
187 const MatchFinder::MatchResult
&Result
) {
188 const auto *Ctor
= Result
.Nodes
.getNodeAs
<CXXConstructorDecl
>("ctor");
189 const auto *Body
= cast
<CompoundStmt
>(Ctor
->getBody());
191 const CXXRecordDecl
*Class
= Ctor
->getParent();
192 bool FirstToCtorInits
= true;
194 llvm::DenseMap
<const FieldDecl
*, AssignedLevel
> AssignedFields
{};
196 for (const CXXCtorInitializer
*Init
: Ctor
->inits())
197 if (FieldDecl
*Field
= Init
->getMember())
198 updateAssignmentLevel(Field
, Init
->getInit(), Ctor
, AssignedFields
);
200 for (const Stmt
*S
: Body
->body()) {
201 if (S
->getBeginLoc().isMacroID()) {
202 StringRef MacroName
= Lexer::getImmediateMacroName(
203 S
->getBeginLoc(), *Result
.SourceManager
, getLangOpts());
204 if (MacroName
.contains_insensitive("assert"))
207 if (isControlStatement(S
))
210 if (isNoReturnCallStatement(S
))
213 if (const auto *CondOp
= dyn_cast
<ConditionalOperator
>(S
)) {
214 if (isNoReturnCallStatement(CondOp
->getLHS()) ||
215 isNoReturnCallStatement(CondOp
->getRHS()))
219 const FieldDecl
*Field
= nullptr;
220 const Expr
*InitValue
= nullptr;
221 std::tie(Field
, InitValue
) = isAssignmentToMemberOf(Class
, S
, Ctor
);
224 updateAssignmentLevel(Field
, InitValue
, Ctor
, AssignedFields
);
225 if (!canAdvanceAssignment(AssignedFields
[Field
]))
227 const bool IsInDefaultMemberInitializer
=
228 IsUseDefaultMemberInitEnabled
&& getLangOpts().CPlusPlus11
&&
229 Ctor
->isDefaultConstructor() &&
230 (getLangOpts().CPlusPlus20
|| !Field
->isBitField()) &&
231 !Field
->hasInClassInitializer() &&
232 (!isa
<RecordDecl
>(Class
->getDeclContext()) ||
233 !cast
<RecordDecl
>(Class
->getDeclContext())->isUnion()) &&
234 shouldBeDefaultMemberInitializer(InitValue
);
235 if (IsInDefaultMemberInitializer
) {
236 bool InvalidFix
= false;
237 SourceLocation FieldEnd
=
238 Lexer::getLocForEndOfToken(Field
->getSourceRange().getEnd(), 0,
239 *Result
.SourceManager
, getLangOpts());
240 InvalidFix
|= FieldEnd
.isInvalid() || FieldEnd
.isMacroID();
241 SourceLocation SemiColonEnd
;
242 if (auto NextToken
= Lexer::findNextToken(
243 S
->getEndLoc(), *Result
.SourceManager
, getLangOpts()))
244 SemiColonEnd
= NextToken
->getEndLoc();
248 diag(S
->getBeginLoc(), "%0 should be initialized in an in-class"
249 " default member initializer")
253 CharSourceRange StmtRange
=
254 CharSourceRange::getCharRange(S
->getBeginLoc(), SemiColonEnd
);
256 SmallString
<128> Insertion(
257 {UseAssignment
? " = " : "{",
258 Lexer::getSourceText(
259 CharSourceRange(InitValue
->getSourceRange(), true),
260 *Result
.SourceManager
, getLangOpts()),
261 UseAssignment
? "" : "}"});
263 Diag
<< FixItHint::CreateInsertion(FieldEnd
, Insertion
)
264 << FixItHint::CreateRemoval(StmtRange
);
267 StringRef InsertPrefix
= "";
268 bool HasInitAlready
= false;
269 SourceLocation InsertPos
;
270 SourceRange ReplaceRange
;
271 bool AddComma
= false;
272 bool InvalidFix
= false;
273 unsigned Index
= Field
->getFieldIndex();
274 const CXXCtorInitializer
*LastInListInit
= nullptr;
275 for (const CXXCtorInitializer
*Init
: Ctor
->inits()) {
276 if (!Init
->isWritten() || Init
->isInClassMemberInitializer())
278 if (Init
->getMember() == Field
) {
279 HasInitAlready
= true;
280 if (isa
<ImplicitValueInitExpr
>(Init
->getInit()))
281 InsertPos
= Init
->getRParenLoc();
283 ReplaceRange
= Init
->getInit()->getSourceRange();
287 if (Init
->isMemberInitializer() &&
288 Index
< Init
->getMember()->getFieldIndex()) {
289 InsertPos
= Init
->getSourceLocation();
290 // There are initializers after the one we are inserting, so add a
291 // comma after this insertion in order to not break anything.
295 LastInListInit
= Init
;
297 if (HasInitAlready
) {
298 if (InsertPos
.isValid())
299 InvalidFix
|= InsertPos
.isMacroID();
301 InvalidFix
|= ReplaceRange
.getBegin().isMacroID() ||
302 ReplaceRange
.getEnd().isMacroID();
304 if (InsertPos
.isInvalid()) {
305 if (LastInListInit
) {
306 InsertPos
= Lexer::getLocForEndOfToken(
307 LastInListInit
->getRParenLoc(), 0, *Result
.SourceManager
,
309 // Inserting after the last constructor initializer, so we need a
313 InsertPos
= Lexer::getLocForEndOfToken(
314 Ctor
->getTypeSourceInfo()
316 .getAs
<clang::FunctionTypeLoc
>()
318 0, *Result
.SourceManager
, getLangOpts());
320 // If this is first time in the loop, there are no initializers so
321 // `:` declares member initialization list. If this is a
322 // subsequent pass then we have already inserted a `:` so continue
324 InsertPrefix
= FirstToCtorInits
? " : " : ", ";
327 InvalidFix
|= InsertPos
.isMacroID();
330 SourceLocation SemiColonEnd
;
331 if (auto NextToken
= Lexer::findNextToken(
332 S
->getEndLoc(), *Result
.SourceManager
, getLangOpts()))
333 SemiColonEnd
= NextToken
->getEndLoc();
337 auto Diag
= diag(S
->getBeginLoc(), "%0 should be initialized in a member"
338 " initializer of the constructor")
342 StringRef NewInit
= Lexer::getSourceText(
343 CharSourceRange(InitValue
->getSourceRange(), true),
344 *Result
.SourceManager
, getLangOpts());
345 if (HasInitAlready
) {
346 if (InsertPos
.isValid())
347 Diag
<< FixItHint::CreateInsertion(InsertPos
, NewInit
);
349 Diag
<< FixItHint::CreateReplacement(ReplaceRange
, NewInit
);
351 SmallString
<128> Insertion({InsertPrefix
, Field
->getName(), "(",
352 NewInit
, AddComma
? "), " : ")"});
353 Diag
<< FixItHint::CreateInsertion(InsertPos
, Insertion
,
355 FirstToCtorInits
= areDiagsSelfContained();
357 Diag
<< FixItHint::CreateRemoval(
358 CharSourceRange::getCharRange(S
->getBeginLoc(), SemiColonEnd
));
363 } // namespace clang::tidy::cppcoreguidelines