13#include "clang/AST/ASTContext.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/Lex/Lexer.h"
16#include "llvm/ADT/SmallPtrSet.h"
20using llvm::SmallPtrSet;
21using llvm::SmallPtrSetImpl;
28 return Node.hasDefaultConstructor();
35template <
typename T,
typename Func>
38 for (
const FieldDecl *F : Fields) {
39 if (F->isAnonymousStructOrUnion()) {
40 if (
const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl())
48template <
typename T,
typename Func>
50 bool &AnyMemberHasInitPerUnion,
52 for (
const FieldDecl *F : Fields) {
53 if (F->isAnonymousStructOrUnion()) {
54 if (
const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl()) {
55 AnyMemberHasInitPerUnion =
false;
61 if (Record.isUnion() && AnyMemberHasInitPerUnion)
68 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
69 const RecordDecl *R = M->getParent();
70 if (R && R->isUnion()) {
72 for (
const auto *F : R->fields())
80 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
82 match(findAll(binaryOperator(
84 hasLHS(memberExpr(member(fieldDecl().bind(
"fieldDecl")))))),
86 for (
const auto &Match : Matches)
90static StringRef
getName(
const FieldDecl *Field) {
return Field->getName(); }
92static StringRef
getName(
const RecordDecl *Record) {
94 if (
const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
95 return Typedef->getName();
96 return Record->getName();
101template <
typename R,
typename T>
104 const SmallPtrSetImpl<const T *> &DeclsToInit) {
105 SmallVector<StringRef, 16> Names;
106 for (
const T *Decl : OrderedDecls) {
107 if (DeclsToInit.contains(Decl))
108 Names.emplace_back(
getName(Decl));
110 return llvm::join(Names.begin(), Names.end(),
", ");
115 return Lexer::getLocForEndOfToken(
Location, 0, Context.getSourceManager(),
116 Context.getLangOpts());
122enum class InitializerPlacement {
141struct InitializerInsertion {
142 InitializerInsertion(InitializerPlacement Placement,
143 const CXXCtorInitializer *Where)
144 : Placement(Placement), Where(Where) {}
146 SourceLocation getLocation(
const ASTContext &Context,
147 const CXXConstructorDecl &Constructor)
const {
148 assert((Where !=
nullptr || Placement == InitializerPlacement::New) &&
149 "Location should be relative to an existing initializer or this "
150 "insertion represents a new initializer list.");
153 case InitializerPlacement::New:
155 Constructor.getBody()->getBeginLoc(),
156 Context.getSourceManager(), Context.getLangOpts())
159 case InitializerPlacement::Before:
161 Where->getSourceRange().getBegin(),
162 Context.getSourceManager(), Context.getLangOpts())
165 case InitializerPlacement::After:
172 std::string codeToInsert()
const {
173 assert(!Initializers.empty() &&
"No initializers to insert");
175 llvm::raw_string_ostream Stream(Code);
176 const std::string Joined =
177 llvm::join(Initializers.begin(), Initializers.end(),
"(), ");
179 case InitializerPlacement::New:
180 Stream <<
" : " << Joined <<
"()";
182 case InitializerPlacement::Before:
183 Stream <<
" " << Joined <<
"(),";
185 case InitializerPlacement::After:
186 Stream <<
", " << Joined <<
"()";
192 InitializerPlacement Placement;
193 const CXXCtorInitializer *Where;
194 SmallVector<std::string, 4> Initializers;
201 if (
const auto *RT = Type->getAsCanonical<RecordType>())
202 return RT->getDecl();
206template <
typename R,
typename T>
207static SmallVector<InitializerInsertion, 16>
209 const R &OrderedDecls,
210 const SmallPtrSetImpl<const T *> &DeclsToInit) {
211 SmallVector<InitializerInsertion, 16> Insertions;
212 Insertions.emplace_back(InitializerPlacement::New,
nullptr);
214 typename R::const_iterator Decl = std::begin(OrderedDecls);
215 for (
const CXXCtorInitializer *Init : Inits) {
216 if (Init->isWritten()) {
217 if (Insertions.size() == 1)
218 Insertions.emplace_back(InitializerPlacement::Before, Init);
222 const auto *InitDecl =
223 Init->isAnyMemberInitializer()
224 ?
static_cast<const NamedDecl *
>(Init->getAnyMember())
225 : Init->getBaseClass()->getAsCXXRecordDecl();
228 for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
229 if (
const auto *D = dyn_cast<T>(*Decl)) {
230 if (DeclsToInit.contains(D))
231 Insertions.back().Initializers.emplace_back(
getName(D));
235 Insertions.emplace_back(InitializerPlacement::After, Init);
240 for (; Decl != std::end(OrderedDecls); ++Decl) {
241 if (
const auto *D = dyn_cast<T>(*Decl)) {
242 if (DeclsToInit.contains(D))
243 Insertions.back().Initializers.emplace_back(
getName(D));
253 SmallVectorImpl<const NamedDecl *> &Decls) {
255 for (
const auto &Base : ClassDecl.bases()) {
258 Decls.emplace_back(Decl);
262 [&](
const FieldDecl *F) { Decls.push_back(F); });
267 DiagnosticBuilder &Diag,
268 const CXXConstructorDecl *Ctor,
269 const SmallPtrSetImpl<const T *> &DeclsToInit) {
271 if (Ctor->getBeginLoc().isMacroID())
274 SmallVector<const NamedDecl *, 16> OrderedDecls;
277 for (
const auto &Insertion :
279 if (!Insertion.Initializers.empty())
280 Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
281 Insertion.codeToInsert());
288 IgnoreArrays(Options.get(
"IgnoreArrays", false)),
289 UseAssignment(Options.get(
"UseAssignment", false)) {}
292 auto IsUserProvidedNonDelegatingConstructor =
293 allOf(isUserProvided(), unless(isInstantiated()),
294 unless(isDelegatingConstructor()),
295 ofClass(cxxRecordDecl().bind(
"parent")),
296 unless(hasAnyConstructorInitializer(cxxCtorInitializer(
297 isWritten(), unless(isMemberInitializer()),
299 qualType(hasDeclaration(equalsBoundNode(
"parent")))))))));
301 auto IsNonTrivialDefaultConstructor = allOf(
302 isDefaultConstructor(), unless(isUserProvided()),
303 hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
305 cxxConstructorDecl(isDefinition(),
306 anyOf(IsUserProvidedNonDelegatingConstructor,
307 IsNonTrivialDefaultConstructor))
315 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
316 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
317 unless(isImplicit()))),
318 unless(has(cxxConstructorDecl()))),
319 unless(isTriviallyDefaultConstructible()))
323 auto HasDefaultConstructor = hasInitializer(
324 cxxConstructExpr(unless(requiresZeroInitialization()),
325 hasDeclaration(cxxConstructorDecl(
326 isDefaultConstructor(), unless(isUserProvided())))));
328 varDecl(isDefinition(), HasDefaultConstructor,
329 hasAutomaticStorageDuration(),
330 hasType(recordDecl(has(fieldDecl()),
331 isTriviallyDefaultConstructible())))
337 if (
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(
"ctor")) {
339 if (!Ctor->getBody())
343 if (Ctor->isExplicitlyDefaulted() && !Ctor->isDefaultConstructor())
345 checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
346 checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
347 }
else if (
const auto *Record =
348 Result.Nodes.getNodeAs<CXXRecordDecl>(
"record")) {
349 assert(Record->hasDefaultConstructor() &&
350 "Matched record should have a default constructor");
351 checkMissingMemberInitializer(*Result.Context, *Record,
nullptr);
352 checkMissingBaseClassInitializer(*Result.Context, *Record,
nullptr);
353 }
else if (
const auto *Var = Result.Nodes.getNodeAs<VarDecl>(
"var")) {
354 checkUninitializedTrivialType(*Result.Context, Var);
359 Options.store(Opts,
"IgnoreArrays", IgnoreArrays);
360 Options.store(Opts,
"UseAssignment", UseAssignment);
366 if (T->isIncompleteArrayType())
369 while (
const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
370 if (!ArrayT->getSize())
373 T = ArrayT->getElementType();
379static bool isEmpty(
const ASTContext &Context,
const QualType &Type) {
380 if (
const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
381 return ClassDecl->isEmpty();
387 static constexpr llvm::StringLiteral DefaultInitializer =
"{}";
389 return DefaultInitializer;
391 if (QT->isPointerType())
394 const auto *BT = dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
396 return DefaultInitializer;
398 switch (BT->getKind()) {
399 case BuiltinType::Bool:
401 case BuiltinType::Float:
403 case BuiltinType::Double:
405 case BuiltinType::LongDouble:
407 case BuiltinType::SChar:
408 case BuiltinType::Char_S:
409 case BuiltinType::WChar_S:
410 case BuiltinType::Char16:
411 case BuiltinType::Char32:
412 case BuiltinType::Short:
413 case BuiltinType::Int:
415 case BuiltinType::UChar:
416 case BuiltinType::Char_U:
417 case BuiltinType::WChar_U:
418 case BuiltinType::UShort:
419 case BuiltinType::UInt:
421 case BuiltinType::Long:
423 case BuiltinType::ULong:
425 case BuiltinType::LongLong:
427 case BuiltinType::ULongLong:
431 return DefaultInitializer;
438 SmallPtrSetImpl<const FieldDecl *> &FieldsToInit) {
439 bool AnyMemberHasInitPerUnion =
false;
441 Record, Record.fields(), AnyMemberHasInitPerUnion,
442 [&](
const FieldDecl *F) {
443 if (IgnoreArrays && F->getType()->isArrayType())
445 if (F->hasInClassInitializer() && F->getParent()->isUnion()) {
446 AnyMemberHasInitPerUnion = true;
447 removeFieldInitialized(F, FieldsToInit);
449 if (!F->hasInClassInitializer() &&
452 !
isEmpty(Context, F->getType()) && !F->isUnnamedBitField() &&
453 !AnyMemberHasInitPerUnion)
454 FieldsToInit.insert(F);
458void ProTypeMemberInitCheck::checkMissingMemberInitializer(
459 ASTContext &Context,
const CXXRecordDecl &ClassDecl,
460 const CXXConstructorDecl *Ctor) {
461 const bool IsUnion = ClassDecl.isUnion();
463 if (IsUnion && ClassDecl.hasInClassInitializer())
467 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
468 computeFieldsToInit(Context, ClassDecl, IgnoreArrays, FieldsToInit);
469 if (FieldsToInit.empty())
473 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
476 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
479 removeFieldInitialized(Init->getAnyMember(), FieldsToInit);
487 SmallVector<const FieldDecl *, 16> OrderedFields;
489 [&](
const FieldDecl *F) { OrderedFields.push_back(F); });
493 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
494 forEachField(ClassDecl, FieldsToInit, [&](
const FieldDecl *F) {
495 if (HasRecordClassMemberSet.insert(F).second)
496 AllFieldsToInit.insert(F);
498 if (FieldsToInit.empty())
501 DiagnosticBuilder Diag =
502 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
503 "%select{|union }0constructor %select{does not|should}0 initialize "
504 "%select{|one of }0these fields: %1")
507 if (AllFieldsToInit.empty())
512 if (Ctor && Ctor->getBeginLoc().isMacroID())
517 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
518 bool AnyMemberHasInitPerUnion =
false;
520 AnyMemberHasInitPerUnion, [&](
const FieldDecl *F) {
521 if (!FieldsToInit.contains(F))
527 if (F->getType()->isEnumeralType() ||
528 (!getLangOpts().CPlusPlus20 && F->isBitField()))
530 FieldsToFix.insert(F);
531 AnyMemberHasInitPerUnion = true;
533 if (FieldsToFix.empty())
537 if (Context.getLangOpts().CPlusPlus11) {
538 for (
const FieldDecl *Field : FieldsToFix) {
539 Diag << FixItHint::CreateInsertion(
549void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
550 const ASTContext &Context,
const CXXRecordDecl &ClassDecl,
551 const CXXConstructorDecl *Ctor) {
553 SmallVector<const RecordDecl *, 4> AllBases;
554 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
555 for (
const CXXBaseSpecifier &Base : ClassDecl.bases()) {
557 AllBases.emplace_back(BaseClassDecl);
558 if (!BaseClassDecl->field_empty() &&
561 BasesToInit.insert(BaseClassDecl);
565 if (BasesToInit.empty())
570 if (Ctor->isImplicit())
573 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
574 if (Init->isBaseInitializer() && Init->isWritten())
575 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
579 if (BasesToInit.empty())
582 DiagnosticBuilder Diag =
583 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
584 "constructor does not initialize these bases: %0")
591void ProTypeMemberInitCheck::checkUninitializedTrivialType(
592 const ASTContext &Context,
const VarDecl *Var) {
594 const CXXRecordDecl *
Record = Var->getType()->getAsCXXRecordDecl();
598 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
601 if (FieldsToInit.empty())
604 const DiagnosticBuilder Diag =
605 diag(Var->getBeginLoc(),
"uninitialized record type: %0") << Var;
607 Diag << FixItHint::CreateInsertion(
609 Context.getLangOpts().CPlusPlus11 ?
"{}" :
" = {}");
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
ProTypeMemberInitCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
llvm::SmallVector< uint64_t, 1024 > Record
static SourceLocation getLocationForEndOfToken(const ASTContext &Context, SourceLocation Location)
static void removeFieldsInitializedInBody(const Stmt &Stmt, ASTContext &Context, SmallPtrSetImpl< const FieldDecl * > &FieldDecls)
static std::string toCommaSeparatedString(const R &OrderedDecls, const SmallPtrSetImpl< const T * > &DeclsToInit)
static llvm::StringLiteral getInitializer(QualType QT, bool UseAssignment)
static bool isEmpty(const ASTContext &Context, const QualType &Type)
static void removeFieldInitialized(const FieldDecl *M, SmallPtrSetImpl< const FieldDecl * > &FieldDecls)
static StringRef getName(const FieldDecl *Field)
static void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag, const CXXConstructorDecl *Ctor, const SmallPtrSetImpl< const T * > &DeclsToInit)
static bool isIncompleteOrZeroLengthArrayType(const ASTContext &Context, QualType T)
static void forEachFieldWithFilter(const RecordDecl &Record, const T &Fields, bool &AnyMemberHasInitPerUnion, const Func &Fn)
static void getInitializationsInOrder(const CXXRecordDecl &ClassDecl, SmallVectorImpl< const NamedDecl * > &Decls)
static const RecordDecl * getCanonicalRecordDecl(const QualType &Type)
static void forEachField(const RecordDecl &Record, const T &Fields, const Func &Fn)
static SmallVector< InitializerInsertion, 16 > computeInsertions(const CXXConstructorDecl::init_const_range &Inits, const R &OrderedDecls, const SmallPtrSetImpl< const T * > &DeclsToInit)
static void computeFieldsToInit(const ASTContext &Context, const RecordDecl &Record, bool IgnoreArrays, SmallPtrSetImpl< const FieldDecl * > &FieldsToInit)
AST_MATCHER(BinaryOperator, isRelationalOperator)
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context)
Returns true if Type is trivially default constructible.
llvm::StringMap< ClangTidyValue > OptionMap