clang-tools  3.9.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 // Iterate over all the fields in a record type, both direct and indirect (e.g.
31 // if the record contains an anonmyous struct). If OneFieldPerUnion is true and
32 // the record type (or indirect field) is a union, forEachField will stop after
33 // the first field.
34 template <typename T, typename Func>
35 void forEachField(const RecordDecl *Record, const T &Fields,
36  bool OneFieldPerUnion, 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(), OneFieldPerUnion, Fn);
41  } else {
42  Fn(F);
43  }
44 
45  if (OneFieldPerUnion && Record->isUnion())
46  break;
47  }
48 }
49 
50 void removeFieldsInitializedInBody(
51  const Stmt &Stmt, ASTContext &Context,
52  SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
53  auto Matches =
54  match(findAll(binaryOperator(
55  hasOperatorName("="),
56  hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))),
57  Stmt, Context);
58  for (const auto &Match : Matches)
59  FieldDecls.erase(Match.getNodeAs<FieldDecl>("fieldDecl"));
60 }
61 
62 StringRef getName(const FieldDecl *Field) { return Field->getName(); }
63 
64 StringRef getName(const RecordDecl *Record) {
65  // Get the typedef name if this is a C-style anonymous struct and typedef.
66  if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
67  return Typedef->getName();
68  return Record->getName();
69 }
70 
71 // Creates comma separated list of decls requiring initialization in order of
72 // declaration.
73 template <typename R, typename T>
74 std::string
75 toCommaSeparatedString(const R &OrderedDecls,
76  const SmallPtrSetImpl<const T *> &DeclsToInit) {
77  SmallVector<StringRef, 16> Names;
78  for (const T *Decl : OrderedDecls) {
79  if (DeclsToInit.count(Decl))
80  Names.emplace_back(getName(Decl));
81  }
82  return llvm::join(Names.begin(), Names.end(), ", ");
83 }
84 
85 SourceLocation getLocationForEndOfToken(const ASTContext &Context,
86  SourceLocation Location) {
87  return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
88  Context.getLangOpts());
89 }
90 
91 // There are 3 kinds of insertion placements:
92 enum class InitializerPlacement {
93  // 1. The fields are inserted after an existing CXXCtorInitializer stored in
94  // Where. This will be the case whenever there is a written initializer before
95  // the fields available.
96  After,
97 
98  // 2. The fields are inserted before the first existing initializer stored in
99  // Where.
100  Before,
101 
102  // 3. There are no written initializers and the fields will be inserted before
103  // the constructor's body creating a new initializer list including the ':'.
104  New
105 };
106 
107 // An InitializerInsertion contains a list of fields and/or base classes to
108 // insert into the initializer list of a constructor. We use this to ensure
109 // proper absolute ordering according to the class declaration relative to the
110 // (perhaps improper) ordering in the existing initializer list, if any.
111 struct IntializerInsertion {
112  IntializerInsertion(InitializerPlacement Placement,
113  const CXXCtorInitializer *Where)
114  : Placement(Placement), Where(Where) {}
115 
116  SourceLocation getLocation(const ASTContext &Context,
117  const CXXConstructorDecl &Constructor) const {
118  assert((Where != nullptr || Placement == InitializerPlacement::New) &&
119  "Location should be relative to an existing initializer or this "
120  "insertion represents a new initializer list.");
121  SourceLocation Location;
122  switch (Placement) {
123  case InitializerPlacement::New:
125  Context, Constructor.getBody()->getLocStart())
126  .getLocation();
127  break;
128  case InitializerPlacement::Before:
130  Context, Where->getSourceRange().getBegin())
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 T *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 T *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(), false,
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->getLocStart().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 
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  auto HasDefaultConstructor = hasInitializer(
273  cxxConstructExpr(unless(requiresZeroInitialization()),
274  hasDeclaration(cxxConstructorDecl(
275  isDefaultConstructor(), unless(isUserProvided())))));
276  Finder->addMatcher(
277  varDecl(isDefinition(), HasDefaultConstructor,
278  hasAutomaticStorageDuration(),
279  hasType(recordDecl(has(fieldDecl()),
281  .bind("var"),
282  this);
283 }
284 
285 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
286  if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
287  // Skip declarations delayed by late template parsing without a body.
288  if (!Ctor->getBody())
289  return;
290  checkMissingMemberInitializer(*Result.Context, Ctor);
291  checkMissingBaseClassInitializer(*Result.Context, Ctor);
292  } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
293  checkUninitializedTrivialType(*Result.Context, Var);
294  }
295 }
296 
298  Options.store(Opts, "IgnoreArrays", IgnoreArrays);
299 }
300 
301 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
302  ASTContext &Context, const CXXConstructorDecl *Ctor) {
303  const CXXRecordDecl *ClassDecl = Ctor->getParent();
304  bool IsUnion = ClassDecl->isUnion();
305 
306  if (IsUnion && ClassDecl->hasInClassInitializer())
307  return;
308 
309  // Gather all fields (direct and indirect) that need to be initialized.
310  SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
311  forEachField(ClassDecl, ClassDecl->fields(), false, [&](const FieldDecl *F) {
312  if (!F->hasInClassInitializer() &&
314  Context))
315  FieldsToInit.insert(F);
316  });
317  if (FieldsToInit.empty())
318  return;
319 
320  for (const CXXCtorInitializer *Init : Ctor->inits()) {
321  // Remove any fields that were explicitly written in the initializer list
322  // or in-class.
323  if (Init->isAnyMemberInitializer() && Init->isWritten()) {
324  if (IsUnion)
325  return; // We can only initialize one member of a union.
326  FieldsToInit.erase(Init->getAnyMember());
327  }
328  }
329  removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
330 
331  // Collect all fields in order, both direct fields and indirect fields from
332  // anonmyous record types.
333  SmallVector<const FieldDecl *, 16> OrderedFields;
334  forEachField(ClassDecl, ClassDecl->fields(), false,
335  [&](const FieldDecl *F) { OrderedFields.push_back(F); });
336 
337  // Collect all the fields we need to initialize, including indirect fields.
338  SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
339  forEachField(ClassDecl, FieldsToInit, false,
340  [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
341  if (AllFieldsToInit.empty())
342  return;
343 
344  DiagnosticBuilder Diag =
345  diag(Ctor->getLocStart(),
346  IsUnion
347  ? "union constructor should initialize one of these fields: %0"
348  : "constructor does not initialize these fields: %0")
349  << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
350 
351  // Do not propose fixes in macros since we cannot place them correctly.
352  if (Ctor->getLocStart().isMacroID())
353  return;
354 
355  // Collect all fields but only suggest a fix for the first member of unions,
356  // as initializing more than one union member is an error.
357  SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
358  forEachField(ClassDecl, FieldsToInit, true, [&](const FieldDecl *F) {
359  // Don't suggest fixes for enums because we don't know a good default.
360  if (!F->getType()->isEnumeralType())
361  FieldsToFix.insert(F);
362  });
363  if (FieldsToFix.empty())
364  return;
365 
366  // Use in-class initialization if possible.
367  if (Context.getLangOpts().CPlusPlus11) {
368  for (const FieldDecl *Field : FieldsToFix) {
369  Diag << FixItHint::CreateInsertion(
370  getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
371  "{}");
372  }
373  } else {
374  // Otherwise, rewrite the constructor's initializer list.
375  fixInitializerList(Context, Diag, Ctor, FieldsToFix);
376  }
377 }
378 
379 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
380  const ASTContext &Context, const CXXConstructorDecl *Ctor) {
381  const CXXRecordDecl *ClassDecl = Ctor->getParent();
382 
383  // Gather any base classes that need to be initialized.
384  SmallVector<const RecordDecl *, 4> AllBases;
385  SmallPtrSet<const RecordDecl *, 4> BasesToInit;
386  for (const CXXBaseSpecifier &Base : ClassDecl->bases()) {
387  if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
388  AllBases.emplace_back(BaseClassDecl);
389  if (!BaseClassDecl->field_empty() &&
391  Context))
392  BasesToInit.insert(BaseClassDecl);
393  }
394  }
395 
396  if (BasesToInit.empty())
397  return;
398 
399  // Remove any bases that were explicitly written in the initializer list.
400  for (const CXXCtorInitializer *Init : Ctor->inits()) {
401  if (Init->isBaseInitializer() && Init->isWritten())
402  BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
403  }
404 
405  if (BasesToInit.empty())
406  return;
407 
408  DiagnosticBuilder Diag =
409  diag(Ctor->getLocStart(),
410  "constructor does not initialize these bases: %0")
411  << toCommaSeparatedString(AllBases, BasesToInit);
412 
413  fixInitializerList(Context, Diag, Ctor, BasesToInit);
414 }
415 
416 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
417  const ASTContext &Context, const VarDecl *Var) {
418  DiagnosticBuilder Diag =
419  diag(Var->getLocStart(), "uninitialized record type: %0") << Var;
420 
421  Diag << FixItHint::CreateInsertion(
422  getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
423  Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
424 }
425 
426 } // namespace cppcoreguidelines
427 } // namespace tidy
428 } // namespace clang
const std::string Name
Definition: USRFinder.cpp:140
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:170
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:210
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Base class for all clang-tidy checks.
Definition: ClangTidy.h:110
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:80
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:385
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:93
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
InitializerPlacement Placement
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidy.cpp:352
const NamedDecl * Result
Definition: USRFinder.cpp:137
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