clang-tools  7.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  Context, Constructor.getBody()->getLocStart())
124  .getLocation();
125  break;
126  case InitializerPlacement::Before:
128  Context, Where->getSourceRange().getBegin())
129  .getLocation();
130  break;
131  case InitializerPlacement::After:
132  Location = Where->getRParenLoc();
133  break;
134  }
135  return getLocationForEndOfToken(Context, Location);
136  }
137 
138  std::string codeToInsert() const {
139  assert(!Initializers.empty() && "No initializers to insert");
140  std::string Code;
141  llvm::raw_string_ostream Stream(Code);
142  std::string joined =
143  llvm::join(Initializers.begin(), Initializers.end(), "(), ");
144  switch (Placement) {
145  case InitializerPlacement::New:
146  Stream << " : " << joined << "()";
147  break;
148  case InitializerPlacement::Before:
149  Stream << " " << joined << "(),";
150  break;
151  case InitializerPlacement::After:
152  Stream << ", " << joined << "()";
153  break;
154  }
155  return Stream.str();
156  }
157 
158  InitializerPlacement Placement;
159  const CXXCtorInitializer *Where;
160  SmallVector<std::string, 4> Initializers;
161 };
162 
163 // Convenience utility to get a RecordDecl from a QualType.
164 const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
165  if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
166  return RT->getDecl();
167  return nullptr;
168 }
169 
170 template <typename R, typename T>
171 SmallVector<IntializerInsertion, 16>
172 computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
173  const R &OrderedDecls,
174  const SmallPtrSetImpl<const T *> &DeclsToInit) {
175  SmallVector<IntializerInsertion, 16> Insertions;
176  Insertions.emplace_back(InitializerPlacement::New, nullptr);
177 
178  typename R::const_iterator Decl = std::begin(OrderedDecls);
179  for (const CXXCtorInitializer *Init : Inits) {
180  if (Init->isWritten()) {
181  if (Insertions.size() == 1)
182  Insertions.emplace_back(InitializerPlacement::Before, Init);
183 
184  // Gets either the field or base class being initialized by the provided
185  // initializer.
186  const auto *InitDecl =
187  Init->isAnyMemberInitializer()
188  ? static_cast<const NamedDecl *>(Init->getAnyMember())
189  : Init->getBaseClass()->getAsCXXRecordDecl();
190 
191  // Add all fields between current field up until the next intializer.
192  for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
193  if (const auto *D = dyn_cast<T>(*Decl)) {
194  if (DeclsToInit.count(D) > 0)
195  Insertions.back().Initializers.emplace_back(getName(D));
196  }
197  }
198 
199  Insertions.emplace_back(InitializerPlacement::After, Init);
200  }
201  }
202 
203  // Add remaining decls that require initialization.
204  for (; Decl != std::end(OrderedDecls); ++Decl) {
205  if (const auto *D = dyn_cast<T>(*Decl)) {
206  if (DeclsToInit.count(D) > 0)
207  Insertions.back().Initializers.emplace_back(getName(D));
208  }
209  }
210  return Insertions;
211 }
212 
213 // Gets the list of bases and members that could possibly be initialized, in
214 // order as they appear in the class declaration.
215 void getInitializationsInOrder(const CXXRecordDecl &ClassDecl,
216  SmallVectorImpl<const NamedDecl *> &Decls) {
217  Decls.clear();
218  for (const auto &Base : ClassDecl.bases()) {
219  // Decl may be null if the base class is a template parameter.
220  if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
221  Decls.emplace_back(Decl);
222  }
223  }
224  forEachField(ClassDecl, ClassDecl.fields(),
225  [&](const FieldDecl *F) { Decls.push_back(F); });
226 }
227 
228 template <typename T>
229 void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
230  const CXXConstructorDecl *Ctor,
231  const SmallPtrSetImpl<const T *> &DeclsToInit) {
232  // Do not propose fixes in macros since we cannot place them correctly.
233  if (Ctor->getLocStart().isMacroID())
234  return;
235 
236  SmallVector<const NamedDecl *, 16> OrderedDecls;
237  getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
238 
239  for (const auto &Insertion :
240  computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
241  if (!Insertion.Initializers.empty())
242  Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
243  Insertion.codeToInsert());
244  }
245 }
246 
247 } // anonymous namespace
248 
249 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
250  ClangTidyContext *Context)
251  : ClangTidyCheck(Name, Context),
252  IgnoreArrays(Options.get("IgnoreArrays", false)) {}
253 
254 void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
255  if (!getLangOpts().CPlusPlus)
256  return;
257 
258  auto IsUserProvidedNonDelegatingConstructor =
259  allOf(isUserProvided(),
260  unless(anyOf(isInstantiated(), isDelegatingConstructor())));
261  auto IsNonTrivialDefaultConstructor = allOf(
262  isDefaultConstructor(), unless(isUserProvided()),
263  hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
264  Finder->addMatcher(
265  cxxConstructorDecl(isDefinition(),
266  anyOf(IsUserProvidedNonDelegatingConstructor,
267  IsNonTrivialDefaultConstructor))
268  .bind("ctor"),
269  this);
270 
271  // Match classes with a default constructor that is defaulted or is not in the
272  // AST.
273  Finder->addMatcher(
274  cxxRecordDecl(
275  isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
276  anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
277  unless(isImplicit()))),
278  unless(has(cxxConstructorDecl()))),
280  .bind("record"),
281  this);
282 
283  auto HasDefaultConstructor = hasInitializer(
284  cxxConstructExpr(unless(requiresZeroInitialization()),
285  hasDeclaration(cxxConstructorDecl(
286  isDefaultConstructor(), unless(isUserProvided())))));
287  Finder->addMatcher(
288  varDecl(isDefinition(), HasDefaultConstructor,
289  hasAutomaticStorageDuration(),
290  hasType(recordDecl(has(fieldDecl()),
292  .bind("var"),
293  this);
294 }
295 
296 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
297  if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
298  // Skip declarations delayed by late template parsing without a body.
299  if (!Ctor->getBody())
300  return;
301  checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
302  checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
303  } else if (const auto *Record =
304  Result.Nodes.getNodeAs<CXXRecordDecl>("record")) {
305  assert(Record->hasDefaultConstructor() &&
306  "Matched record should have a default constructor");
307  checkMissingMemberInitializer(*Result.Context, *Record, nullptr);
308  checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr);
309  } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
310  checkUninitializedTrivialType(*Result.Context, Var);
311  }
312 }
313 
315  Options.store(Opts, "IgnoreArrays", IgnoreArrays);
316 }
317 
318 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
319 static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) {
320  if (T->isIncompleteArrayType())
321  return true;
322 
323  while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
324  if (!ArrayT->getSize())
325  return true;
326 
327  T = ArrayT->getElementType();
328  }
329 
330  return false;
331 }
332 
333 static bool isEmpty(ASTContext &Context, const QualType &Type) {
334  if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
335  return ClassDecl->isEmpty();
336  }
337  return isIncompleteOrZeroLengthArrayType(Context, Type);
338 }
339 
340 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
341  ASTContext &Context, const CXXRecordDecl &ClassDecl,
342  const CXXConstructorDecl *Ctor) {
343  bool IsUnion = ClassDecl.isUnion();
344 
345  if (IsUnion && ClassDecl.hasInClassInitializer())
346  return;
347 
348  // Gather all fields (direct and indirect) that need to be initialized.
349  SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
350  forEachField(ClassDecl, ClassDecl.fields(), [&](const FieldDecl *F) {
351  if (!F->hasInClassInitializer() &&
353  Context) &&
354  !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
355  FieldsToInit.insert(F);
356  });
357  if (FieldsToInit.empty())
358  return;
359 
360  if (Ctor) {
361  for (const CXXCtorInitializer *Init : Ctor->inits()) {
362  // Remove any fields that were explicitly written in the initializer list
363  // or in-class.
364  if (Init->isAnyMemberInitializer() && Init->isWritten()) {
365  if (IsUnion)
366  return; // We can only initialize one member of a union.
367  FieldsToInit.erase(Init->getAnyMember());
368  }
369  }
370  removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
371  }
372 
373  // Collect all fields in order, both direct fields and indirect fields from
374  // anonmyous record types.
375  SmallVector<const FieldDecl *, 16> OrderedFields;
376  forEachField(ClassDecl, ClassDecl.fields(),
377  [&](const FieldDecl *F) { OrderedFields.push_back(F); });
378 
379  // Collect all the fields we need to initialize, including indirect fields.
380  SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
381  forEachField(ClassDecl, FieldsToInit,
382  [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
383  if (AllFieldsToInit.empty())
384  return;
385 
386  DiagnosticBuilder Diag =
387  diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(),
388  IsUnion
389  ? "union constructor should initialize one of these fields: %0"
390  : "constructor does not initialize these fields: %0")
391  << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
392 
393  // Do not propose fixes for constructors in macros since we cannot place them
394  // correctly.
395  if (Ctor && Ctor->getLocStart().isMacroID())
396  return;
397 
398  // Collect all fields but only suggest a fix for the first member of unions,
399  // as initializing more than one union member is an error.
400  SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
401  SmallPtrSet<const RecordDecl *, 4> UnionsSeen;
402  forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) {
403  if (!FieldsToInit.count(F))
404  return;
405  // Don't suggest fixes for enums because we don't know a good default.
406  // Don't suggest fixes for bitfields because in-class initialization is not
407  // possible until C++2a.
408  if (F->getType()->isEnumeralType() ||
409  (!getLangOpts().CPlusPlus2a && F->isBitField()))
410  return;
411  if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second)
412  FieldsToFix.insert(F);
413  });
414  if (FieldsToFix.empty())
415  return;
416 
417  // Use in-class initialization if possible.
418  if (Context.getLangOpts().CPlusPlus11) {
419  for (const FieldDecl *Field : FieldsToFix) {
420  Diag << FixItHint::CreateInsertion(
421  getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
422  "{}");
423  }
424  } else if (Ctor) {
425  // Otherwise, rewrite the constructor's initializer list.
426  fixInitializerList(Context, Diag, Ctor, FieldsToFix);
427  }
428 }
429 
430 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
431  const ASTContext &Context, const CXXRecordDecl &ClassDecl,
432  const CXXConstructorDecl *Ctor) {
433 
434  // Gather any base classes that need to be initialized.
435  SmallVector<const RecordDecl *, 4> AllBases;
436  SmallPtrSet<const RecordDecl *, 4> BasesToInit;
437  for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
438  if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
439  AllBases.emplace_back(BaseClassDecl);
440  if (!BaseClassDecl->field_empty() &&
442  Context))
443  BasesToInit.insert(BaseClassDecl);
444  }
445  }
446 
447  if (BasesToInit.empty())
448  return;
449 
450  // Remove any bases that were explicitly written in the initializer list.
451  if (Ctor) {
452  if (Ctor->isImplicit())
453  return;
454 
455  for (const CXXCtorInitializer *Init : Ctor->inits()) {
456  if (Init->isBaseInitializer() && Init->isWritten())
457  BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
458  }
459  }
460 
461  if (BasesToInit.empty())
462  return;
463 
464  DiagnosticBuilder Diag =
465  diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(),
466  "constructor does not initialize these bases: %0")
467  << toCommaSeparatedString(AllBases, BasesToInit);
468 
469  if (Ctor)
470  fixInitializerList(Context, Diag, Ctor, BasesToInit);
471 }
472 
473 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
474  const ASTContext &Context, const VarDecl *Var) {
475  DiagnosticBuilder Diag =
476  diag(Var->getLocStart(), "uninitialized record type: %0") << Var;
477 
478  Diag << FixItHint::CreateInsertion(
479  getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
480  Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
481 }
482 
483 } // namespace cppcoreguidelines
484 } // namespace tidy
485 } // namespace clang
llvm::StringRef Name
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:460
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T)
Token getPreviousToken(const ASTContext &Context, SourceLocation Location, 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
std::map< std::string, std::string > OptionMap
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:427