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())
81 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
83 match(findAll(binaryOperator(
85 hasLHS(memberExpr(member(fieldDecl().bind(
"fieldDecl")))))),
87 for (
const auto &Match : Matches)
91static StringRef
getName(
const FieldDecl *Field) {
return Field->getName(); }
93static StringRef
getName(
const RecordDecl *Record) {
95 if (
const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
96 return Typedef->getName();
97 return Record->getName();
102template <
typename R,
typename T>
105 const SmallPtrSetImpl<const T *> &DeclsToInit) {
107 for (
const T *Decl : OrderedDecls)
108 if (DeclsToInit.contains(Decl))
109 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(), Context.getSourceManager(),
156 Context.getLangOpts());
157 Location = Tok ? Tok->getLocation() : SourceLocation{};
160 case InitializerPlacement::Before: {
162 Where->getSourceRange().getBegin(), Context.getSourceManager(),
163 Context.getLangOpts());
164 Location = Tok ? Tok->getLocation() : SourceLocation{};
167 case InitializerPlacement::After:
168 Location = Where->getRParenLoc();
174 std::string codeToInsert()
const {
175 assert(!Initializers.empty() &&
"No initializers to insert");
177 llvm::raw_string_ostream Stream(Code);
178 const std::string Joined =
179 llvm::join(Initializers.begin(), Initializers.end(),
"(), ");
181 case InitializerPlacement::New:
182 Stream <<
" : " << Joined <<
"()";
184 case InitializerPlacement::Before:
185 Stream <<
" " << Joined <<
"(),";
187 case InitializerPlacement::After:
188 Stream <<
", " << Joined <<
"()";
194 InitializerPlacement Placement;
195 const CXXCtorInitializer *Where;
196 SmallVector<std::string, 4> Initializers;
203 if (
const auto *RT = Type->getAsCanonical<RecordType>())
204 return RT->getDecl();
208template <
typename R,
typename T>
211 const R &OrderedDecls,
212 const SmallPtrSetImpl<const T *> &DeclsToInit) {
214 Insertions.emplace_back(InitializerPlacement::New,
nullptr);
216 typename R::const_iterator Decl = std::begin(OrderedDecls);
217 for (
const CXXCtorInitializer *Init : Inits) {
218 if (Init->isWritten()) {
219 if (Insertions.size() == 1)
220 Insertions.emplace_back(InitializerPlacement::Before, Init);
224 const auto *InitDecl =
225 Init->isAnyMemberInitializer()
226 ?
static_cast<const NamedDecl *
>(Init->getAnyMember())
227 : Init->getBaseClass()->getAsCXXRecordDecl();
230 for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
231 if (
const auto *D = dyn_cast<T>(*Decl)) {
232 if (DeclsToInit.contains(D))
233 Insertions.back().Initializers.emplace_back(
getName(D));
237 Insertions.emplace_back(InitializerPlacement::After, Init);
242 for (; Decl != std::end(OrderedDecls); ++Decl) {
243 if (
const auto *D = dyn_cast<T>(*Decl)) {
244 if (DeclsToInit.contains(D))
245 Insertions.back().Initializers.emplace_back(
getName(D));
257 for (
const auto &Base : ClassDecl.bases()) {
260 Decls.emplace_back(Decl);
263 [&](
const FieldDecl *F) { Decls.push_back(F); });
268 DiagnosticBuilder &Diag,
269 const CXXConstructorDecl *Ctor,
270 const SmallPtrSetImpl<const T *> &DeclsToInit) {
272 if (Ctor->getBeginLoc().isMacroID())
278 for (
const auto &Insertion :
280 if (!Insertion.Initializers.empty())
281 Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
282 Insertion.codeToInsert());
289 IgnoreArrays(Options.get(
"IgnoreArrays", false)),
290 UseAssignment(Options.get(
"UseAssignment", false)) {}
293 auto IsUserProvidedNonDelegatingConstructor =
294 allOf(isUserProvided(), unless(isInstantiated()),
295 unless(isDelegatingConstructor()),
296 ofClass(cxxRecordDecl().bind(
"parent")),
297 unless(hasAnyConstructorInitializer(cxxCtorInitializer(
298 isWritten(), unless(isMemberInitializer()),
300 qualType(hasDeclaration(equalsBoundNode(
"parent")))))))));
302 auto IsNonTrivialDefaultConstructor = allOf(
303 isDefaultConstructor(), unless(isUserProvided()),
304 hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
306 cxxConstructorDecl(isDefinition(),
307 anyOf(IsUserProvidedNonDelegatingConstructor,
308 IsNonTrivialDefaultConstructor))
316 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
317 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
318 unless(isImplicit()))),
319 unless(has(cxxConstructorDecl()))),
320 unless(isTriviallyDefaultConstructible()))
324 auto HasDefaultConstructor = hasInitializer(
325 cxxConstructExpr(unless(requiresZeroInitialization()),
326 hasDeclaration(cxxConstructorDecl(
327 isDefaultConstructor(), unless(isUserProvided())))));
329 varDecl(isDefinition(), HasDefaultConstructor,
330 hasAutomaticStorageDuration(),
331 hasType(recordDecl(has(fieldDecl()),
332 isTriviallyDefaultConstructible())))
338 if (
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(
"ctor")) {
340 if (!Ctor->getBody())
344 if (Ctor->isExplicitlyDefaulted() && !Ctor->isDefaultConstructor())
346 checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
347 checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
348 }
else if (
const auto *Record =
349 Result.Nodes.getNodeAs<CXXRecordDecl>(
"record")) {
350 assert(Record->hasDefaultConstructor() &&
351 "Matched record should have a default constructor");
352 checkMissingMemberInitializer(*Result.Context, *Record,
nullptr);
353 checkMissingBaseClassInitializer(*Result.Context, *Record,
nullptr);
354 }
else if (
const auto *Var = Result.Nodes.getNodeAs<VarDecl>(
"var")) {
355 checkUninitializedTrivialType(*Result.Context, Var);
360 Options.store(Opts,
"IgnoreArrays", IgnoreArrays);
361 Options.store(Opts,
"UseAssignment", UseAssignment);
367 if (T->isIncompleteArrayType())
370 while (
const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
371 if (!ArrayT->getSize())
374 T = ArrayT->getElementType();
380static bool isEmpty(
const ASTContext &Context,
const QualType &Type) {
381 if (
const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl())
382 return ClassDecl->isEmpty();
387 static constexpr StringRef 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());
578 if (BasesToInit.empty())
581 DiagnosticBuilder Diag =
582 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
583 "constructor does not initialize these bases: %0")
590void ProTypeMemberInitCheck::checkUninitializedTrivialType(
591 const ASTContext &Context,
const VarDecl *Var) {
593 const CXXRecordDecl *
Record = Var->getType()->getAsCXXRecordDecl();
597 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
600 if (FieldsToInit.empty())
603 const DiagnosticBuilder Diag =
604 diag(Var->getBeginLoc(),
"uninitialized record type: %0") << Var;
606 Diag << FixItHint::CreateInsertion(
608 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)
std::optional< Token > getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or std::nullopt if not found.
bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context)
Returns true if Type is trivially default constructible.
llvm::StringMap< ClangTidyValue > OptionMap