• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
9 #include "ProTypeMemberInitCheck.h"
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 
AST_MATCHER(CXXRecordDecl,hasDefaultConstructor)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>
forEachField(const RecordDecl & Record,const T & Fields,Func && Fn)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 
removeFieldsInitializedInBody(const Stmt & Stmt,ASTContext & Context,SmallPtrSetImpl<const FieldDecl * > & FieldDecls)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 
getName(const FieldDecl * Field)59 StringRef getName(const FieldDecl *Field) { return Field->getName(); }
60 
getName(const RecordDecl * Record)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
toCommaSeparatedString(const R & OrderedDecls,const SmallPtrSetImpl<const T * > & DeclsToInit)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 
getLocationForEndOfToken(const ASTContext & Context,SourceLocation Location)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:
89 enum class InitializerPlacement {
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 {
IntializerInsertionclang::tidy::cppcoreguidelines::__anon2f173d760111::IntializerInsertion109   IntializerInsertion(InitializerPlacement Placement,
110                       const CXXCtorInitializer *Where)
111       : Placement(Placement), Where(Where) {}
112 
getLocationclang::tidy::cppcoreguidelines::__anon2f173d760111::IntializerInsertion113   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:
121       Location = utils::lexer::getPreviousToken(
122                      Constructor.getBody()->getBeginLoc(),
123                      Context.getSourceManager(), Context.getLangOpts())
124                      .getLocation();
125       break;
126     case InitializerPlacement::Before:
127       Location = utils::lexer::getPreviousToken(
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 
codeToInsertclang::tidy::cppcoreguidelines::__anon2f173d760111::IntializerInsertion139   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.
getCanonicalRecordDecl(const QualType & Type)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>
computeInsertions(const CXXConstructorDecl::init_const_range & Inits,const R & OrderedDecls,const SmallPtrSetImpl<const T * > & DeclsToInit)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 initializer.
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.
getInitializationsInOrder(const CXXRecordDecl & ClassDecl,SmallVectorImpl<const NamedDecl * > & Decls)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>
fixInitializerList(const ASTContext & Context,DiagnosticBuilder & Diag,const CXXConstructorDecl * Ctor,const SmallPtrSetImpl<const T * > & DeclsToInit)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 
ProTypeMemberInitCheck(StringRef Name,ClangTidyContext * Context)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 
registerMatchers(MatchFinder * Finder)256 void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
257   auto IsUserProvidedNonDelegatingConstructor =
258       allOf(isUserProvided(),
259             unless(anyOf(isInstantiated(), isDelegatingConstructor())));
260   auto IsNonTrivialDefaultConstructor = allOf(
261       isDefaultConstructor(), unless(isUserProvided()),
262       hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
263   Finder->addMatcher(
264       cxxConstructorDecl(isDefinition(),
265                          anyOf(IsUserProvidedNonDelegatingConstructor,
266                                IsNonTrivialDefaultConstructor))
267           .bind("ctor"),
268       this);
269 
270   // Match classes with a default constructor that is defaulted or is not in the
271   // AST.
272   Finder->addMatcher(
273       cxxRecordDecl(
274           isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
275           anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
276                                        unless(isImplicit()))),
277                 unless(has(cxxConstructorDecl()))),
278           unless(isTriviallyDefaultConstructible()))
279           .bind("record"),
280       this);
281 
282   auto HasDefaultConstructor = hasInitializer(
283       cxxConstructExpr(unless(requiresZeroInitialization()),
284                        hasDeclaration(cxxConstructorDecl(
285                            isDefaultConstructor(), unless(isUserProvided())))));
286   Finder->addMatcher(
287       varDecl(isDefinition(), HasDefaultConstructor,
288               hasAutomaticStorageDuration(),
289               hasType(recordDecl(has(fieldDecl()),
290                                  isTriviallyDefaultConstructible())))
291           .bind("var"),
292       this);
293 }
294 
check(const MatchFinder::MatchResult & Result)295 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
296   if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
297     // Skip declarations delayed by late template parsing without a body.
298     if (!Ctor->getBody())
299       return;
300     checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
301     checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
302   } else if (const auto *Record =
303                  Result.Nodes.getNodeAs<CXXRecordDecl>("record")) {
304     assert(Record->hasDefaultConstructor() &&
305            "Matched record should have a default constructor");
306     checkMissingMemberInitializer(*Result.Context, *Record, nullptr);
307     checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr);
308   } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
309     checkUninitializedTrivialType(*Result.Context, Var);
310   }
311 }
312 
storeOptions(ClangTidyOptions::OptionMap & Opts)313 void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
314   Options.store(Opts, "IgnoreArrays", IgnoreArrays);
315   Options.store(Opts, "UseAssignment", UseAssignment);
316 }
317 
318 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
isIncompleteOrZeroLengthArrayType(ASTContext & Context,QualType T)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 
isEmpty(ASTContext & Context,const QualType & Type)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 
getInitializer(QualType QT,bool UseAssignment)340 static const char *getInitializer(QualType QT, bool UseAssignment) {
341   const char *DefaultInitializer = "{}";
342   if (!UseAssignment)
343     return DefaultInitializer;
344 
345   if (QT->isPointerType())
346     return " = nullptr";
347 
348   const BuiltinType *BT =
349       dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
350   if (!BT)
351     return DefaultInitializer;
352 
353   switch (BT->getKind()) {
354   case BuiltinType::Bool:
355     return " = false";
356   case BuiltinType::Float:
357     return " = 0.0F";
358   case BuiltinType::Double:
359     return " = 0.0";
360   case BuiltinType::LongDouble:
361     return " = 0.0L";
362   case BuiltinType::SChar:
363   case BuiltinType::Char_S:
364   case BuiltinType::WChar_S:
365   case BuiltinType::Char16:
366   case BuiltinType::Char32:
367   case BuiltinType::Short:
368   case BuiltinType::Int:
369     return " = 0";
370   case BuiltinType::UChar:
371   case BuiltinType::Char_U:
372   case BuiltinType::WChar_U:
373   case BuiltinType::UShort:
374   case BuiltinType::UInt:
375     return " = 0U";
376   case BuiltinType::Long:
377     return " = 0L";
378   case BuiltinType::ULong:
379     return " = 0UL";
380   case BuiltinType::LongLong:
381     return " = 0LL";
382   case BuiltinType::ULongLong:
383     return " = 0ULL";
384 
385   default:
386     return DefaultInitializer;
387   }
388 }
389 
checkMissingMemberInitializer(ASTContext & Context,const CXXRecordDecl & ClassDecl,const CXXConstructorDecl * Ctor)390 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
391     ASTContext &Context, const CXXRecordDecl &ClassDecl,
392     const CXXConstructorDecl *Ctor) {
393   bool IsUnion = ClassDecl.isUnion();
394 
395   if (IsUnion && ClassDecl.hasInClassInitializer())
396     return;
397 
398   // Gather all fields (direct and indirect) that need to be initialized.
399   SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
400   forEachField(ClassDecl, ClassDecl.fields(), [&](const FieldDecl *F) {
401     if (!F->hasInClassInitializer() &&
402         utils::type_traits::isTriviallyDefaultConstructible(F->getType(),
403                                                             Context) &&
404         !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
405       FieldsToInit.insert(F);
406   });
407   if (FieldsToInit.empty())
408     return;
409 
410   if (Ctor) {
411     for (const CXXCtorInitializer *Init : Ctor->inits()) {
412       // Remove any fields that were explicitly written in the initializer list
413       // or in-class.
414       if (Init->isAnyMemberInitializer() && Init->isWritten()) {
415         if (IsUnion)
416           return; // We can only initialize one member of a union.
417         FieldsToInit.erase(Init->getAnyMember());
418       }
419     }
420     removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
421   }
422 
423   // Collect all fields in order, both direct fields and indirect fields from
424   // anonymous record types.
425   SmallVector<const FieldDecl *, 16> OrderedFields;
426   forEachField(ClassDecl, ClassDecl.fields(),
427                [&](const FieldDecl *F) { OrderedFields.push_back(F); });
428 
429   // Collect all the fields we need to initialize, including indirect fields.
430   SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
431   forEachField(ClassDecl, FieldsToInit,
432                [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
433   if (AllFieldsToInit.empty())
434     return;
435 
436   DiagnosticBuilder Diag =
437       diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
438            IsUnion
439                ? "union constructor should initialize one of these fields: %0"
440                : "constructor does not initialize these fields: %0")
441       << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
442 
443   // Do not propose fixes for constructors in macros since we cannot place them
444   // correctly.
445   if (Ctor && Ctor->getBeginLoc().isMacroID())
446     return;
447 
448   // Collect all fields but only suggest a fix for the first member of unions,
449   // as initializing more than one union member is an error.
450   SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
451   SmallPtrSet<const RecordDecl *, 4> UnionsSeen;
452   forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) {
453     if (!FieldsToInit.count(F))
454       return;
455     // Don't suggest fixes for enums because we don't know a good default.
456     // Don't suggest fixes for bitfields because in-class initialization is not
457     // possible until C++20.
458     if (F->getType()->isEnumeralType() ||
459         (!getLangOpts().CPlusPlus20 && F->isBitField()))
460       return;
461     if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second)
462       FieldsToFix.insert(F);
463   });
464   if (FieldsToFix.empty())
465     return;
466 
467   // Use in-class initialization if possible.
468   if (Context.getLangOpts().CPlusPlus11) {
469     for (const FieldDecl *Field : FieldsToFix) {
470       Diag << FixItHint::CreateInsertion(
471           getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
472           getInitializer(Field->getType(), UseAssignment));
473     }
474   } else if (Ctor) {
475     // Otherwise, rewrite the constructor's initializer list.
476     fixInitializerList(Context, Diag, Ctor, FieldsToFix);
477   }
478 }
479 
checkMissingBaseClassInitializer(const ASTContext & Context,const CXXRecordDecl & ClassDecl,const CXXConstructorDecl * Ctor)480 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
481     const ASTContext &Context, const CXXRecordDecl &ClassDecl,
482     const CXXConstructorDecl *Ctor) {
483 
484   // Gather any base classes that need to be initialized.
485   SmallVector<const RecordDecl *, 4> AllBases;
486   SmallPtrSet<const RecordDecl *, 4> BasesToInit;
487   for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
488     if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
489       AllBases.emplace_back(BaseClassDecl);
490       if (!BaseClassDecl->field_empty() &&
491           utils::type_traits::isTriviallyDefaultConstructible(Base.getType(),
492                                                               Context))
493         BasesToInit.insert(BaseClassDecl);
494     }
495   }
496 
497   if (BasesToInit.empty())
498     return;
499 
500   // Remove any bases that were explicitly written in the initializer list.
501   if (Ctor) {
502     if (Ctor->isImplicit())
503       return;
504 
505     for (const CXXCtorInitializer *Init : Ctor->inits()) {
506       if (Init->isBaseInitializer() && Init->isWritten())
507         BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
508     }
509   }
510 
511   if (BasesToInit.empty())
512     return;
513 
514   DiagnosticBuilder Diag =
515       diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
516            "constructor does not initialize these bases: %0")
517       << toCommaSeparatedString(AllBases, BasesToInit);
518 
519   if (Ctor)
520     fixInitializerList(Context, Diag, Ctor, BasesToInit);
521 }
522 
checkUninitializedTrivialType(const ASTContext & Context,const VarDecl * Var)523 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
524     const ASTContext &Context, const VarDecl *Var) {
525   DiagnosticBuilder Diag =
526       diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
527 
528   Diag << FixItHint::CreateInsertion(
529       getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
530       Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
531 }
532 
533 } // namespace cppcoreguidelines
534 } // namespace tidy
535 } // namespace clang
536