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));
109 return llvm::join(Names.begin(), Names.end(),
", ");
114 return Lexer::getLocForEndOfToken(
Location, 0, Context.getSourceManager(),
115 Context.getLangOpts());
121enum class InitializerPlacement {
140struct InitializerInsertion {
141 InitializerInsertion(InitializerPlacement Placement,
142 const CXXCtorInitializer *Where)
143 : Placement(Placement), Where(Where) {}
145 SourceLocation getLocation(
const ASTContext &Context,
146 const CXXConstructorDecl &Constructor)
const {
147 assert((Where !=
nullptr || Placement == InitializerPlacement::New) &&
148 "Location should be relative to an existing initializer or this "
149 "insertion represents a new initializer list.");
152 case InitializerPlacement::New:
154 Constructor.getBody()->getBeginLoc(),
155 Context.getSourceManager(), Context.getLangOpts())
158 case InitializerPlacement::Before:
160 Where->getSourceRange().getBegin(),
161 Context.getSourceManager(), Context.getLangOpts())
164 case InitializerPlacement::After:
171 std::string codeToInsert()
const {
172 assert(!Initializers.empty() &&
"No initializers to insert");
174 llvm::raw_string_ostream Stream(Code);
175 const std::string Joined =
176 llvm::join(Initializers.begin(), Initializers.end(),
"(), ");
178 case InitializerPlacement::New:
179 Stream <<
" : " << Joined <<
"()";
181 case InitializerPlacement::Before:
182 Stream <<
" " << Joined <<
"(),";
184 case InitializerPlacement::After:
185 Stream <<
", " << Joined <<
"()";
191 InitializerPlacement Placement;
192 const CXXCtorInitializer *Where;
193 SmallVector<std::string, 4> Initializers;
200 if (
const auto *RT = Type->getAsCanonical<RecordType>())
201 return RT->getDecl();
205template <
typename R,
typename T>
206static SmallVector<InitializerInsertion, 16>
208 const R &OrderedDecls,
209 const SmallPtrSetImpl<const T *> &DeclsToInit) {
210 SmallVector<InitializerInsertion, 16> Insertions;
211 Insertions.emplace_back(InitializerPlacement::New,
nullptr);
213 typename R::const_iterator Decl = std::begin(OrderedDecls);
214 for (
const CXXCtorInitializer *Init : Inits) {
215 if (Init->isWritten()) {
216 if (Insertions.size() == 1)
217 Insertions.emplace_back(InitializerPlacement::Before, Init);
221 const auto *InitDecl =
222 Init->isAnyMemberInitializer()
223 ?
static_cast<const NamedDecl *
>(Init->getAnyMember())
224 : Init->getBaseClass()->getAsCXXRecordDecl();
227 for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
228 if (
const auto *D = dyn_cast<T>(*Decl)) {
229 if (DeclsToInit.contains(D))
230 Insertions.back().Initializers.emplace_back(
getName(D));
234 Insertions.emplace_back(InitializerPlacement::After, Init);
239 for (; Decl != std::end(OrderedDecls); ++Decl) {
240 if (
const auto *D = dyn_cast<T>(*Decl)) {
241 if (DeclsToInit.contains(D))
242 Insertions.back().Initializers.emplace_back(
getName(D));
252 SmallVectorImpl<const NamedDecl *> &Decls) {
254 for (
const auto &Base : ClassDecl.bases()) {
257 Decls.emplace_back(Decl);
260 [&](
const FieldDecl *F) { Decls.push_back(F); });
265 DiagnosticBuilder &Diag,
266 const CXXConstructorDecl *Ctor,
267 const SmallPtrSetImpl<const T *> &DeclsToInit) {
269 if (Ctor->getBeginLoc().isMacroID())
272 SmallVector<const NamedDecl *, 16> OrderedDecls;
275 for (
const auto &Insertion :
277 if (!Insertion.Initializers.empty())
278 Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
279 Insertion.codeToInsert());
286 IgnoreArrays(Options.get(
"IgnoreArrays", false)),
287 UseAssignment(Options.get(
"UseAssignment", false)) {}
290 auto IsUserProvidedNonDelegatingConstructor =
291 allOf(isUserProvided(), unless(isInstantiated()),
292 unless(isDelegatingConstructor()),
293 ofClass(cxxRecordDecl().bind(
"parent")),
294 unless(hasAnyConstructorInitializer(cxxCtorInitializer(
295 isWritten(), unless(isMemberInitializer()),
297 qualType(hasDeclaration(equalsBoundNode(
"parent")))))))));
299 auto IsNonTrivialDefaultConstructor = allOf(
300 isDefaultConstructor(), unless(isUserProvided()),
301 hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
303 cxxConstructorDecl(isDefinition(),
304 anyOf(IsUserProvidedNonDelegatingConstructor,
305 IsNonTrivialDefaultConstructor))
313 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
314 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
315 unless(isImplicit()))),
316 unless(has(cxxConstructorDecl()))),
317 unless(isTriviallyDefaultConstructible()))
321 auto HasDefaultConstructor = hasInitializer(
322 cxxConstructExpr(unless(requiresZeroInitialization()),
323 hasDeclaration(cxxConstructorDecl(
324 isDefaultConstructor(), unless(isUserProvided())))));
326 varDecl(isDefinition(), HasDefaultConstructor,
327 hasAutomaticStorageDuration(),
328 hasType(recordDecl(has(fieldDecl()),
329 isTriviallyDefaultConstructible())))
335 if (
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(
"ctor")) {
337 if (!Ctor->getBody())
341 if (Ctor->isExplicitlyDefaulted() && !Ctor->isDefaultConstructor())
343 checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
344 checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
345 }
else if (
const auto *Record =
346 Result.Nodes.getNodeAs<CXXRecordDecl>(
"record")) {
347 assert(Record->hasDefaultConstructor() &&
348 "Matched record should have a default constructor");
349 checkMissingMemberInitializer(*Result.Context, *Record,
nullptr);
350 checkMissingBaseClassInitializer(*Result.Context, *Record,
nullptr);
351 }
else if (
const auto *Var = Result.Nodes.getNodeAs<VarDecl>(
"var")) {
352 checkUninitializedTrivialType(*Result.Context, Var);
357 Options.store(Opts,
"IgnoreArrays", IgnoreArrays);
358 Options.store(Opts,
"UseAssignment", UseAssignment);
364 if (T->isIncompleteArrayType())
367 while (
const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
368 if (!ArrayT->getSize())
371 T = ArrayT->getElementType();
377static bool isEmpty(
const ASTContext &Context,
const QualType &Type) {
378 if (
const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl())
379 return ClassDecl->isEmpty();
384 static constexpr StringRef DefaultInitializer =
"{}";
386 return DefaultInitializer;
388 if (QT->isPointerType())
391 const auto *BT = dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
393 return DefaultInitializer;
395 switch (BT->getKind()) {
396 case BuiltinType::Bool:
398 case BuiltinType::Float:
400 case BuiltinType::Double:
402 case BuiltinType::LongDouble:
404 case BuiltinType::SChar:
405 case BuiltinType::Char_S:
406 case BuiltinType::WChar_S:
407 case BuiltinType::Char16:
408 case BuiltinType::Char32:
409 case BuiltinType::Short:
410 case BuiltinType::Int:
412 case BuiltinType::UChar:
413 case BuiltinType::Char_U:
414 case BuiltinType::WChar_U:
415 case BuiltinType::UShort:
416 case BuiltinType::UInt:
418 case BuiltinType::Long:
420 case BuiltinType::ULong:
422 case BuiltinType::LongLong:
424 case BuiltinType::ULongLong:
428 return DefaultInitializer;
435 SmallPtrSetImpl<const FieldDecl *> &FieldsToInit) {
436 bool AnyMemberHasInitPerUnion =
false;
438 Record, Record.fields(), AnyMemberHasInitPerUnion,
439 [&](
const FieldDecl *F) {
440 if (IgnoreArrays && F->getType()->isArrayType())
442 if (F->hasInClassInitializer() && F->getParent()->isUnion()) {
443 AnyMemberHasInitPerUnion = true;
444 removeFieldInitialized(F, FieldsToInit);
446 if (!F->hasInClassInitializer() &&
449 !
isEmpty(Context, F->getType()) && !F->isUnnamedBitField() &&
450 !AnyMemberHasInitPerUnion)
451 FieldsToInit.insert(F);
455void ProTypeMemberInitCheck::checkMissingMemberInitializer(
456 ASTContext &Context,
const CXXRecordDecl &ClassDecl,
457 const CXXConstructorDecl *Ctor) {
458 const bool IsUnion = ClassDecl.isUnion();
460 if (IsUnion && ClassDecl.hasInClassInitializer())
464 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
465 computeFieldsToInit(Context, ClassDecl, IgnoreArrays, FieldsToInit);
466 if (FieldsToInit.empty())
470 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
473 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
476 removeFieldInitialized(Init->getAnyMember(), FieldsToInit);
484 SmallVector<const FieldDecl *, 16> OrderedFields;
486 [&](
const FieldDecl *F) { OrderedFields.push_back(F); });
490 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
491 forEachField(ClassDecl, FieldsToInit, [&](
const FieldDecl *F) {
492 if (HasRecordClassMemberSet.insert(F).second)
493 AllFieldsToInit.insert(F);
495 if (FieldsToInit.empty())
498 DiagnosticBuilder Diag =
499 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
500 "%select{|union }0constructor %select{does not|should}0 initialize "
501 "%select{|one of }0these fields: %1")
504 if (AllFieldsToInit.empty())
509 if (Ctor && Ctor->getBeginLoc().isMacroID())
514 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
515 bool AnyMemberHasInitPerUnion =
false;
517 AnyMemberHasInitPerUnion, [&](
const FieldDecl *F) {
518 if (!FieldsToInit.contains(F))
524 if (F->getType()->isEnumeralType() ||
525 (!getLangOpts().CPlusPlus20 && F->isBitField()))
527 FieldsToFix.insert(F);
528 AnyMemberHasInitPerUnion = true;
530 if (FieldsToFix.empty())
534 if (Context.getLangOpts().CPlusPlus11) {
535 for (
const FieldDecl *Field : FieldsToFix) {
536 Diag << FixItHint::CreateInsertion(
546void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
547 const ASTContext &Context,
const CXXRecordDecl &ClassDecl,
548 const CXXConstructorDecl *Ctor) {
550 SmallVector<const RecordDecl *, 4> AllBases;
551 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
552 for (
const CXXBaseSpecifier &Base : ClassDecl.bases()) {
554 AllBases.emplace_back(BaseClassDecl);
555 if (!BaseClassDecl->field_empty() &&
558 BasesToInit.insert(BaseClassDecl);
562 if (BasesToInit.empty())
567 if (Ctor->isImplicit())
570 for (
const CXXCtorInitializer *Init : Ctor->inits())
571 if (Init->isBaseInitializer() && Init->isWritten())
572 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
575 if (BasesToInit.empty())
578 DiagnosticBuilder Diag =
579 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
580 "constructor does not initialize these bases: %0")
587void ProTypeMemberInitCheck::checkUninitializedTrivialType(
588 const ASTContext &Context,
const VarDecl *Var) {
590 const CXXRecordDecl *
Record = Var->getType()->getAsCXXRecordDecl();
594 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
597 if (FieldsToInit.empty())
600 const DiagnosticBuilder Diag =
601 diag(Var->getBeginLoc(),
"uninitialized record type: %0") << Var;
603 Diag << FixItHint::CreateInsertion(
605 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 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 StringRef getInitializer(QualType QT, bool UseAssignment)
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