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);
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);
365 if (T->isIncompleteArrayType())
368 while (
const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
369 if (!ArrayT->getSize())
372 T = ArrayT->getElementType();
378static bool isEmpty(ASTContext &Context,
const QualType &Type) {
379 if (
const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
380 return ClassDecl->isEmpty();
386 static constexpr llvm::StringLiteral DefaultInitializer =
"{}";
388 return DefaultInitializer;
390 if (QT->isPointerType())
393 const auto *BT = dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
395 return DefaultInitializer;
397 switch (BT->getKind()) {
398 case BuiltinType::Bool:
400 case BuiltinType::Float:
402 case BuiltinType::Double:
404 case BuiltinType::LongDouble:
406 case BuiltinType::SChar:
407 case BuiltinType::Char_S:
408 case BuiltinType::WChar_S:
409 case BuiltinType::Char16:
410 case BuiltinType::Char32:
411 case BuiltinType::Short:
412 case BuiltinType::Int:
414 case BuiltinType::UChar:
415 case BuiltinType::Char_U:
416 case BuiltinType::WChar_U:
417 case BuiltinType::UShort:
418 case BuiltinType::UInt:
420 case BuiltinType::Long:
422 case BuiltinType::ULong:
424 case BuiltinType::LongLong:
426 case BuiltinType::ULongLong:
430 return DefaultInitializer;
434void ProTypeMemberInitCheck::checkMissingMemberInitializer(
435 ASTContext &Context,
const CXXRecordDecl &ClassDecl,
436 const CXXConstructorDecl *Ctor) {
437 bool IsUnion = ClassDecl.isUnion();
439 if (IsUnion && ClassDecl.hasInClassInitializer())
443 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
444 bool AnyMemberHasInitPerUnion =
false;
446 ClassDecl, ClassDecl.fields(), AnyMemberHasInitPerUnion,
447 [&](
const FieldDecl *F) {
448 if (IgnoreArrays && F->getType()->isArrayType())
450 if (F->hasInClassInitializer() && F->getParent()->isUnion()) {
451 AnyMemberHasInitPerUnion = true;
452 removeFieldInitialized(F, FieldsToInit);
454 if (!F->hasInClassInitializer() &&
457 !
isEmpty(Context, F->getType()) && !F->isUnnamedBitField() &&
458 !AnyMemberHasInitPerUnion)
459 FieldsToInit.insert(F);
461 if (FieldsToInit.empty())
465 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
468 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
479 SmallVector<const FieldDecl *, 16> OrderedFields;
481 [&](
const FieldDecl *F) { OrderedFields.push_back(F); });
485 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
486 forEachField(ClassDecl, FieldsToInit, [&](
const FieldDecl *F) {
487 if (HasRecordClassMemberSet.insert(F).second)
488 AllFieldsToInit.insert(F);
490 if (FieldsToInit.empty())
493 DiagnosticBuilder Diag =
494 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
495 "%select{|union }0constructor %select{does not|should}0 initialize "
496 "%select{|one of }0these fields: %1")
499 if (AllFieldsToInit.empty())
504 if (Ctor && Ctor->getBeginLoc().isMacroID())
509 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
510 AnyMemberHasInitPerUnion =
false;
512 AnyMemberHasInitPerUnion, [&](
const FieldDecl *F) {
513 if (!FieldsToInit.contains(F))
519 if (F->getType()->isEnumeralType() ||
520 (!getLangOpts().CPlusPlus20 && F->isBitField()))
522 FieldsToFix.insert(F);
523 AnyMemberHasInitPerUnion = true;
525 if (FieldsToFix.empty())
529 if (Context.getLangOpts().CPlusPlus11) {
530 for (
const FieldDecl *Field : FieldsToFix) {
531 Diag << FixItHint::CreateInsertion(
541void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
542 const ASTContext &Context,
const CXXRecordDecl &ClassDecl,
543 const CXXConstructorDecl *Ctor) {
546 SmallVector<const RecordDecl *, 4> AllBases;
547 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
548 for (
const CXXBaseSpecifier &Base : ClassDecl.bases()) {
550 AllBases.emplace_back(BaseClassDecl);
551 if (!BaseClassDecl->field_empty() &&
554 BasesToInit.insert(BaseClassDecl);
558 if (BasesToInit.empty())
563 if (Ctor->isImplicit())
566 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
567 if (Init->isBaseInitializer() && Init->isWritten())
568 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
572 if (BasesToInit.empty())
575 DiagnosticBuilder Diag =
576 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
577 "constructor does not initialize these bases: %0")
584void ProTypeMemberInitCheck::checkUninitializedTrivialType(
585 const ASTContext &Context,
const VarDecl *Var) {
586 DiagnosticBuilder Diag =
587 diag(Var->getBeginLoc(),
"uninitialized record type: %0") << Var;
589 Diag << FixItHint::CreateInsertion(
591 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
static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T)
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 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 isEmpty(ASTContext &Context, const QualType &Type)
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)
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