clang-tools  8.0.0
ProTypeMemberInitCheck.cpp
Go to the documentation of this file.
1 //===--- ProTypeMemberInitCheck.cpp - clang-tidy---------------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "ProTypeMemberInitCheck.h"
11 #include "../utils/LexerUtils.h"
12 #include "../utils/Matchers.h"
13 #include "../utils/TypeTraits.h"
14 #include "clang/AST/ASTContext.h"
15 #include "clang/ASTMatchers/ASTMatchFinder.h"
16 #include "clang/Lex/Lexer.h"
17 #include "llvm/ADT/SmallPtrSet.h"
18 
19 using namespace clang::ast_matchers;
20 using namespace clang::tidy::matchers;
21 using llvm::SmallPtrSet;
22 using llvm::SmallPtrSetImpl;
23 
24 namespace clang {
25 namespace tidy {
26 namespace cppcoreguidelines {
27 
28 namespace {
29 
30 AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) {
31  return Node.hasDefaultConstructor();
32 }
33 
34 // Iterate over all the fields in a record type, both direct and indirect (e.g.
35 // if the record contains an anonmyous struct).
36 template <typename T, typename Func>
37 void forEachField(const RecordDecl &Record, const T &Fields, Func &&Fn) {
38  for (const FieldDecl *F : Fields) {
39  if (F->isAnonymousStructOrUnion()) {
40  if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl())
41  forEachField(*R, R->fields(), Fn);
42  } else {
43  Fn(F);
44  }
45  }
46 }
47 
48 void removeFieldsInitializedInBody(
49  const Stmt &Stmt, ASTContext &Context,
50  SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
51  auto Matches =
52  match(findAll(binaryOperator(
53  hasOperatorName("="),
54  hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))),
55  Stmt, Context);
56  for (const auto &Match : Matches)
57  FieldDecls.erase(Match.getNodeAs<FieldDecl>("fieldDecl"));
58 }
59 
60 StringRef getName(const FieldDecl *Field) { return Field->getName(); }
61 
62 StringRef getName(const RecordDecl *Record) {
63  // Get the typedef name if this is a C-style anonymous struct and typedef.
64  if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
65  return Typedef->getName();
66  return Record->getName();
67 }
68 
69 // Creates comma separated list of decls requiring initialization in order of
70 // declaration.
71 template <typename R, typename T>
72 std::string
73 toCommaSeparatedString(const R &OrderedDecls,
74  const SmallPtrSetImpl<const T *> &DeclsToInit) {
75  SmallVector<StringRef, 16> Names;
76  for (const T *Decl : OrderedDecls) {
77  if (DeclsToInit.count(Decl))
78  Names.emplace_back(getName(Decl));
79  }
80  return llvm::join(Names.begin(), Names.end(), ", ");
81 }
82 
83 SourceLocation getLocationForEndOfToken(const ASTContext &Context,
84  SourceLocation Location) {
85  return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
86  Context.getLangOpts());
87 }
88 
89 // There are 3 kinds of insertion placements:
91  // 1. The fields are inserted after an existing CXXCtorInitializer stored in
92  // Where. This will be the case whenever there is a written initializer before
93  // the fields available.
94  After,
95 
96  // 2. The fields are inserted before the first existing initializer stored in
97  // Where.
98  Before,
99 
100  // 3. There are no written initializers and the fields will be inserted before
101  // the constructor's body creating a new initializer list including the ':'.
102  New
103 };
104 
105 // An InitializerInsertion contains a list of fields and/or base classes to
106 // insert into the initializer list of a constructor. We use this to ensure
107 // proper absolute ordering according to the class declaration relative to the
108 // (perhaps improper) ordering in the existing initializer list, if any.
109 struct IntializerInsertion {
110  IntializerInsertion(InitializerPlacement Placement,
111  const CXXCtorInitializer *Where)
112  : Placement(Placement), Where(Where) {}
113 
114  SourceLocation getLocation(const ASTContext &Context,
115  const CXXConstructorDecl &Constructor) const {
116  assert((Where != nullptr || Placement == InitializerPlacement::New) &&
117  "Location should be relative to an existing initializer or this "
118  "insertion represents a new initializer list.");
119  SourceLocation Location;
120  switch (Placement) {
121  case InitializerPlacement::New:
123  Constructor.getBody()->getBeginLoc(),
124  Context.getSourceManager(), Context.getLangOpts())
125  .getLocation();
126  break;
127  case InitializerPlacement::Before:
129  Where->getSourceRange().getBegin(),
130  Context.getSourceManager(), Context.getLangOpts())
131  .getLocation();
132  break;
133  case InitializerPlacement::After:
134  Location = Where->getRParenLoc();
135  break;
136  }
137  return getLocationForEndOfToken(Context, Location);
138  }
139 
140  std::string codeToInsert() const {
141  assert(!Initializers.empty() && "No initializers to insert");
142  std::string Code;
143  llvm::raw_string_ostream Stream(Code);
144  std::string joined =
145  llvm::join(Initializers.begin(), Initializers.end(), "(), ");
146  switch (Placement) {
147  case InitializerPlacement::New:
148  Stream << " : " << joined << "()";
149  break;
150  case InitializerPlacement::Before:
151  Stream << " " << joined << "(),";
152  break;
153  case InitializerPlacement::After:
154  Stream << ", " << joined << "()";
155  break;
156  }
157  return Stream.str();
158  }
159 
160  InitializerPlacement Placement;
161  const CXXCtorInitializer *Where;
162  SmallVector<std::string, 4> Initializers;
163 };
164 
165 // Convenience utility to get a RecordDecl from a QualType.
166 const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
167  if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
168  return RT->getDecl();
169  return nullptr;
170 }
171 
172 template <typename R, typename T>
173 SmallVector<IntializerInsertion, 16>
174 computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
175  const R &OrderedDecls,
176  const SmallPtrSetImpl<const T *> &DeclsToInit) {
177  SmallVector<IntializerInsertion, 16> Insertions;
178  Insertions.emplace_back(InitializerPlacement::New, nullptr);
179 
180  typename R::const_iterator Decl = std::begin(OrderedDecls);
181  for (const CXXCtorInitializer *Init : Inits) {
182  if (Init->isWritten()) {
183  if (Insertions.size() == 1)
184  Insertions.emplace_back(InitializerPlacement::Before, Init);
185 
186  // Gets either the field or base class being initialized by the provided
187  // initializer.
188  const auto *InitDecl =
189  Init->isAnyMemberInitializer()
190  ? static_cast<const NamedDecl *>(Init->getAnyMember())
191  : Init->getBaseClass()->getAsCXXRecordDecl();
192 
193  // Add all fields between current field up until the next intializer.
194  for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
195  if (const auto *D = dyn_cast<T>(*Decl)) {
196  if (DeclsToInit.count(D) > 0)
197  Insertions.back().Initializers.emplace_back(getName(D));
198  }
199  }
200 
201  Insertions.emplace_back(InitializerPlacement::After, Init);
202  }
203  }
204 
205  // Add remaining decls that require initialization.
206  for (; Decl != std::end(OrderedDecls); ++Decl) {
207  if (const auto *D = dyn_cast<T>(*Decl)) {
208  if (DeclsToInit.count(D) > 0)
209  Insertions.back().Initializers.emplace_back(getName(D));
210  }
211  }
212  return Insertions;
213 }
214 
215 // Gets the list of bases and members that could possibly be initialized, in
216 // order as they appear in the class declaration.
217 void getInitializationsInOrder(const CXXRecordDecl &ClassDecl,
218  SmallVectorImpl<const NamedDecl *> &Decls) {
219  Decls.clear();
220  for (const auto &Base : ClassDecl.bases()) {
221  // Decl may be null if the base class is a template parameter.
222  if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
223  Decls.emplace_back(Decl);
224  }
225  }
226  forEachField(ClassDecl, ClassDecl.fields(),
227  [&](const FieldDecl *F) { Decls.push_back(F); });
228 }
229 
230 template <typename T>
231 void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
232  const CXXConstructorDecl *Ctor,
233  const SmallPtrSetImpl<const T *> &DeclsToInit) {
234  // Do not propose fixes in macros since we cannot place them correctly.
235  if (Ctor->getBeginLoc().isMacroID())
236  return;
237 
238  SmallVector<const NamedDecl *, 16> OrderedDecls;
239  getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
240 
241  for (const auto &Insertion :
242  computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
243  if (!Insertion.Initializers.empty())
244  Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
245  Insertion.codeToInsert());
246  }
247 }
248 
249 } // anonymous namespace
250 
251 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
252  ClangTidyContext *Context)
253  : ClangTidyCheck(Name, Context),
254  IgnoreArrays(Options.get("IgnoreArrays", false)) {}
255 
256 void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
257  if (!getLangOpts().CPlusPlus)
258  return;
259 
260  auto IsUserProvidedNonDelegatingConstructor =
261  allOf(isUserProvided(),
262  unless(anyOf(isInstantiated(), isDelegatingConstructor())));
263  auto IsNonTrivialDefaultConstructor = allOf(
264  isDefaultConstructor(), unless(isUserProvided()),
265  hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
266  Finder->addMatcher(
267  cxxConstructorDecl(isDefinition(),
268  anyOf(IsUserProvidedNonDelegatingConstructor,
269  IsNonTrivialDefaultConstructor))
270  .bind("ctor"),
271  this);
272 
273  // Match classes with a default constructor that is defaulted or is not in the
274  // AST.
275  Finder->addMatcher(
276  cxxRecordDecl(
277  isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
278  anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
279  unless(isImplicit()))),
280  unless(has(cxxConstructorDecl()))),
282  .bind("record"),
283  this);
284 
285  auto HasDefaultConstructor = hasInitializer(
286  cxxConstructExpr(unless(requiresZeroInitialization()),
287  hasDeclaration(cxxConstructorDecl(
288  isDefaultConstructor(), unless(isUserProvided())))));
289  Finder->addMatcher(
290  varDecl(isDefinition(), HasDefaultConstructor,
291  hasAutomaticStorageDuration(),
292  hasType(recordDecl(has(fieldDecl()),
294  .bind("var"),
295  this);
296 }
297 
298 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
299  if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
300  // Skip declarations delayed by late template parsing without a body.
301  if (!Ctor->getBody())
302  return;
303  checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
304  checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
305  } else if (const auto *Record =
306  Result.Nodes.getNodeAs<CXXRecordDecl>("record")) {
307  assert(Record->hasDefaultConstructor() &&
308  "Matched record should have a default constructor");
309  checkMissingMemberInitializer(*Result.Context, *Record, nullptr);
310  checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr);
311  } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
312  checkUninitializedTrivialType(*Result.Context, Var);
313  }
314 }
315 
317  Options.store(Opts, "IgnoreArrays", IgnoreArrays);
318 }
319 
320 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
321 static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) {
322  if (T->isIncompleteArrayType())
323  return true;
324 
325  while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
326  if (!ArrayT->getSize())
327  return true;
328 
329  T = ArrayT->getElementType();
330  }
331 
332  return false;
333 }
334 
335 static bool isEmpty(ASTContext &Context, const QualType &Type) {
336  if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
337  return ClassDecl->isEmpty();
338  }
339  return isIncompleteOrZeroLengthArrayType(Context, Type);
340 }
341 
342 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
343  ASTContext &Context, const CXXRecordDecl &ClassDecl,
344  const CXXConstructorDecl *Ctor) {
345  bool IsUnion = ClassDecl.isUnion();
346 
347  if (IsUnion && ClassDecl.hasInClassInitializer())
348  return;
349 
350  // Gather all fields (direct and indirect) that need to be initialized.
351  SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
352  forEachField(ClassDecl, ClassDecl.fields(), [&](const FieldDecl *F) {
353  if (!F->hasInClassInitializer() &&
355  Context) &&
356  !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
357  FieldsToInit.insert(F);
358  });
359  if (FieldsToInit.empty())
360  return;
361 
362  if (Ctor) {
363  for (const CXXCtorInitializer *Init : Ctor->inits()) {
364  // Remove any fields that were explicitly written in the initializer list
365  // or in-class.
366  if (Init->isAnyMemberInitializer() && Init->isWritten()) {
367  if (IsUnion)
368  return; // We can only initialize one member of a union.
369  FieldsToInit.erase(Init->getAnyMember());
370  }
371  }
372  removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
373  }
374 
375  // Collect all fields in order, both direct fields and indirect fields from
376  // anonmyous record types.
377  SmallVector<const FieldDecl *, 16> OrderedFields;
378  forEachField(ClassDecl, ClassDecl.fields(),
379  [&](const FieldDecl *F) { OrderedFields.push_back(F); });
380 
381  // Collect all the fields we need to initialize, including indirect fields.
382  SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
383  forEachField(ClassDecl, FieldsToInit,
384  [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
385  if (AllFieldsToInit.empty())
386  return;
387 
388  DiagnosticBuilder Diag =
389  diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
390  IsUnion
391  ? "union constructor should initialize one of these fields: %0"
392  : "constructor does not initialize these fields: %0")
393  << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
394 
395  // Do not propose fixes for constructors in macros since we cannot place them
396  // correctly.
397  if (Ctor && Ctor->getBeginLoc().isMacroID())
398  return;
399 
400  // Collect all fields but only suggest a fix for the first member of unions,
401  // as initializing more than one union member is an error.
402  SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
403  SmallPtrSet<const RecordDecl *, 4> UnionsSeen;
404  forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) {
405  if (!FieldsToInit.count(F))
406  return;
407  // Don't suggest fixes for enums because we don't know a good default.
408  // Don't suggest fixes for bitfields because in-class initialization is not
409  // possible until C++2a.
410  if (F->getType()->isEnumeralType() ||
411  (!getLangOpts().CPlusPlus2a && F->isBitField()))
412  return;
413  if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second)
414  FieldsToFix.insert(F);
415  });
416  if (FieldsToFix.empty())
417  return;
418 
419  // Use in-class initialization if possible.
420  if (Context.getLangOpts().CPlusPlus11) {
421  for (const FieldDecl *Field : FieldsToFix) {
422  Diag << FixItHint::CreateInsertion(
423  getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
424  "{}");
425  }
426  } else if (Ctor) {
427  // Otherwise, rewrite the constructor's initializer list.
428  fixInitializerList(Context, Diag, Ctor, FieldsToFix);
429  }
430 }
431 
432 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
433  const ASTContext &Context, const CXXRecordDecl &ClassDecl,
434  const CXXConstructorDecl *Ctor) {
435 
436  // Gather any base classes that need to be initialized.
437  SmallVector<const RecordDecl *, 4> AllBases;
438  SmallPtrSet<const RecordDecl *, 4> BasesToInit;
439  for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
440  if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
441  AllBases.emplace_back(BaseClassDecl);
442  if (!BaseClassDecl->field_empty() &&
444  Context))
445  BasesToInit.insert(BaseClassDecl);
446  }
447  }
448 
449  if (BasesToInit.empty())
450  return;
451 
452  // Remove any bases that were explicitly written in the initializer list.
453  if (Ctor) {
454  if (Ctor->isImplicit())
455  return;
456 
457  for (const CXXCtorInitializer *Init : Ctor->inits()) {
458  if (Init->isBaseInitializer() && Init->isWritten())
459  BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
460  }
461  }
462 
463  if (BasesToInit.empty())
464  return;
465 
466  DiagnosticBuilder Diag =
467  diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
468  "constructor does not initialize these bases: %0")
469  << toCommaSeparatedString(AllBases, BasesToInit);
470 
471  if (Ctor)
472  fixInitializerList(Context, Diag, Ctor, BasesToInit);
473 }
474 
475 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
476  const ASTContext &Context, const VarDecl *Var) {
477  DiagnosticBuilder Diag =
478  diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
479 
480  Diag << FixItHint::CreateInsertion(
481  getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
482  Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
483 }
484 
485 } // namespace cppcoreguidelines
486 } // namespace tidy
487 } // namespace clang
AST_MATCHER(BinaryOperator, isAssignmentOperator)
Definition: Matchers.h:20
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: ClangTidy.cpp:473
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T)
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
Definition: LexerUtils.cpp:17
llvm::SmallVector< uint64_t, 1024 > Record
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static bool isEmpty(ASTContext &Context, const QualType &Type)
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
SmallVector< std::string, 4 > Initializers
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context)
Returns true if Type is trivially default constructible.
Definition: TypeTraits.cpp:89
const CXXCtorInitializer * Where
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
const Decl * D
Definition: XRefs.cpp:79
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
InitializerPlacement Placement
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:438