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 InvalidFix = false;
207 unsigned Index = Field->getFieldIndex();
208 const CXXCtorInitializer *LastInListInit = nullptr;
209 for (const CXXCtorInitializer *Init : Ctor->inits()) {
210 if (!Init->isWritten() || Init->isInClassMemberInitializer())
211 continue;
212 if (Init->getMember() == Field) {
213 HasInitAlready = true;
214 if (isa<ImplicitValueInitExpr>(Init->getInit()))
215 InsertPos = Init->getRParenLoc();
216 else {
217 ReplaceRange = Init->getInit()->getSourceRange();
218 }
219 break;
220 }
221 if (Init->isMemberInitializer() &&
222 Index < Init->getMember()->getFieldIndex()) {
223 InsertPos = Init->getSourceLocation();
224 // There are initializers after the one we are inserting, so add a
225 // comma after this insertion in order to not break anything.
226 AddComma = true;
227 break;
228 }
229 LastInListInit = Init;
230 }
231 if (HasInitAlready) {
232 if (InsertPos.isValid())
233 InvalidFix |= InsertPos.isMacroID();
234 else
235 InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
236 ReplaceRange.getEnd().isMacroID();
237 } else {
238 if (InsertPos.isInvalid()) {
239 if (LastInListInit) {
240 InsertPos =
241 Lexer::getLocForEndOfToken(LastInListInit->getRParenLoc(), 0,
242 *Result.SourceManager, getLangOpts());
243 // Inserting after the last constructor initializer, so we need a
244 // comma.
245 InsertPrefix = ", ";
246 } else {
247 InsertPos = Lexer::getLocForEndOfToken(
248 Ctor->getTypeSourceInfo()
249 ->getTypeLoc()
250 .getAs<clang::FunctionTypeLoc>()
251 .getLocalRangeEnd(),
252 0, *Result.SourceManager, getLangOpts());
253
254 // If this is first time in the loop, there are no initializers so
255 // `:` declares member initialization list. If this is a
256 // subsequent pass then we have already inserted a `:` so continue
257 // with a comma.
258 InsertPrefix = FirstToCtorInits ? " : " : ", ";
259 }
260 }
261 InvalidFix |= InsertPos.isMacroID();
262 }
263
264 SourceLocation SemiColonEnd;
265 if (auto NextToken = Lexer::findNextToken(
266 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
267 SemiColonEnd = NextToken->getEndLoc();
268 else
269 InvalidFix = true;
270
271 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member"
272 " initializer of the constructor")
273 << Field;
274 if (InvalidFix)
275 continue;
276 StringRef NewInit = Lexer::getSourceText(
277 Result.SourceManager->getExpansionRange(InitValue->getSourceRange()),
278 *Result.SourceManager, getLangOpts());
279 if (HasInitAlready) {
280 if (InsertPos.isValid())
281 Diag << FixItHint::CreateInsertion(InsertPos, NewInit);
282 else
283 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
284 } else {
285 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit,
286 AddComma ? "), " : ")"});
287 Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
288 FirstToCtorInits);
289 FirstToCtorInits = areDiagsSelfContained();
290 }
291 Diag << FixItHint::CreateRemoval(
292 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
293 }
294}
295
296} // namespace clang::tidy::cppcoreguidelines
297

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