clang-tools  10.0.0git
ProTypeMemberInitCheck.cpp
Go to the documentation of this file.
1 //===--- ProTypeMemberInitCheck.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 "../utils/LexerUtils.h"
11 #include "../utils/Matchers.h"
12 #include "../utils/TypeTraits.h"
13 #include "clang/AST/ASTContext.h"
14 #include "clang/ASTMatchers/ASTMatchFinder.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/ADT/SmallPtrSet.h"
17 
18 using namespace clang::ast_matchers;
19 using namespace clang::tidy::matchers;
20 using llvm::SmallPtrSet;
21 using llvm::SmallPtrSetImpl;
22 
23 namespace clang {
24 namespace tidy {
25 namespace cppcoreguidelines {
26 
27 namespace {
28 
29 AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) {
30  return Node.hasDefaultConstructor();
31 }
32 
33 // Iterate over all the fields in a record type, both direct and indirect (e.g.
34 // if the record contains an anonymous struct).
35 template <typename T, typename Func>
36 void forEachField(const RecordDecl &Record, const T &Fields, Func &&Fn) {
37  for (const FieldDecl *F : Fields) {
38  if (F->isAnonymousStructOrUnion()) {
39  if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl())
40  forEachField(*R, R->fields(), Fn);
41  } else {
42  Fn(F);
43  }
44  }
45 }
46 
47 void removeFieldsInitializedInBody(
48  const Stmt &Stmt, ASTContext &Context,
49  SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
50  auto Matches =
51  match(findAll(binaryOperator(
52  hasOperatorName("="),
53  hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))),
54  Stmt, Context);
55  for (const auto &Match : Matches)
56  FieldDecls.erase(Match.getNodeAs<FieldDecl>("fieldDecl"));
57 }
58 
59 StringRef getName(const FieldDecl *Field) { return Field->getName(); }
60 
61 StringRef getName(const RecordDecl *Record) {
62  // Get the typedef name if this is a C-style anonymous struct and typedef.
63  if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
64  return Typedef->getName();
65  return Record->getName();
66 }
67 
68 // Creates comma separated list of decls requiring initialization in order of
69 // declaration.
70 template <typename R, typename T>
71 std::string
72 toCommaSeparatedString(const R &OrderedDecls,
73  const SmallPtrSetImpl<const T *> &DeclsToInit) {
74  SmallVector<StringRef, 16> Names;
75  for (const T *Decl : OrderedDecls) {
76  if (DeclsToInit.count(Decl))
77  Names.emplace_back(getName(Decl));
78  }
79  return llvm::join(Names.begin(), Names.end(), ", ");
80 }
81 
82 SourceLocation getLocationForEndOfToken(const ASTContext &Context,
83  SourceLocation Location) {
84  return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
85  Context.getLangOpts());
86 }
87 
88 // There are 3 kinds of insertion placements:
90  // 1. The fields are inserted after an existing CXXCtorInitializer stored in
91  // Where. This will be the case whenever there is a written initializer before
92  // the fields available.
93  After,
94 
95  // 2. The fields are inserted before the first existing initializer stored in
96  // Where.
97  Before,
98 
99  // 3. There are no written initializers and the fields will be inserted before
100  // the constructor's body creating a new initializer list including the ':'.
101  New
102 };
103 
104 // An InitializerInsertion contains a list of fields and/or base classes to
105 // insert into the initializer list of a constructor. We use this to ensure
106 // proper absolute ordering according to the class declaration relative to the
107 // (perhaps improper) ordering in the existing initializer list, if any.
108 struct IntializerInsertion {
109  IntializerInsertion(InitializerPlacement Placement,
110  const CXXCtorInitializer *Where)
111  : Placement(Placement), Where(Where) {}
112 
113  SourceLocation getLocation(const ASTContext &Context,
114  const CXXConstructorDecl &Constructor) const {
115  assert((Where != nullptr || Placement == InitializerPlacement::New) &&
116  "Location should be relative to an existing initializer or this "
117  "insertion represents a new initializer list.");
118  SourceLocation Location;
119  switch (Placement) {
120  case InitializerPlacement::New:
122  Constructor.getBody()->getBeginLoc(),
123  Context.getSourceManager(), Context.getLangOpts())
124  .getLocation();
125  break;
126  case InitializerPlacement::Before:
128  Where->getSourceRange().getBegin(),
129  Context.getSourceManager(), Context.getLangOpts())
130  .getLocation();
131  break;
132  case InitializerPlacement::After:
133  Location = Where->getRParenLoc();
134  break;
135  }
136  return getLocationForEndOfToken(Context, Location);
137  }
138 
139  std::string codeToInsert() const {
140  assert(!Initializers.empty() && "No initializers to insert");
141  std::string Code;
142  llvm::raw_string_ostream Stream(Code);
143  std::string joined =
144  llvm::join(Initializers.begin(), Initializers.end(), "(), ");
145  switch (Placement) {
146  case InitializerPlacement::New:
147  Stream << " : " << joined << "()";
148  break;
149  case InitializerPlacement::Before:
150  Stream << " " << joined << "(),";
151  break;
152  case InitializerPlacement::After:
153  Stream << ", " << joined << "()";
154  break;
155  }
156  return Stream.str();
157  }
158 
159  InitializerPlacement Placement;
160  const CXXCtorInitializer *Where;
161  SmallVector<std::string, 4> Initializers;
162 };
163 
164 // Convenience utility to get a RecordDecl from a QualType.
165 const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
166  if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
167  return RT->getDecl();
168  return nullptr;
169 }
170 
171 template <typename R, typename T>
172 SmallVector<IntializerInsertion, 16>
173 computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
174  const R &OrderedDecls,
175  const SmallPtrSetImpl<const T *> &DeclsToInit) {
176  SmallVector<IntializerInsertion, 16> Insertions;
177  Insertions.emplace_back(InitializerPlacement::New, nullptr);
178 
179  typename R::const_iterator Decl = std::begin(OrderedDecls);
180  for (const CXXCtorInitializer *Init : Inits) {
181  if (Init->isWritten()) {
182  if (Insertions.size() == 1)
183  Insertions.emplace_back(InitializerPlacement::Before, Init);
184 
185  // Gets either the field or base class being initialized by the provided
186  // initializer.
187  const auto *InitDecl =
188  Init->isAnyMemberInitializer()
189  ? static_cast<const NamedDecl *>(Init->getAnyMember())
190  : Init->getBaseClass()->getAsCXXRecordDecl();
191 
192  // Add all fields between current field up until the next intializer.
193  for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
194  if (const auto *D = dyn_cast<T>(*Decl)) {
195  if (DeclsToInit.count(D) > 0)
196  Insertions.back().Initializers.emplace_back(getName(D));
197  }
198  }
199 
200  Insertions.emplace_back(InitializerPlacement::After, Init);
201  }
202  }
203 
204  // Add remaining decls that require initialization.
205  for (; Decl != std::end(OrderedDecls); ++Decl) {
206  if (const auto *D = dyn_cast<T>(*Decl)) {
207  if (DeclsToInit.count(D) > 0)
208  Insertions.back().Initializers.emplace_back(getName(D));
209  }
210  }
211  return Insertions;
212 }
213 
214 // Gets the list of bases and members that could possibly be initialized, in
215 // order as they appear in the class declaration.
216 void getInitializationsInOrder(const CXXRecordDecl &ClassDecl,
217  SmallVectorImpl<const NamedDecl *> &Decls) {
218  Decls.clear();
219  for (const auto &Base : ClassDecl.bases()) {
220  // Decl may be null if the base class is a template parameter.
221  if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
222  Decls.emplace_back(Decl);
223  }
224  }
225  forEachField(ClassDecl, ClassDecl.fields(),
226  [&](const FieldDecl *F) { Decls.push_back(F); });
227 }
228 
229 template <typename T>
230 void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
231  const CXXConstructorDecl *Ctor,
232  const SmallPtrSetImpl<const T *> &DeclsToInit) {
233  // Do not propose fixes in macros since we cannot place them correctly.
234  if (Ctor->getBeginLoc().isMacroID())
235  return;
236 
237  SmallVector<const NamedDecl *, 16> OrderedDecls;
238  getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
239 
240  for (const auto &Insertion :
241  computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
242  if (!Insertion.Initializers.empty())
243  Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
244  Insertion.codeToInsert());
245  }
246 }
247 
248 } // anonymous namespace
249 
250 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
251  ClangTidyContext *Context)
252  : ClangTidyCheck(Name, Context),
253  IgnoreArrays(Options.get("IgnoreArrays", false)),
254  UseAssignment(Options.getLocalOrGlobal("UseAssignment", 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  Options.store(Opts, "UseAssignment", UseAssignment);
319 }
320 
321 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
322 static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) {
323  if (T->isIncompleteArrayType())
324  return true;
325 
326  while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
327  if (!ArrayT->getSize())
328  return true;
329 
330  T = ArrayT->getElementType();
331  }
332 
333  return false;
334 }
335 
336 static bool isEmpty(ASTContext &Context, const QualType &Type) {
337  if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
338  return ClassDecl->isEmpty();
339  }
340  return isIncompleteOrZeroLengthArrayType(Context, Type);
341 }
342 
343 static const char *getInitializer(QualType QT, bool UseAssignment) {
344  const char *DefaultInitializer = "{}";
345  if (!UseAssignment)
346  return DefaultInitializer;
347 
348  if (QT->isPointerType())
349  return " = nullptr";
350 
351  const BuiltinType *BT =
352  dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
353  if (!BT)
354  return DefaultInitializer;
355 
356  switch (BT->getKind()) {
357  case BuiltinType::Bool:
358  return " = false";
359  case BuiltinType::Float:
360  return " = 0.0F";
361  case BuiltinType::Double:
362  return " = 0.0";
363  case BuiltinType::LongDouble:
364  return " = 0.0L";
365  case BuiltinType::SChar:
366  case BuiltinType::Char_S:
367  case BuiltinType::WChar_S:
368  case BuiltinType::Char16:
369  case BuiltinType::Char32:
370  case BuiltinType::Short:
371  case BuiltinType::Int:
372  return " = 0";
373  case BuiltinType::UChar:
374  case BuiltinType::Char_U:
375  case BuiltinType::WChar_U:
376  case BuiltinType::UShort:
377  case BuiltinType::UInt:
378  return " = 0U";
379  case BuiltinType::Long:
380  return " = 0L";
381  case BuiltinType::ULong:
382  return " = 0UL";
383  case BuiltinType::LongLong:
384  return " = 0LL";
385  case BuiltinType::ULongLong:
386  return " = 0ULL";
387 
388  default:
389  return DefaultInitializer;
390  }
391 }
392 
393 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
394  ASTContext &Context, const CXXRecordDecl &ClassDecl,
395  const CXXConstructorDecl *Ctor) {
396  bool IsUnion = ClassDecl.isUnion();
397 
398  if (IsUnion && ClassDecl.hasInClassInitializer())
399  return;
400 
401  // Gather all fields (direct and indirect) that need to be initialized.
402  SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
403  forEachField(ClassDecl, ClassDecl.fields(), [&](const FieldDecl *F) {
404  if (!F->hasInClassInitializer() &&
406  Context) &&
407  !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
408  FieldsToInit.insert(F);
409  });
410  if (FieldsToInit.empty())
411  return;
412 
413  if (Ctor) {
414  for (const CXXCtorInitializer *Init : Ctor->inits()) {
415  // Remove any fields that were explicitly written in the initializer list
416  // or in-class.
417  if (Init->isAnyMemberInitializer() && Init->isWritten()) {
418  if (IsUnion)
419  return; // We can only initialize one member of a union.
420  FieldsToInit.erase(Init->getAnyMember());
421  }
422  }
423  removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
424  }
425 
426  // Collect all fields in order, both direct fields and indirect fields from
427  // anonymous record types.
428  SmallVector<const FieldDecl *, 16> OrderedFields;
429  forEachField(ClassDecl, ClassDecl.fields(),
430  [&](const FieldDecl *F) { OrderedFields.push_back(F); });
431 
432  // Collect all the fields we need to initialize, including indirect fields.
433  SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
434  forEachField(ClassDecl, FieldsToInit,
435  [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
436  if (AllFieldsToInit.empty())
437  return;
438 
439  DiagnosticBuilder Diag =
440  diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
441  IsUnion
442  ? "union constructor should initialize one of these fields: %0"
443  : "constructor does not initialize these fields: %0")
444  << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
445 
446  // Do not propose fixes for constructors in macros since we cannot place them
447  // correctly.
448  if (Ctor && Ctor->getBeginLoc().isMacroID())
449  return;
450 
451  // Collect all fields but only suggest a fix for the first member of unions,
452  // as initializing more than one union member is an error.
453  SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
454  SmallPtrSet<const RecordDecl *, 4> UnionsSeen;
455  forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) {
456  if (!FieldsToInit.count(F))
457  return;
458  // Don't suggest fixes for enums because we don't know a good default.
459  // Don't suggest fixes for bitfields because in-class initialization is not
460  // possible until C++2a.
461  if (F->getType()->isEnumeralType() ||
462  (!getLangOpts().CPlusPlus2a && F->isBitField()))
463  return;
464  if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second)
465  FieldsToFix.insert(F);
466  });
467  if (FieldsToFix.empty())
468  return;
469 
470  // Use in-class initialization if possible.
471  if (Context.getLangOpts().CPlusPlus11) {
472  for (const FieldDecl *Field : FieldsToFix) {
473  Diag << FixItHint::CreateInsertion(
474  getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
475  getInitializer(Field->getType(), UseAssignment));
476  }
477  } else if (Ctor) {
478  // Otherwise, rewrite the constructor's initializer list.
479  fixInitializerList(Context, Diag, Ctor, FieldsToFix);
480  }
481 }
482 
483 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
484  const ASTContext &Context, const CXXRecordDecl &ClassDecl,
485  const CXXConstructorDecl *Ctor) {
486 
487  // Gather any base classes that need to be initialized.
488  SmallVector<const RecordDecl *, 4> AllBases;
489  SmallPtrSet<const RecordDecl *, 4> BasesToInit;
490  for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
491  if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
492  AllBases.emplace_back(BaseClassDecl);
493  if (!BaseClassDecl->field_empty() &&
495  Context))
496  BasesToInit.insert(BaseClassDecl);
497  }
498  }
499 
500  if (BasesToInit.empty())
501  return;
502 
503  // Remove any bases that were explicitly written in the initializer list.
504  if (Ctor) {
505  if (Ctor->isImplicit())
506  return;
507 
508  for (const CXXCtorInitializer *Init : Ctor->inits()) {
509  if (Init->isBaseInitializer() && Init->isWritten())
510  BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
511  }
512  }
513 
514  if (BasesToInit.empty())
515  return;
516 
517  DiagnosticBuilder Diag =
518  diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
519  "constructor does not initialize these bases: %0")
520  << toCommaSeparatedString(AllBases, BasesToInit);
521 
522  if (Ctor)
523  fixInitializerList(Context, Diag, Ctor, BasesToInit);
524 }
525 
526 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
527  const ASTContext &Context, const VarDecl *Var) {
528  DiagnosticBuilder Diag =
529  diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
530 
531  Diag << FixItHint::CreateInsertion(
532  getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
533  Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
534 }
535 
536 } // namespace cppcoreguidelines
537 } // namespace tidy
538 } // namespace clang
std::string Code
const FunctionDecl * Decl
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:16
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.
const LangOptions & getLangOpts() const
Returns the language options from the context.
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...
static const char * getInitializer(QualType QT, bool UseAssignment)
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:94
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.
bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context)
Returns true if Type is trivially default constructible.
Definition: TypeTraits.cpp:92
const CXXCtorInitializer * Where
static constexpr llvm::StringLiteral Name
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.
llvm::SmallDenseMap< const NamedDecl *, RelSet > Decls
Definition: FindTarget.cpp:170
InitializerPlacement Placement
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
std::unique_ptr< GlobalCompilationDatabase > Base
NodeType Type
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.