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