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"
20using llvm::SmallPtrSet;
21using llvm::SmallPtrSetImpl;
28 return Node.hasDefaultConstructor();
33template <
typename T,
typename Func>
34void 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);
45template <
typename T,
typename Func>
46void 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);
57 if (
Record.isUnion() && AnyMemberHasInitPerUnion)
62void removeFieldInitialized(
const FieldDecl *
M,
63 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
64 const RecordDecl *R =
M->getParent();
65 if (R && R->isUnion()) {
67 for (
const auto *F : R->fields())
73void removeFieldsInitializedInBody(
74 const Stmt &Stmt, ASTContext &Context,
75 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
77 match(findAll(binaryOperator(
79 hasLHS(memberExpr(member(fieldDecl().bind(
"fieldDecl")))))),
81 for (
const auto &Match : Matches)
82 removeFieldInitialized(Match.getNodeAs<FieldDecl>(
"fieldDecl"), FieldDecls);
85StringRef getName(
const FieldDecl *Field) {
return Field->getName(); }
87StringRef getName(
const RecordDecl *Record) {
89 if (
const TypedefNameDecl *Typedef =
Record->getTypedefNameForAnonDecl())
96template <
typename R,
typename T>
98toCommaSeparatedString(
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));
105 return llvm::join(Names.begin(), Names.end(),
", ");
108SourceLocation getLocationForEndOfToken(
const ASTContext &Context,
110 return Lexer::getLocForEndOfToken(
Location, 0, Context.getSourceManager(),
111 Context.getLangOpts());
115enum class InitializerPlacement {
134struct InitializerInsertion {
135 InitializerInsertion(InitializerPlacement Placement,
136 const CXXCtorInitializer *Where)
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.");
146 case InitializerPlacement::New:
149 Context.getSourceManager(), Context.getLangOpts())
152 case InitializerPlacement::Before:
154 Where->getSourceRange().getBegin(),
155 Context.getSourceManager(), Context.getLangOpts())
158 case InitializerPlacement::After:
162 return getLocationForEndOfToken(Context,
Location);
165 std::string codeToInsert()
const {
166 assert(!
Initializers.empty() &&
"No initializers to insert");
168 llvm::raw_string_ostream Stream(
Code);
172 case InitializerPlacement::New:
173 Stream <<
" : " << Joined <<
"()";
175 case InitializerPlacement::Before:
176 Stream <<
" " << Joined <<
"(),";
178 case InitializerPlacement::After:
179 Stream <<
", " << Joined <<
"()";
191const RecordDecl *getCanonicalRecordDecl(
const QualType &
Type) {
192 if (
const auto *RT =
Type.getCanonicalType()->getAs<RecordType>())
193 return RT->getDecl();
197template <
typename R,
typename T>
198SmallVector<InitializerInsertion, 16>
199computeInsertions(
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);
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);
213 const auto *InitDecl =
214 Init->isAnyMemberInitializer()
215 ?
static_cast<const NamedDecl *
>(Init->getAnyMember())
216 : Init->getBaseClass()->getAsCXXRecordDecl();
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));
226 Insertions.emplace_back(InitializerPlacement::After, Init);
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));
242void getInitializationsInOrder(
const CXXRecordDecl &ClassDecl,
243 SmallVectorImpl<const NamedDecl *> &Decls) {
245 for (
const auto &Base : ClassDecl.bases()) {
247 if (
const NamedDecl *
Decl = getCanonicalRecordDecl(Base.getType())) {
248 Decls.emplace_back(
Decl);
251 forEachField(ClassDecl, ClassDecl.fields(),
252 [&](
const FieldDecl *F) { Decls.push_back(F); });
256void fixInitializerList(
const ASTContext &Context, DiagnosticBuilder &Diag,
257 const CXXConstructorDecl *Ctor,
258 const SmallPtrSetImpl<const T *> &DeclsToInit) {
260 if (Ctor->getBeginLoc().isMacroID())
263 SmallVector<const NamedDecl *, 16> OrderedDecls;
264 getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
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());
279 IgnoreArrays(Options.get(
"IgnoreArrays", false)),
280 UseAssignment(Options.getLocalOrGlobal(
"UseAssignment", false)) {}
283 auto IsUserProvidedNonDelegatingConstructor =
284 allOf(isUserProvided(), unless(isInstantiated()),
285 unless(isDelegatingConstructor()),
286 ofClass(cxxRecordDecl().bind(
"parent")),
287 unless(hasAnyConstructorInitializer(cxxCtorInitializer(
288 isWritten(), unless(isMemberInitializer()),
290 qualType(hasDeclaration(equalsBoundNode(
"parent")))))))));
292 auto IsNonTrivialDefaultConstructor = allOf(
293 isDefaultConstructor(), unless(isUserProvided()),
294 hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
296 cxxConstructorDecl(isDefinition(),
297 anyOf(IsUserProvidedNonDelegatingConstructor,
298 IsNonTrivialDefaultConstructor))
306 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
307 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
308 unless(isImplicit()))),
309 unless(has(cxxConstructorDecl()))),
310 unless(isTriviallyDefaultConstructible()))
314 auto HasDefaultConstructor = hasInitializer(
315 cxxConstructExpr(unless(requiresZeroInitialization()),
316 hasDeclaration(cxxConstructorDecl(
317 isDefaultConstructor(), unless(isUserProvided())))));
319 varDecl(isDefinition(), HasDefaultConstructor,
320 hasAutomaticStorageDuration(),
321 hasType(recordDecl(has(fieldDecl()),
322 isTriviallyDefaultConstructible())))
328 if (
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(
"ctor")) {
330 if (!Ctor->getBody())
334 if (Ctor->isExplicitlyDefaulted() && !Ctor->isDefaultConstructor())
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);
356 if (T->isIncompleteArrayType())
359 while (
const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
360 if (!ArrayT->getSize())
363 T = ArrayT->getElementType();
370 if (
const CXXRecordDecl *ClassDecl =
Type->getAsCXXRecordDecl()) {
371 return ClassDecl->isEmpty();
377 const char *DefaultInitializer =
"{}";
379 return DefaultInitializer;
381 if (QT->isPointerType())
384 const auto *BT = dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
386 return DefaultInitializer;
388 switch (BT->getKind()) {
389 case BuiltinType::Bool:
391 case BuiltinType::Float:
393 case BuiltinType::Double:
395 case BuiltinType::LongDouble:
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:
405 case BuiltinType::UChar:
406 case BuiltinType::Char_U:
407 case BuiltinType::WChar_U:
408 case BuiltinType::UShort:
409 case BuiltinType::UInt:
411 case BuiltinType::Long:
413 case BuiltinType::ULong:
415 case BuiltinType::LongLong:
417 case BuiltinType::ULongLong:
421 return DefaultInitializer;
425void ProTypeMemberInitCheck::checkMissingMemberInitializer(
426 ASTContext &Context,
const CXXRecordDecl &ClassDecl,
427 const CXXConstructorDecl *Ctor) {
428 bool IsUnion = ClassDecl.isUnion();
430 if (IsUnion && ClassDecl.hasInClassInitializer())
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())
440 if (F->hasInClassInitializer() && F->getParent()->isUnion()) {
441 AnyMemberHasInitPerUnion = true;
442 removeFieldInitialized(F, FieldsToInit);
444 if (!F->hasInClassInitializer() &&
447 !
isEmpty(Context, F->getType()) && !F->isUnnamedBitField() &&
448 !AnyMemberHasInitPerUnion)
449 FieldsToInit.insert(F);
451 if (FieldsToInit.empty())
455 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
458 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
461 removeFieldInitialized(Init->getAnyMember(), FieldsToInit);
464 removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
469 SmallVector<const FieldDecl *, 16> OrderedFields;
470 forEachField(ClassDecl, ClassDecl.fields(),
471 [&](
const FieldDecl *F) { OrderedFields.push_back(F); });
475 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
476 forEachField(ClassDecl, FieldsToInit, [&](
const FieldDecl *F) {
477 if (!HasRecordClassMemberSet.contains(F)) {
478 AllFieldsToInit.insert(F);
479 HasRecordClassMemberSet.insert(F);
482 if (FieldsToInit.empty())
485 DiagnosticBuilder Diag =
486 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
487 "%select{|union }0constructor %select{does not|should}0 initialize "
488 "%select{|one of }0these fields: %1")
489 << IsUnion << toCommaSeparatedString(OrderedFields, FieldsToInit);
491 if (AllFieldsToInit.empty())
496 if (Ctor && Ctor->getBeginLoc().isMacroID())
501 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
502 AnyMemberHasInitPerUnion =
false;
503 forEachFieldWithFilter(ClassDecl, ClassDecl.fields(),
504 AnyMemberHasInitPerUnion, [&](
const FieldDecl *F) {
505 if (!FieldsToInit.count(F))
510 if (F->getType()->isEnumeralType() ||
511 (!getLangOpts().CPlusPlus20 && F->isBitField()))
513 FieldsToFix.insert(F);
514 AnyMemberHasInitPerUnion = true;
516 if (FieldsToFix.empty())
520 if (Context.getLangOpts().CPlusPlus11) {
521 for (
const FieldDecl *Field : FieldsToFix) {
522 Diag << FixItHint::CreateInsertion(
523 getLocationForEndOfToken(Context,
Field->getSourceRange().getEnd()),
528 fixInitializerList(Context, Diag, Ctor, FieldsToFix);
532void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
533 const ASTContext &Context,
const CXXRecordDecl &ClassDecl,
534 const CXXConstructorDecl *Ctor) {
537 SmallVector<const RecordDecl *, 4> AllBases;
538 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
539 for (
const CXXBaseSpecifier &Base : ClassDecl.bases()) {
540 if (
const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
541 AllBases.emplace_back(BaseClassDecl);
542 if (!BaseClassDecl->field_empty() &&
545 BasesToInit.insert(BaseClassDecl);
549 if (BasesToInit.empty())
554 if (Ctor->isImplicit())
557 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
558 if (Init->isBaseInitializer() && Init->isWritten())
559 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
563 if (BasesToInit.empty())
566 DiagnosticBuilder Diag =
567 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
568 "constructor does not initialize these bases: %0")
569 << toCommaSeparatedString(AllBases, BasesToInit);
572 fixInitializerList(Context, Diag, Ctor, BasesToInit);
575void ProTypeMemberInitCheck::checkUninitializedTrivialType(
576 const ASTContext &Context,
const VarDecl *Var) {
577 DiagnosticBuilder Diag =
578 diag(Var->getBeginLoc(),
"uninitialized record type: %0") << Var;
580 Diag << FixItHint::CreateInsertion(
581 getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
582 Context.getLangOpts().CPlusPlus11 ?
"{}" :
" = {}");
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
InitializerPlacement Placement
SmallVector< std::string, 4 > Initializers
const CXXCtorInitializer * Where
::clang::DynTypedNode Node
const google::protobuf::Message & M
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
ProTypeMemberInitCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
llvm::SmallVector< uint64_t, 1024 > Record
static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T)
AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor)
static const char * getInitializer(QualType QT, bool UseAssignment)
static bool isEmpty(ASTContext &Context, const QualType &Type)
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