xref: /llvm-project/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp (revision aaadaee7b228d7010ff7076f5002ebb96b5e03dc)
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::tidy::cppcoreguidelines {
24 
25 namespace {
26 
27 AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) {
28   return Node.hasDefaultConstructor();
29 }
30 
31 // Iterate over all the fields in a record type, both direct and indirect (e.g.
32 // if the record contains an anonymous struct).
33 template <typename T, typename Func>
34 void forEachField(const RecordDecl &Record, const T &Fields, const Func &Fn) {
35   for (const FieldDecl *F : Fields) {
36     if (F->isAnonymousStructOrUnion()) {
37       if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl())
38         forEachField(*R, R->fields(), Fn);
39     } else {
40       Fn(F);
41     }
42   }
43 }
44 
45 template <typename T, typename Func>
46 void forEachFieldWithFilter(const RecordDecl &Record, const T &Fields,
47                             bool &AnyMemberHasInitPerUnion, const Func &Fn) {
48   for (const FieldDecl *F : Fields) {
49     if (F->isAnonymousStructOrUnion()) {
50       if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl()) {
51         AnyMemberHasInitPerUnion = false;
52         forEachFieldWithFilter(*R, R->fields(), AnyMemberHasInitPerUnion, Fn);
53       }
54     } else {
55       Fn(F);
56     }
57     if (Record.isUnion() && AnyMemberHasInitPerUnion)
58       break;
59   }
60 }
61 
62 void removeFieldInitialized(const FieldDecl *M,
63                             SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
64   const RecordDecl *R = M->getParent();
65   if (R && R->isUnion()) {
66     // Erase all members in a union if any member of it is initialized.
67     for (const auto *F : R->fields())
68       FieldDecls.erase(F);
69   } else
70     FieldDecls.erase(M);
71 }
72 
73 void removeFieldsInitializedInBody(
74     const Stmt &Stmt, ASTContext &Context,
75     SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
76   auto Matches =
77       match(findAll(binaryOperator(
78                 hasOperatorName("="),
79                 hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))),
80             Stmt, Context);
81   for (const auto &Match : Matches)
82     removeFieldInitialized(Match.getNodeAs<FieldDecl>("fieldDecl"), FieldDecls);
83 }
84 
85 StringRef getName(const FieldDecl *Field) { return Field->getName(); }
86 
87 StringRef getName(const RecordDecl *Record) {
88   // Get the typedef name if this is a C-style anonymous struct and typedef.
89   if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
90     return Typedef->getName();
91   return Record->getName();
92 }
93 
94 // Creates comma separated list of decls requiring initialization in order of
95 // declaration.
96 template <typename R, typename T>
97 std::string
98 toCommaSeparatedString(const R &OrderedDecls,
99                        const SmallPtrSetImpl<const T *> &DeclsToInit) {
100   SmallVector<StringRef, 16> Names;
101   for (const T *Decl : OrderedDecls) {
102     if (DeclsToInit.count(Decl))
103       Names.emplace_back(getName(Decl));
104   }
105   return llvm::join(Names.begin(), Names.end(), ", ");
106 }
107 
108 SourceLocation getLocationForEndOfToken(const ASTContext &Context,
109                                         SourceLocation Location) {
110   return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
111                                     Context.getLangOpts());
112 }
113 
114 // There are 3 kinds of insertion placements:
115 enum class InitializerPlacement {
116   // 1. The fields are inserted after an existing CXXCtorInitializer stored in
117   // Where. This will be the case whenever there is a written initializer before
118   // the fields available.
119   After,
120 
121   // 2. The fields are inserted before the first existing initializer stored in
122   // Where.
123   Before,
124 
125   // 3. There are no written initializers and the fields will be inserted before
126   // the constructor's body creating a new initializer list including the ':'.
127   New
128 };
129 
130 // An InitializerInsertion contains a list of fields and/or base classes to
131 // insert into the initializer list of a constructor. We use this to ensure
132 // proper absolute ordering according to the class declaration relative to the
133 // (perhaps improper) ordering in the existing initializer list, if any.
134 struct InitializerInsertion {
135   InitializerInsertion(InitializerPlacement Placement,
136                        const CXXCtorInitializer *Where)
137       : Placement(Placement), Where(Where) {}
138 
139   SourceLocation getLocation(const ASTContext &Context,
140                              const CXXConstructorDecl &Constructor) const {
141     assert((Where != nullptr || Placement == InitializerPlacement::New) &&
142            "Location should be relative to an existing initializer or this "
143            "insertion represents a new initializer list.");
144     SourceLocation Location;
145     switch (Placement) {
146     case InitializerPlacement::New:
147       Location = utils::lexer::getPreviousToken(
148                      Constructor.getBody()->getBeginLoc(),
149                      Context.getSourceManager(), Context.getLangOpts())
150                      .getLocation();
151       break;
152     case InitializerPlacement::Before:
153       Location = utils::lexer::getPreviousToken(
154                      Where->getSourceRange().getBegin(),
155                      Context.getSourceManager(), Context.getLangOpts())
156                      .getLocation();
157       break;
158     case InitializerPlacement::After:
159       Location = Where->getRParenLoc();
160       break;
161     }
162     return getLocationForEndOfToken(Context, Location);
163   }
164 
165   std::string codeToInsert() const {
166     assert(!Initializers.empty() && "No initializers to insert");
167     std::string Code;
168     llvm::raw_string_ostream Stream(Code);
169     std::string Joined =
170         llvm::join(Initializers.begin(), Initializers.end(), "(), ");
171     switch (Placement) {
172     case InitializerPlacement::New:
173       Stream << " : " << Joined << "()";
174       break;
175     case InitializerPlacement::Before:
176       Stream << " " << Joined << "(),";
177       break;
178     case InitializerPlacement::After:
179       Stream << ", " << Joined << "()";
180       break;
181     }
182     return Stream.str();
183   }
184 
185   InitializerPlacement Placement;
186   const CXXCtorInitializer *Where;
187   SmallVector<std::string, 4> Initializers;
188 };
189 
190 // Convenience utility to get a RecordDecl from a QualType.
191 const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
192   if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
193     return RT->getDecl();
194   return nullptr;
195 }
196 
197 template <typename R, typename T>
198 SmallVector<InitializerInsertion, 16>
199 computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
200                   const R &OrderedDecls,
201                   const SmallPtrSetImpl<const T *> &DeclsToInit) {
202   SmallVector<InitializerInsertion, 16> Insertions;
203   Insertions.emplace_back(InitializerPlacement::New, nullptr);
204 
205   typename R::const_iterator Decl = std::begin(OrderedDecls);
206   for (const CXXCtorInitializer *Init : Inits) {
207     if (Init->isWritten()) {
208       if (Insertions.size() == 1)
209         Insertions.emplace_back(InitializerPlacement::Before, Init);
210 
211       // Gets either the field or base class being initialized by the provided
212       // initializer.
213       const auto *InitDecl =
214           Init->isAnyMemberInitializer()
215               ? static_cast<const NamedDecl *>(Init->getAnyMember())
216               : Init->getBaseClass()->getAsCXXRecordDecl();
217 
218       // Add all fields between current field up until the next initializer.
219       for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
220         if (const auto *D = dyn_cast<T>(*Decl)) {
221           if (DeclsToInit.contains(D))
222             Insertions.back().Initializers.emplace_back(getName(D));
223         }
224       }
225 
226       Insertions.emplace_back(InitializerPlacement::After, Init);
227     }
228   }
229 
230   // Add remaining decls that require initialization.
231   for (; Decl != std::end(OrderedDecls); ++Decl) {
232     if (const auto *D = dyn_cast<T>(*Decl)) {
233       if (DeclsToInit.contains(D))
234         Insertions.back().Initializers.emplace_back(getName(D));
235     }
236   }
237   return Insertions;
238 }
239 
240 // Gets the list of bases and members that could possibly be initialized, in
241 // order as they appear in the class declaration.
242 void getInitializationsInOrder(const CXXRecordDecl &ClassDecl,
243                                SmallVectorImpl<const NamedDecl *> &Decls) {
244   Decls.clear();
245   for (const auto &Base : ClassDecl.bases()) {
246     // Decl may be null if the base class is a template parameter.
247     if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
248       Decls.emplace_back(Decl);
249     }
250   }
251   forEachField(ClassDecl, ClassDecl.fields(),
252                [&](const FieldDecl *F) { Decls.push_back(F); });
253 }
254 
255 template <typename T>
256 void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
257                         const CXXConstructorDecl *Ctor,
258                         const SmallPtrSetImpl<const T *> &DeclsToInit) {
259   // Do not propose fixes in macros since we cannot place them correctly.
260   if (Ctor->getBeginLoc().isMacroID())
261     return;
262 
263   SmallVector<const NamedDecl *, 16> OrderedDecls;
264   getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
265 
266   for (const auto &Insertion :
267        computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
268     if (!Insertion.Initializers.empty())
269       Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
270                                          Insertion.codeToInsert());
271   }
272 }
273 
274 } // anonymous namespace
275 
276 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
277                                                ClangTidyContext *Context)
278     : ClangTidyCheck(Name, Context),
279       IgnoreArrays(Options.get("IgnoreArrays", false)),
280       UseAssignment(Options.get("UseAssignment", false)) {}
281 
282 void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
283   auto IsUserProvidedNonDelegatingConstructor =
284       allOf(isUserProvided(), unless(isInstantiated()),
285             unless(isDelegatingConstructor()),
286             ofClass(cxxRecordDecl().bind("parent")),
287             unless(hasAnyConstructorInitializer(cxxCtorInitializer(
288                 isWritten(), unless(isMemberInitializer()),
289                 hasTypeLoc(loc(
290                     qualType(hasDeclaration(equalsBoundNode("parent")))))))));
291 
292   auto IsNonTrivialDefaultConstructor = allOf(
293       isDefaultConstructor(), unless(isUserProvided()),
294       hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
295   Finder->addMatcher(
296       cxxConstructorDecl(isDefinition(),
297                          anyOf(IsUserProvidedNonDelegatingConstructor,
298                                IsNonTrivialDefaultConstructor))
299           .bind("ctor"),
300       this);
301 
302   // Match classes with a default constructor that is defaulted or is not in the
303   // AST.
304   Finder->addMatcher(
305       cxxRecordDecl(
306           isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
307           anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
308                                        unless(isImplicit()))),
309                 unless(has(cxxConstructorDecl()))),
310           unless(isTriviallyDefaultConstructible()))
311           .bind("record"),
312       this);
313 
314   auto HasDefaultConstructor = hasInitializer(
315       cxxConstructExpr(unless(requiresZeroInitialization()),
316                        hasDeclaration(cxxConstructorDecl(
317                            isDefaultConstructor(), unless(isUserProvided())))));
318   Finder->addMatcher(
319       varDecl(isDefinition(), HasDefaultConstructor,
320               hasAutomaticStorageDuration(),
321               hasType(recordDecl(has(fieldDecl()),
322                                  isTriviallyDefaultConstructible())))
323           .bind("var"),
324       this);
325 }
326 
327 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
328   if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
329     // Skip declarations delayed by late template parsing without a body.
330     if (!Ctor->getBody())
331       return;
332     // Skip out-of-band explicitly defaulted special member functions
333     // (except the default constructor).
334     if (Ctor->isExplicitlyDefaulted() && !Ctor->isDefaultConstructor())
335       return;
336     checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
337     checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
338   } else if (const auto *Record =
339                  Result.Nodes.getNodeAs<CXXRecordDecl>("record")) {
340     assert(Record->hasDefaultConstructor() &&
341            "Matched record should have a default constructor");
342     checkMissingMemberInitializer(*Result.Context, *Record, nullptr);
343     checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr);
344   } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
345     checkUninitializedTrivialType(*Result.Context, Var);
346   }
347 }
348 
349 void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
350   Options.store(Opts, "IgnoreArrays", IgnoreArrays);
351   Options.store(Opts, "UseAssignment", UseAssignment);
352 }
353 
354 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
355 static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) {
356   if (T->isIncompleteArrayType())
357     return true;
358 
359   while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
360     if (!ArrayT->getSize())
361       return true;
362 
363     T = ArrayT->getElementType();
364   }
365 
366   return false;
367 }
368 
369 static bool isEmpty(ASTContext &Context, const QualType &Type) {
370   if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
371     return ClassDecl->isEmpty();
372   }
373   return isIncompleteOrZeroLengthArrayType(Context, Type);
374 }
375 
376 static const char *getInitializer(QualType QT, bool UseAssignment) {
377   const char *DefaultInitializer = "{}";
378   if (!UseAssignment)
379     return DefaultInitializer;
380 
381   if (QT->isPointerType())
382     return " = nullptr";
383 
384   const auto *BT = dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
385   if (!BT)
386     return DefaultInitializer;
387 
388   switch (BT->getKind()) {
389   case BuiltinType::Bool:
390     return " = false";
391   case BuiltinType::Float:
392     return " = 0.0F";
393   case BuiltinType::Double:
394     return " = 0.0";
395   case BuiltinType::LongDouble:
396     return " = 0.0L";
397   case BuiltinType::SChar:
398   case BuiltinType::Char_S:
399   case BuiltinType::WChar_S:
400   case BuiltinType::Char16:
401   case BuiltinType::Char32:
402   case BuiltinType::Short:
403   case BuiltinType::Int:
404     return " = 0";
405   case BuiltinType::UChar:
406   case BuiltinType::Char_U:
407   case BuiltinType::WChar_U:
408   case BuiltinType::UShort:
409   case BuiltinType::UInt:
410     return " = 0U";
411   case BuiltinType::Long:
412     return " = 0L";
413   case BuiltinType::ULong:
414     return " = 0UL";
415   case BuiltinType::LongLong:
416     return " = 0LL";
417   case BuiltinType::ULongLong:
418     return " = 0ULL";
419 
420   default:
421     return DefaultInitializer;
422   }
423 }
424 
425 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
426     ASTContext &Context, const CXXRecordDecl &ClassDecl,
427     const CXXConstructorDecl *Ctor) {
428   bool IsUnion = ClassDecl.isUnion();
429 
430   if (IsUnion && ClassDecl.hasInClassInitializer())
431     return;
432 
433   // Gather all fields (direct and indirect) that need to be initialized.
434   SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
435   bool AnyMemberHasInitPerUnion = false;
436   forEachFieldWithFilter(ClassDecl, ClassDecl.fields(),
437                          AnyMemberHasInitPerUnion, [&](const FieldDecl *F) {
438     if (IgnoreArrays && F->getType()->isArrayType())
439       return;
440     if (F->hasInClassInitializer() && F->getParent()->isUnion()) {
441       AnyMemberHasInitPerUnion = true;
442       removeFieldInitialized(F, FieldsToInit);
443     }
444     if (!F->hasInClassInitializer() &&
445         utils::type_traits::isTriviallyDefaultConstructible(F->getType(),
446                                                             Context) &&
447         !isEmpty(Context, F->getType()) && !F->isUnnamedBitField() &&
448         !AnyMemberHasInitPerUnion)
449       FieldsToInit.insert(F);
450   });
451   if (FieldsToInit.empty())
452     return;
453 
454   if (Ctor) {
455     for (const CXXCtorInitializer *Init : Ctor->inits()) {
456       // Remove any fields that were explicitly written in the initializer list
457       // or in-class.
458       if (Init->isAnyMemberInitializer() && Init->isWritten()) {
459         if (IsUnion)
460           return; // We can only initialize one member of a union.
461         removeFieldInitialized(Init->getAnyMember(), FieldsToInit);
462       }
463     }
464     removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
465   }
466 
467   // Collect all fields in order, both direct fields and indirect fields from
468   // anonymous record types.
469   SmallVector<const FieldDecl *, 16> OrderedFields;
470   forEachField(ClassDecl, ClassDecl.fields(),
471                [&](const FieldDecl *F) { OrderedFields.push_back(F); });
472 
473   // Collect all the fields we need to initialize, including indirect fields.
474   // It only includes fields that have not been fixed
475   SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
476   forEachField(ClassDecl, FieldsToInit, [&](const FieldDecl *F) {
477     if (HasRecordClassMemberSet.insert(F).second)
478       AllFieldsToInit.insert(F);
479   });
480   if (FieldsToInit.empty())
481     return;
482 
483   DiagnosticBuilder Diag =
484       diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
485            "%select{|union }0constructor %select{does not|should}0 initialize "
486            "%select{|one of }0these fields: %1")
487       << IsUnion << toCommaSeparatedString(OrderedFields, FieldsToInit);
488 
489   if (AllFieldsToInit.empty())
490     return;
491 
492   // Do not propose fixes for constructors in macros since we cannot place them
493   // correctly.
494   if (Ctor && Ctor->getBeginLoc().isMacroID())
495     return;
496 
497   // Collect all fields but only suggest a fix for the first member of unions,
498   // as initializing more than one union member is an error.
499   SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
500   AnyMemberHasInitPerUnion = false;
501   forEachFieldWithFilter(ClassDecl, ClassDecl.fields(),
502                          AnyMemberHasInitPerUnion, [&](const FieldDecl *F) {
503     if (!FieldsToInit.count(F))
504       return;
505     // Don't suggest fixes for enums because we don't know a good default.
506     // Don't suggest fixes for bitfields because in-class initialization is not
507     // possible until C++20.
508     if (F->getType()->isEnumeralType() ||
509         (!getLangOpts().CPlusPlus20 && F->isBitField()))
510       return;
511     FieldsToFix.insert(F);
512     AnyMemberHasInitPerUnion = true;
513   });
514   if (FieldsToFix.empty())
515     return;
516 
517   // Use in-class initialization if possible.
518   if (Context.getLangOpts().CPlusPlus11) {
519     for (const FieldDecl *Field : FieldsToFix) {
520       Diag << FixItHint::CreateInsertion(
521           getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
522           getInitializer(Field->getType(), UseAssignment));
523     }
524   } else if (Ctor) {
525     // Otherwise, rewrite the constructor's initializer list.
526     fixInitializerList(Context, Diag, Ctor, FieldsToFix);
527   }
528 }
529 
530 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
531     const ASTContext &Context, const CXXRecordDecl &ClassDecl,
532     const CXXConstructorDecl *Ctor) {
533 
534   // Gather any base classes that need to be initialized.
535   SmallVector<const RecordDecl *, 4> AllBases;
536   SmallPtrSet<const RecordDecl *, 4> BasesToInit;
537   for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
538     if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
539       AllBases.emplace_back(BaseClassDecl);
540       if (!BaseClassDecl->field_empty() &&
541           utils::type_traits::isTriviallyDefaultConstructible(Base.getType(),
542                                                               Context))
543         BasesToInit.insert(BaseClassDecl);
544     }
545   }
546 
547   if (BasesToInit.empty())
548     return;
549 
550   // Remove any bases that were explicitly written in the initializer list.
551   if (Ctor) {
552     if (Ctor->isImplicit())
553       return;
554 
555     for (const CXXCtorInitializer *Init : Ctor->inits()) {
556       if (Init->isBaseInitializer() && Init->isWritten())
557         BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
558     }
559   }
560 
561   if (BasesToInit.empty())
562     return;
563 
564   DiagnosticBuilder Diag =
565       diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
566            "constructor does not initialize these bases: %0")
567       << toCommaSeparatedString(AllBases, BasesToInit);
568 
569   if (Ctor)
570     fixInitializerList(Context, Diag, Ctor, BasesToInit);
571 }
572 
573 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
574     const ASTContext &Context, const VarDecl *Var) {
575   DiagnosticBuilder Diag =
576       diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
577 
578   Diag << FixItHint::CreateInsertion(
579       getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
580       Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
581 }
582 
583 } // namespace clang::tidy::cppcoreguidelines
584