clang-tools 20.0.0git
PreferMemberInitializerCheck.cpp
Go to the documentation of this file.
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
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
19
20static bool isControlStatement(const Stmt *S) {
21 return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
22 GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
23}
24
25static 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
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.
68 const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor,
69 llvm::DenseMap<const FieldDecl *, AssignedLevel> &AssignedFields) {
70 auto It = AssignedFields.try_emplace(Field, AssignedLevel::None).first;
71
72 if (!canAdvanceAssignment(It->second))
73 // fast path for already decided field.
74 return;
75
76 if (Field->getType().getCanonicalType()->isReferenceType()) {
77 // assign to reference type twice cannot be simplified to once.
78 It->second = AssignedLevel::HasSideEffect;
79 return;
80 }
81
82 auto MemberMatcher =
83 memberExpr(hasObjectExpression(cxxThisExpr()),
84 member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));
85 auto DeclMatcher = declRefExpr(
86 to(valueDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor)))));
87 const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher,
88 hasDescendant(MemberMatcher),
89 hasDescendant(DeclMatcher))),
90 *Init, Field->getASTContext())
91 .empty();
92 if (HasDependence) {
93 It->second = AssignedLevel::HasDependence;
94 return;
95 }
96}
97
99 const FieldDecl *Field;
100 const Expr *Init;
101};
102
103static std::optional<AssignmentPair>
104isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,
105 const CXXConstructorDecl *Ctor) {
106 if (const auto *BO = dyn_cast<BinaryOperator>(S)) {
107 if (BO->getOpcode() != BO_Assign)
108 return {};
109
110 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());
111 if (!ME)
112 return {};
113
114 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
115 if (!Field)
116 return {};
117
118 if (!isa<CXXThisExpr>(ME->getBase()))
119 return {};
120 const Expr *Init = BO->getRHS()->IgnoreParenImpCasts();
121 return AssignmentPair{Field, Init};
122 }
123 if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
124 if (COCE->getOperator() != OO_Equal)
125 return {};
126
127 const auto *ME =
128 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
129 if (!ME)
130 return {};
131
132 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
133 if (!Field)
134 return {};
135
136 if (!isa<CXXThisExpr>(ME->getBase()))
137 return {};
138 const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts();
139 return AssignmentPair{Field, Init};
140 }
141 return {};
142}
143
145 StringRef Name, ClangTidyContext *Context)
146 : ClangTidyCheck(Name, Context) {}
147
149 Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()),
150 unless(isInstantiated()),
151 unless(isDelegatingConstructor()))
152 .bind("ctor"),
153 this);
154}
155
157 const MatchFinder::MatchResult &Result) {
158 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
159 const auto *Body = cast<CompoundStmt>(Ctor->getBody());
160
161 const CXXRecordDecl *Class = Ctor->getParent();
162 bool FirstToCtorInits = true;
163
164 llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{};
165
166 for (const CXXCtorInitializer *Init : Ctor->inits())
167 if (FieldDecl *Field = Init->getMember())
168 updateAssignmentLevel(Field, Init->getInit(), Ctor, AssignedFields);
169
170 for (const Stmt *S : Body->body()) {
171 if (S->getBeginLoc().isMacroID()) {
172 StringRef MacroName = Lexer::getImmediateMacroName(
173 S->getBeginLoc(), *Result.SourceManager, getLangOpts());
174 if (MacroName.contains_insensitive("assert"))
175 return;
176 }
177 if (isControlStatement(S))
178 return;
179
181 return;
182
183 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
184 if (isNoReturnCallStatement(CondOp->getLHS()) ||
185 isNoReturnCallStatement(CondOp->getRHS()))
186 return;
187 }
188
189 std::optional<AssignmentPair> AssignmentToMember =
190 isAssignmentToMemberOf(Class, S, Ctor);
191 if (!AssignmentToMember)
192 continue;
193 const FieldDecl *Field = AssignmentToMember->Field;
194 const Expr *InitValue = AssignmentToMember->Init;
195 updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields);
196 if (!canAdvanceAssignment(AssignedFields[Field]))
197 continue;
198
199 StringRef InsertPrefix = "";
200 bool HasInitAlready = false;
201 SourceLocation InsertPos;
202 SourceRange ReplaceRange;
203 bool AddComma = false;
204 bool AddBrace = false;
205 bool InvalidFix = false;
206 unsigned Index = Field->getFieldIndex();
207 const CXXCtorInitializer *LastInListInit = nullptr;
208 for (const CXXCtorInitializer *Init : Ctor->inits()) {
209 if (!Init->isWritten() || Init->isInClassMemberInitializer())
210 continue;
211 if (Init->getMember() == Field) {
212 HasInitAlready = true;
213 if (isa<ImplicitValueInitExpr>(Init->getInit()))
214 InsertPos = Init->getRParenLoc();
215 else {
216 ReplaceRange = Init->getInit()->getSourceRange();
217 AddBrace = isa<InitListExpr>(Init->getInit());
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 if (AddBrace)
283 Diag << FixItHint::CreateReplacement(ReplaceRange,
284 ("{" + NewInit + "}").str());
285 else
286 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
287 } else {
288 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", NewInit,
289 AddComma ? "), " : ")"});
290 Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
291 FirstToCtorInits);
292 FirstToCtorInits = areDiagsSelfContained();
293 }
294 Diag << FixItHint::CreateRemoval(
295 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
296 }
297}
298
299} // namespace clang::tidy::cppcoreguidelines
static constexpr Index None
Definition: Bracket.cpp:73
llvm::SmallString< 256U > Name
const FieldDecl * Field
std::string MacroName
Definition: Preamble.cpp:240
::clang::DynTypedNode Node
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
bool areDiagsSelfContained() const
Returns true when the check is run in a use case when only 1 fix will be applied at a time.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
static void updateAssignmentLevel(const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor, llvm::DenseMap< const FieldDecl *, AssignedLevel > &AssignedFields)
static bool canAdvanceAssignment(AssignedLevel Level)
static bool isNoReturnCallStatement(const Stmt *S)
static bool isControlStatement(const Stmt *S)
static std::optional< AssignmentPair > isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, const CXXConstructorDecl *Ctor)