[clang][modules] Don't prevent translation of FW_Private includes when explicitly...
[llvm-project.git] / clang-tools-extra / clang-tidy / cppcoreguidelines / PreferMemberInitializerCheck.cpp
blobb6daf8b936bde0f133fcab7cd4997f2567a5918f
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 //===----------------------------------------------------------------------===//
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);
27 if (!Call)
28 return false;
30 const FunctionDecl *Func = Call->getDirectCallee();
31 if (!Func)
32 return false;
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());
45 return false;
48 static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
49 if (isLiteral(Value) || isUnaryExprOfLiteral(Value))
50 return true;
52 if (const auto *DRE = dyn_cast<DeclRefExpr>(Value))
53 return isa<EnumConstantDecl>(DRE->getDecl());
55 return false;
58 namespace {
60 AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {
61 return Node.getFieldIndex() >= Index;
64 enum class AssignedLevel {
65 // Field is not assigned.
66 None,
67 // Field is assigned.
68 Default,
69 // Assignment of field has side effect:
70 // - assign to reference.
71 // FIXME: support other side effect.
72 HasSideEffect,
73 // Assignment of field has data dependence.
74 HasDependence,
77 } // namespace
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
84 // it.
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.
97 return;
99 if (Field->getType().getCanonicalType()->isReferenceType()) {
100 // assign to reference type twice cannot be simplified to once.
101 It->second = AssignedLevel::HasSideEffect;
102 return;
105 auto MemberMatcher =
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())
114 .empty();
115 if (HasDependence) {
116 It->second = AssignedLevel::HasDependence;
117 return;
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());
129 if (!ME)
130 return std::make_pair(nullptr, nullptr);
132 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
133 if (!Field)
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);
145 const auto *ME =
146 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
147 if (!ME)
148 return std::make_pair(nullptr, nullptr);
150 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
151 if (!Field)
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")),
167 UseAssignment(
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()))
182 .bind("ctor"),
183 this);
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"))
205 return;
207 if (isControlStatement(S))
208 return;
210 if (isNoReturnCallStatement(S))
211 return;
213 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
214 if (isNoReturnCallStatement(CondOp->getLHS()) ||
215 isNoReturnCallStatement(CondOp->getRHS()))
216 return;
219 const FieldDecl *Field = nullptr;
220 const Expr *InitValue = nullptr;
221 std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor);
222 if (!Field)
223 continue;
224 updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields);
225 if (!canAdvanceAssignment(AssignedFields[Field]))
226 continue;
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();
245 else
246 InvalidFix = true;
247 auto Diag =
248 diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
249 " default member initializer")
250 << Field;
251 if (InvalidFix)
252 continue;
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);
266 } else {
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())
277 continue;
278 if (Init->getMember() == Field) {
279 HasInitAlready = true;
280 if (isa<ImplicitValueInitExpr>(Init->getInit()))
281 InsertPos = Init->getRParenLoc();
282 else {
283 ReplaceRange = Init->getInit()->getSourceRange();
285 break;
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.
292 AddComma = true;
293 break;
295 LastInListInit = Init;
297 if (HasInitAlready) {
298 if (InsertPos.isValid())
299 InvalidFix |= InsertPos.isMacroID();
300 else
301 InvalidFix |= ReplaceRange.getBegin().isMacroID() ||
302 ReplaceRange.getEnd().isMacroID();
303 } else {
304 if (InsertPos.isInvalid()) {
305 if (LastInListInit) {
306 InsertPos = Lexer::getLocForEndOfToken(
307 LastInListInit->getRParenLoc(), 0, *Result.SourceManager,
308 getLangOpts());
309 // Inserting after the last constructor initializer, so we need a
310 // comma.
311 InsertPrefix = ", ";
312 } else {
313 InsertPos = Lexer::getLocForEndOfToken(
314 Ctor->getTypeSourceInfo()
315 ->getTypeLoc()
316 .getAs<clang::FunctionTypeLoc>()
317 .getLocalRangeEnd(),
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
323 // with a comma.
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();
334 else
335 InvalidFix = true;
337 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member"
338 " initializer of the constructor")
339 << Field;
340 if (InvalidFix)
341 continue;
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);
348 else
349 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit);
350 } else {
351 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(",
352 NewInit, AddComma ? "), " : ")"});
353 Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
354 FirstToCtorInits);
355 FirstToCtorInits = areDiagsSelfContained();
357 Diag << FixItHint::CreateRemoval(
358 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd));
363 } // namespace clang::tidy::cppcoreguidelines