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/AST/Decl.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14#include "llvm/ADT/DenseMap.h"
15
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::cppcoreguidelines {
19
20static bool isControlStatement(const Stmt *S) {
21 return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
22 GotoStmt, CXXTryStmt, CXXThrowExpr>(Val: S);
23}
24
25static bool isNoReturnCallStatement(const Stmt *S) {
26 const auto *Call = dyn_cast<CallExpr>(Val: 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
37namespace {
38
39AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {
40 return Node.getFieldIndex() >= Index;
41}
42
43enum class AssignedLevel {
44 // Field is not assigned.
45 None,
46 // Field is assigned.
47 Default,
48 // Assignment of field has side effect:
49 // - assign to reference.
50 // FIXME: support other side effect.
51 HasSideEffect,
52 // Assignment of field has data dependence.
53 HasDependence,
54};
55
56} // namespace
57
58static bool canAdvanceAssignment(AssignedLevel Level) {
59 return Level == AssignedLevel::None || Level == AssignedLevel::Default;
60}
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
66// this field, but is modified before the assignment.
67static void updateAssignmentLevel(
68 const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor,
69 llvm::DenseMap<const FieldDecl *, AssignedLevel> &AssignedFields) {
70 auto It = AssignedFields.find(Val: Field);
71 if (It == AssignedFields.end())
72 It = AssignedFields.insert(KV: {Field, AssignedLevel::None}).first;
73
74 if (!canAdvanceAssignment(Level: It->second))
75 // fast path for already decided field.
76 return;
77
78 if (Field->getType().getCanonicalType()->isReferenceType()) {
79 // assign to reference type twice cannot be simplified to once.
80 It->second = AssignedLevel::HasSideEffect;
81 return;
82 }
83
84 auto MemberMatcher =
85 memberExpr(hasObjectExpression(InnerMatcher: cxxThisExpr()),
86 member(InnerMatcher: fieldDecl(indexNotLessThan(Index: Field->getFieldIndex()))));
87 auto DeclMatcher = declRefExpr(
88 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor)))));
89 const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher,
90 hasDescendant(MemberMatcher),
91 hasDescendant(DeclMatcher))),
92 *Init, Field->getASTContext())
93 .empty();
94 if (HasDependence) {
95 It->second = AssignedLevel::HasDependence;
96 return;
97 }
98}
99
100struct AssignmentPair {
101 const FieldDecl *Field;
102 const Expr *Init;
103};
104
105static std::optional<AssignmentPair>
106isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,
107 const CXXConstructorDecl *Ctor) {
108 if (const auto *BO = dyn_cast<BinaryOperator>(Val: S)) {
109 if (BO->getOpcode() != BO_Assign)
110 return {};
111
112 const auto *ME = dyn_cast<MemberExpr>(Val: BO->getLHS()->IgnoreParenImpCasts());
113 if (!ME)
114 return {};
115
116 const auto *Field = dyn_cast<FieldDecl>(Val: ME->getMemberDecl());
117 if (!Field)
118 return {};
119
120 if (!isa<CXXThisExpr>(Val: ME->getBase()))
121 return {};
122 const Expr *Init = BO->getRHS()->IgnoreParenImpCasts();
123 return AssignmentPair{.Field: Field, .Init: Init};
124 }
125 if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(Val: S)) {
126 if (COCE->getOperator() != OO_Equal)
127 return {};
128
129 const auto *ME =
130 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
131 if (!ME)
132 return {};
133
134 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
135 if (!Field)
136 return {};
137
138 if (!isa<CXXThisExpr>(ME->getBase()))
139 return {};
140 const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts();
141 return AssignmentPair{Field, Init};
142 }
143 return {};
144}
145
146PreferMemberInitializerCheck::PreferMemberInitializerCheck(
147 StringRef Name, ClangTidyContext *Context)
148 : ClangTidyCheck(Name, Context) {}
149
150void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {
151 Finder->addMatcher(NodeMatch: cxxConstructorDecl(hasBody(InnerMatcher: compoundStmt()),
152 unless(isInstantiated()),
153 unless(isDelegatingConstructor()))
154 .bind(ID: "ctor"),
155 Action: this);
156}
157
158void PreferMemberInitializerCheck::check(
159 const MatchFinder::MatchResult &Result) {
160 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(ID: "ctor");
161 const auto *Body = cast<CompoundStmt>(Val: Ctor->getBody());
162
163 const CXXRecordDecl *Class = Ctor->getParent();
164 bool FirstToCtorInits = true;
165
166 llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{};
167
168 for (const CXXCtorInitializer *Init : Ctor->inits())
169 if (FieldDecl *Field = Init->getMember())
170 updateAssignmentLevel(Field, Init: Init->getInit(), Ctor, AssignedFields);
171
172 for (const Stmt *S : Body->body()) {
173 if (S->getBeginLoc().isMacroID()) {
174 StringRef MacroName = Lexer::getImmediateMacroName(
175 S->getBeginLoc(), *Result.SourceManager, getLangOpts());
176 if (MacroName.contains_insensitive("assert"))
177 return;
178 }
179 if (isControlStatement(S))
180 return;
181
182 if (isNoReturnCallStatement(S))
183 return;
184
185 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
186 if (isNoReturnCallStatement(CondOp->getLHS()) ||
187 isNoReturnCallStatement(CondOp->getRHS()))
188 return;
189 }
190
191 std::optional<AssignmentPair> AssignmentToMember =
192 isAssignmentToMemberOf(Class, S, Ctor);
193 if (!AssignmentToMember)
194 continue;
195 const FieldDecl *Field = AssignmentToMember->Field;
196 const Expr *InitValue = AssignmentToMember->Init;
197 updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields);
198 if (!canAdvanceAssignment(AssignedFields[Field]))
199 continue;
200
201 StringRef InsertPrefix = "";
202 bool HasInitAlready = false;
203 SourceLocation InsertPos;
204 SourceRange ReplaceRange;
205 bool AddComma = false;
206 bool AddBrace = false;
207 bool InvalidFix = false;
208 unsigned Index = Field->getFieldIndex();
209 const CXXCtorInitializer *LastInListInit = nullptr;
210 for (const CXXCtorInitializer *Init : Ctor->inits()) {
211 if (!Init->isWritten() || Init->isInClassMemberInitializer())
212 continue;
213 if (Init->getMember() == Field) {
214 HasInitAlready = true;
215 if (isa<ImplicitValueInitExpr>(Init->getInit()))
216 InsertPos = Init->getRParenLoc();
217 else {
218 ReplaceRange = Init->getInit()->getSourceRange();
219 AddBrace = isa<InitListExpr>(Init->getInit());
220 }
221 break;
222 }
223 if (Init->isMemberInitializer() &&
224 Index < Init->getMember()->getFieldIndex()) {
225 InsertPos = Init->getSourceLocation();
226 // There are initializers after the one we are inserting, so add a
227 // comma after this insertion in order to not break anything.
228 AddComma = true;
229 break;
230 }
231 LastInListInit = Init;
232 }
233 if (HasInitAlready) {
234 if (InsertPos.isValid())
235 InvalidFix |= InsertPos.isMacroID();
236 else
237 InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
238 ReplaceRange.getEnd().isMacroID();
239 } else {
240 if (InsertPos.isInvalid()) {
241 if (LastInListInit) {
242 InsertPos =
243 Lexer::getLocForEndOfToken(LastInListInit->getRParenLoc(), 0,
244 *Result.SourceManager, getLangOpts());
245 // Inserting after the last constructor initializer, so we need a
246 // comma.
247 InsertPrefix = ", ";
248 } else {
249 InsertPos = Lexer::getLocForEndOfToken(
250 Ctor->getTypeSourceInfo()
251 ->getTypeLoc()
252 .getAs<clang::FunctionTypeLoc>()
253 .getLocalRangeEnd(),
254 0, *Result.SourceManager, getLangOpts());
255
256 // If this is first time in the loop, there are no initializers so
257 // `:` declares member initialization list. If this is a
258 // subsequent pass then we have already inserted a `:` so continue
259 // with a comma.
260 InsertPrefix = FirstToCtorInits ? " : " : ", ";
261 }
262 }
263 InvalidFix |= InsertPos.isMacroID();
264 }
265
266 SourceLocation SemiColonEnd;
267 if (auto NextToken = Lexer::findNextToken(
268 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
269 SemiColonEnd = NextToken->getEndLoc();
270 else
271 InvalidFix = true;
272
273 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member"
274 " initializer of the constructor")
275 << Field;
276 if (InvalidFix)
277 continue;
278 StringRef NewInit = Lexer::getSourceText(
279 Result.SourceManager->getExpansionRange(InitValue->getSourceRange()),
280 *Result.SourceManager, getLangOpts());
281 if (HasInitAlready) {
282 if (InsertPos.isValid())
283 Diag << FixItHint::CreateInsertion(InsertPos, NewInit);
284 else if (AddBrace)
285 Diag << FixItHint::CreateReplacement(ReplaceRange,
286 ("{" + NewInit + "}").str());
287 else
288 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
289 } else {
290 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit,
291 AddComma ? "), " : ")"});
292 Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
293 FirstToCtorInits);
294 FirstToCtorInits = areDiagsSelfContained();
295 }
296 Diag << FixItHint::CreateRemoval(
297 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
298 }
299}
300
301} // namespace clang::tidy::cppcoreguidelines
302

source code of clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp