clang-tools  14.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 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace cppcoreguidelines {
19 
20 static bool isControlStatement(const Stmt *S) {
21  return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
22  GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
23 }
24 
25 static 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 
37 static bool isLiteral(const Expr *E) {
38  return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
39  CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
40 }
41 
42 static bool isUnaryExprOfLiteral(const Expr *E) {
43  if (const auto *UnOp = dyn_cast<UnaryOperator>(E))
44  return isLiteral(UnOp->getSubExpr());
45  return false;
46 }
47 
48 static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
49  if (isLiteral(Value) || isUnaryExprOfLiteral(Value))
50  return true;
51 
52  if (const auto *DRE = dyn_cast<DeclRefExpr>(Value))
53  return isa<EnumConstantDecl>(DRE->getDecl());
54 
55  return false;
56 }
57 
58 namespace {
59 AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {
60  return Node.getFieldIndex() >= Index;
61 }
62 } // namespace
63 
64 // Checks if Field is initialised using a field that will be initialised after
65 // it.
66 // TODO: Probably should guard against function calls that could have side
67 // effects or if they do reference another field that's initialized before this
68 // field, but is modified before the assignment.
69 static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init,
70  const CXXConstructorDecl *Context) {
71 
72  auto MemberMatcher =
73  memberExpr(hasObjectExpression(cxxThisExpr()),
74  member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));
75 
76  auto DeclMatcher = declRefExpr(
77  to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context)))));
78 
79  return match(expr(anyOf(MemberMatcher, DeclMatcher,
80  hasDescendant(MemberMatcher),
81  hasDescendant(DeclMatcher))),
82  *Init, Field->getASTContext())
83  .empty();
84 }
85 
86 static std::pair<const FieldDecl *, const Expr *>
87 isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,
88  const CXXConstructorDecl *Ctor) {
89  if (const auto *BO = dyn_cast<BinaryOperator>(S)) {
90  if (BO->getOpcode() != BO_Assign)
91  return std::make_pair(nullptr, nullptr);
92 
93  const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());
94  if (!ME)
95  return std::make_pair(nullptr, nullptr);
96 
97  const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
98  if (!Field)
99  return std::make_pair(nullptr, nullptr);
100 
101  if (!isa<CXXThisExpr>(ME->getBase()))
102  return std::make_pair(nullptr, nullptr);
103  const Expr *Init = BO->getRHS()->IgnoreParenImpCasts();
104  if (isSafeAssignment(Field, Init, Ctor))
105  return std::make_pair(Field, Init);
106  } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
107  if (COCE->getOperator() != OO_Equal)
108  return std::make_pair(nullptr, nullptr);
109 
110  const auto *ME =
111  dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
112  if (!ME)
113  return std::make_pair(nullptr, nullptr);
114 
115  const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
116  if (!Field)
117  return std::make_pair(nullptr, nullptr);
118 
119  if (!isa<CXXThisExpr>(ME->getBase()))
120  return std::make_pair(nullptr, nullptr);
121  const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts();
122  if (isSafeAssignment(Field, Init, Ctor))
123  return std::make_pair(Field, Init);
124  }
125 
126  return std::make_pair(nullptr, nullptr);
127 }
128 
129 PreferMemberInitializerCheck::PreferMemberInitializerCheck(
130  StringRef Name, ClangTidyContext *Context)
131  : ClangTidyCheck(Name, Context),
132  IsUseDefaultMemberInitEnabled(
133  Context->isCheckEnabled("modernize-use-default-member-init")),
134  UseAssignment(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(
145  cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
146  .bind("ctor"),
147  this);
148 }
149 
151  const MatchFinder::MatchResult &Result) {
152  const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
153  const auto *Body = cast<CompoundStmt>(Ctor->getBody());
154 
155  const CXXRecordDecl *Class = Ctor->getParent();
156  bool FirstToCtorInits = true;
157 
158  for (const Stmt *S : Body->body()) {
159  if (S->getBeginLoc().isMacroID()) {
160  StringRef MacroName = Lexer::getImmediateMacroName(
161  S->getBeginLoc(), *Result.SourceManager, getLangOpts());
162  if (MacroName.contains_insensitive("assert"))
163  return;
164  }
165  if (isControlStatement(S))
166  return;
167 
169  return;
170 
171  if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
172  if (isNoReturnCallStatement(CondOp->getLHS()) ||
173  isNoReturnCallStatement(CondOp->getRHS()))
174  return;
175  }
176 
177  const FieldDecl *Field;
178  const Expr *InitValue;
179  std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor);
180  if (Field) {
181  if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
182  Ctor->isDefaultConstructor() &&
183  (getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
184  !Field->hasInClassInitializer() &&
185  (!isa<RecordDecl>(Class->getDeclContext()) ||
186  !cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
188 
189  bool InvalidFix = false;
190  SourceLocation FieldEnd =
191  Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
192  *Result.SourceManager, getLangOpts());
193  InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID();
194  SourceLocation SemiColonEnd;
195  if (auto NextToken = Lexer::findNextToken(
196  S->getEndLoc(), *Result.SourceManager, getLangOpts()))
197  SemiColonEnd = NextToken->getEndLoc();
198  else
199  InvalidFix = true;
200  auto Diag =
201  diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
202  " default member initializer")
203  << Field;
204  if (!InvalidFix) {
205  CharSourceRange StmtRange =
206  CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
207 
208  SmallString<128> Insertion(
209  {UseAssignment ? " = " : "{",
211  CharSourceRange(InitValue->getSourceRange(), true),
212  *Result.SourceManager, getLangOpts()),
213  UseAssignment ? "" : "}"});
214 
215  Diag << FixItHint::CreateInsertion(FieldEnd, Insertion)
216  << FixItHint::CreateRemoval(StmtRange);
217  }
218  } else {
219  StringRef InsertPrefix = "";
220  SourceLocation InsertPos;
221  bool AddComma = false;
222  bool InvalidFix = false;
223  unsigned Index = Field->getFieldIndex();
224  const CXXCtorInitializer *LastInListInit = nullptr;
225  for (const CXXCtorInitializer *Init : Ctor->inits()) {
226  if (!Init->isWritten())
227  continue;
228  if (Init->isMemberInitializer() &&
229  Index < Init->getMember()->getFieldIndex()) {
230  InsertPos = Init->getSourceLocation();
231  // There are initializers after the one we are inserting, so add a
232  // comma after this insertion in order to not break anything.
233  AddComma = true;
234  break;
235  }
236  LastInListInit = Init;
237  }
238  if (InsertPos.isInvalid()) {
239  if (LastInListInit) {
240  InsertPos = Lexer::getLocForEndOfToken(
241  LastInListInit->getRParenLoc(), 0, *Result.SourceManager,
242  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 subsequent
256  // pass then we have already inserted a `:` so continue with a
257  // comma.
258  InsertPrefix = FirstToCtorInits ? " : " : ", ";
259  }
260  }
261  InvalidFix |= InsertPos.isMacroID();
262 
263  SourceLocation SemiColonEnd;
264  if (auto NextToken = Lexer::findNextToken(
265  S->getEndLoc(), *Result.SourceManager, getLangOpts()))
266  SemiColonEnd = NextToken->getEndLoc();
267  else
268  InvalidFix = true;
269 
270  auto Diag =
271  diag(S->getBeginLoc(), "%0 should be initialized in a member"
272  " initializer of the constructor")
273  << Field;
274  if (!InvalidFix) {
275 
276  CharSourceRange StmtRange =
277  CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
278  SmallString<128> Insertion(
279  {InsertPrefix, Field->getName(), "(",
281  CharSourceRange(InitValue->getSourceRange(), true),
282  *Result.SourceManager, getLangOpts()),
283  AddComma ? "), " : ")"});
284  Diag << FixItHint::CreateInsertion(InsertPos, Insertion,
285  FirstToCtorInits)
286  << FixItHint::CreateRemoval(StmtRange);
287  FirstToCtorInits = false;
288  }
289  }
290  }
291  }
292 }
293 
294 } // namespace cppcoreguidelines
295 } // namespace tidy
296 } // namespace clang
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::tidy::cppcoreguidelines::PreferMemberInitializerCheck::UseAssignment
const bool UseAssignment
Definition: PreferMemberInitializerCheck.h:34
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::tidy::cppcoreguidelines::PreferMemberInitializerCheck::IsUseDefaultMemberInitEnabled
const bool IsUseDefaultMemberInitEnabled
Definition: PreferMemberInitializerCheck.h:33
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:109
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::tidy::cppcoreguidelines::isLiteral
static bool isLiteral(const Expr *E)
Definition: PreferMemberInitializerCheck.cpp:37
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:420
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::cppcoreguidelines::shouldBeDefaultMemberInitializer
static bool shouldBeDefaultMemberInitializer(const Expr *Value)
Definition: PreferMemberInitializerCheck.cpp:48
clang::clangd::match
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:94
clang::tidy::cppcoreguidelines::isAssignmentToMemberOf
static std::pair< const FieldDecl *, const Expr * > isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, const CXXConstructorDecl *Ctor)
Definition: PreferMemberInitializerCheck.cpp:87
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:416
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:76
clang::tidy::cppcoreguidelines::PreferMemberInitializerCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: PreferMemberInitializerCheck.cpp:150
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::cppcoreguidelines::PreferMemberInitializerCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: PreferMemberInitializerCheck.cpp:143
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
Index
const SymbolIndex * Index
Definition: Dexp.cpp:99
PreferMemberInitializerCheck.h
clang::tidy::cppcoreguidelines::isControlStatement
static bool isControlStatement(const Stmt *S)
Definition: PreferMemberInitializerCheck.cpp:20
clang::tidy::cppcoreguidelines::isSafeAssignment
static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Context)
Definition: PreferMemberInitializerCheck.cpp:69
clang::tidy::cppcoreguidelines::isNoReturnCallStatement
static bool isNoReturnCallStatement(const Stmt *S)
Definition: PreferMemberInitializerCheck.cpp:25
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::ClangTidyCheck::OptionsView::store
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.
Definition: ClangTidyCheck.cpp:120
clang::tidy::cppcoreguidelines::isUnaryExprOfLiteral
static bool isUnaryExprOfLiteral(const Expr *E)
Definition: PreferMemberInitializerCheck.cpp:42
clang::tidy::cppcoreguidelines::PreferMemberInitializerCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: PreferMemberInitializerCheck.cpp:138
clang::tidy::bugprone::AST_MATCHER_P
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N)
Matches functions that have at least the specified amount of parameters.
Definition: EasilySwappableParametersCheck.cpp:1877
clang::tidy::ClangTidyCheck::OptionsView
Provides access to the ClangTidyCheck options via check-local names.
Definition: ClangTidyCheck.h:140