clang-tools 18.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/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13
14using namespace clang::ast_matchers;
15
17
18static bool isControlStatement(const Stmt *S) {
19 return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
20 GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
21}
22
23static bool isNoReturnCallStatement(const Stmt *S) {
24 const auto *Call = dyn_cast<CallExpr>(S);
25 if (!Call)
26 return false;
27
28 const FunctionDecl *Func = Call->getDirectCallee();
29 if (!Func)
30 return false;
31
32 return Func->isNoReturn();
33}
34
35static bool isLiteral(const Expr *E) {
36 return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
37 CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
38}
39
40static bool isUnaryExprOfLiteral(const Expr *E) {
41 if (const auto *UnOp = dyn_cast<UnaryOperator>(E))
42 return isLiteral(UnOp->getSubExpr());
43 return false;
44}
45
46static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
47 if (isLiteral(Value) || isUnaryExprOfLiteral(Value))
48 return true;
49
50 if (const auto *DRE = dyn_cast<DeclRefExpr>(Value))
51 return isa<EnumConstantDecl>(DRE->getDecl());
52
53 return false;
54}
55
56namespace {
57AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {
58 return Node.getFieldIndex() >= Index;
59}
60} // namespace
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 this
66// field, but is modified before the assignment.
67static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init,
68 const CXXConstructorDecl *Context) {
69
70 auto MemberMatcher =
71 memberExpr(hasObjectExpression(cxxThisExpr()),
72 member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));
73
74 auto DeclMatcher = declRefExpr(
75 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context)))));
76
77 return match(expr(anyOf(MemberMatcher, DeclMatcher,
78 hasDescendant(MemberMatcher),
79 hasDescendant(DeclMatcher))),
80 *Init, Field->getASTContext())
81 .empty();
82}
83
84static std::pair<const FieldDecl *, const Expr *>
85isAssignmentToMemberOf(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);
90
91 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());
92 if (!ME)
93 return std::make_pair(nullptr, nullptr);
94
95 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
96 if (!Field)
97 return std::make_pair(nullptr, nullptr);
98
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);
107
108 const auto *ME =
109 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
110 if (!ME)
111 return std::make_pair(nullptr, nullptr);
112
113 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
114 if (!Field)
115 return std::make_pair(nullptr, nullptr);
116
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);
122 }
123
124 return std::make_pair(nullptr, nullptr);
125}
126
128 StringRef Name, ClangTidyContext *Context)
129 : ClangTidyCheck(Name, Context),
130 IsUseDefaultMemberInitEnabled(
131 Context->isCheckEnabled("modernize-use-default-member-init")),
132 UseAssignment(
133 Options.get("UseAssignment",
134 OptionsView("modernize-use-default-member-init",
135 Context->getOptions().CheckOptions, Context)
136 .get("UseAssignment", false))) {}
137
140 Options.store(Opts, "UseAssignment", UseAssignment);
141}
142
144 Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()),
145 unless(isInstantiated()),
146 unless(isDelegatingConstructor()))
147 .bind("ctor"),
148 this);
149}
150
152 const MatchFinder::MatchResult &Result) {
153 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
154 const auto *Body = cast<CompoundStmt>(Ctor->getBody());
155
156 const CXXRecordDecl *Class = Ctor->getParent();
157 bool FirstToCtorInits = true;
158
159 for (const Stmt *S : Body->body()) {
160 if (S->getBeginLoc().isMacroID()) {
161 StringRef MacroName = Lexer::getImmediateMacroName(
162 S->getBeginLoc(), *Result.SourceManager, getLangOpts());
163 if (MacroName.contains_insensitive("assert"))
164 return;
165 }
166 if (isControlStatement(S))
167 return;
168
170 return;
171
172 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
173 if (isNoReturnCallStatement(CondOp->getLHS()) ||
174 isNoReturnCallStatement(CondOp->getRHS()))
175 return;
176 }
177
178 const FieldDecl *Field = nullptr;
179 const Expr *InitValue = nullptr;
180 std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor);
181 if (Field) {
182 if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
183 Ctor->isDefaultConstructor() &&
184 (getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
185 !Field->hasInClassInitializer() &&
186 (!isa<RecordDecl>(Class->getDeclContext()) ||
187 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
189
190 bool InvalidFix = false;
191 SourceLocation FieldEnd =
192 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
193 *Result.SourceManager, getLangOpts());
194 InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID();
195 SourceLocation SemiColonEnd;
196 if (auto NextToken = Lexer::findNextToken(
197 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
198 SemiColonEnd = NextToken->getEndLoc();
199 else
200 InvalidFix = true;
201 auto Diag =
202 diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
203 " default member initializer")
204 << Field;
205 if (InvalidFix)
206 continue;
207 CharSourceRange StmtRange =
208 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
209
210 SmallString<128> Insertion(
211 {UseAssignment ? " = " : "{",
212 Lexer::getSourceText(
213 CharSourceRange(InitValue->getSourceRange(), true),
214 *Result.SourceManager, getLangOpts()),
215 UseAssignment ? "" : "}"});
216
217 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion)
218 << FixItHint::CreateRemoval(StmtRange);
219
220 } else {
221 StringRef InsertPrefix = "";
222 bool HasInitAlready = false;
223 SourceLocation InsertPos;
224 SourceRange ReplaceRange;
225 bool AddComma = false;
226 bool InvalidFix = false;
227 unsigned Index = Field->getFieldIndex();
228 const CXXCtorInitializer *LastInListInit = nullptr;
229 for (const CXXCtorInitializer *Init : Ctor->inits()) {
230 if (!Init->isWritten() || Init->isInClassMemberInitializer())
231 continue;
232 if (Init->getMember() == Field) {
233 HasInitAlready = true;
234 if (isa<ImplicitValueInitExpr>(Init->getInit()))
235 InsertPos = Init->getRParenLoc();
236 else {
237 ReplaceRange = Init->getInit()->getSourceRange();
238 }
239 break;
240 }
241 if (Init->isMemberInitializer() &&
242 Index < Init->getMember()->getFieldIndex()) {
243 InsertPos = Init->getSourceLocation();
244 // There are initializers after the one we are inserting, so add a
245 // comma after this insertion in order to not break anything.
246 AddComma = true;
247 break;
248 }
249 LastInListInit = Init;
250 }
251 if (HasInitAlready) {
252 if (InsertPos.isValid())
253 InvalidFix |= InsertPos.isMacroID();
254 else
255 InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
256 ReplaceRange.getEnd().isMacroID();
257 } else {
258 if (InsertPos.isInvalid()) {
259 if (LastInListInit) {
260 InsertPos = Lexer::getLocForEndOfToken(
261 LastInListInit->getRParenLoc(), 0, *Result.SourceManager,
262 getLangOpts());
263 // Inserting after the last constructor initializer, so we need a
264 // comma.
265 InsertPrefix = ", ";
266 } else {
267 InsertPos = Lexer::getLocForEndOfToken(
268 Ctor->getTypeSourceInfo()
269 ->getTypeLoc()
270 .getAs<clang::FunctionTypeLoc>()
271 .getLocalRangeEnd(),
272 0, *Result.SourceManager, getLangOpts());
273
274 // If this is first time in the loop, there are no initializers so
275 // `:` declares member initialization list. If this is a
276 // subsequent pass then we have already inserted a `:` so continue
277 // with a comma.
278 InsertPrefix = FirstToCtorInits ? " : " : ", ";
279 }
280 }
281 InvalidFix |= InsertPos.isMacroID();
282 }
283
284 SourceLocation SemiColonEnd;
285 if (auto NextToken = Lexer::findNextToken(
286 S->getEndLoc(), *Result.SourceManager, getLangOpts()))
287 SemiColonEnd = NextToken->getEndLoc();
288 else
289 InvalidFix = true;
290
291 auto Diag =
292 diag(S->getBeginLoc(), "%0 should be initialized in a member"
293 " initializer of the constructor")
294 << Field;
295 if (InvalidFix)
296 continue;
297 StringRef NewInit = Lexer::getSourceText(
298 CharSourceRange(InitValue->getSourceRange(), true),
299 *Result.SourceManager, getLangOpts());
300 if (HasInitAlready) {
301 if (InsertPos.isValid())
302 Diag << FixItHint::CreateInsertion(InsertPos, NewInit);
303 else
304 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
305 } else {
306 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(",
307 NewInit, AddComma ? "), " : ")"});
308 Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
309 FirstToCtorInits);
310 FirstToCtorInits = areDiagsSelfContained();
311 }
312 Diag << FixItHint::CreateRemoval(
313 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
314 }
315 }
316 }
317}
318
319} // namespace clang::tidy::cppcoreguidelines
const Expr * E
llvm::StringRef Name
const FieldDecl * Field
std::string MacroName
Definition: Preamble.cpp:240
::clang::DynTypedNode Node
Provides access to the ClangTidyCheck options via check-local names.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
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.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
static bool isUnaryExprOfLiteral(const Expr *E)
static bool shouldBeDefaultMemberInitializer(const Expr *Value)
static std::pair< const FieldDecl *, const Expr * > isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, const CXXConstructorDecl *Ctor)
static bool isNoReturnCallStatement(const Stmt *S)
static bool isControlStatement(const Stmt *S)
static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Context)
llvm::StringMap< ClangTidyValue > OptionMap