11 #include "../utils/LexerUtils.h"
12 #include "../utils/Matchers.h"
13 #include "../utils/TypeTraits.h"
14 #include "clang/AST/ASTContext.h"
15 #include "clang/ASTMatchers/ASTMatchFinder.h"
16 #include "clang/Lex/Lexer.h"
17 #include "llvm/ADT/SmallPtrSet.h"
19 using namespace clang::ast_matchers;
20 using namespace clang::tidy::matchers;
21 using llvm::SmallPtrSet;
22 using llvm::SmallPtrSetImpl;
26 namespace cppcoreguidelines {
31 return Node.hasDefaultConstructor();
38 template <
typename T,
typename Func>
39 void forEachField(
const RecordDecl &Record,
const T &Fields,
40 bool OneFieldPerUnion, Func &&Fn) {
41 for (
const FieldDecl *F : Fields) {
42 if (F->isAnonymousStructOrUnion()) {
43 if (
const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl())
44 forEachField(*R, R->fields(), OneFieldPerUnion, Fn);
49 if (OneFieldPerUnion && Record.isUnion())
54 void removeFieldsInitializedInBody(
55 const Stmt &Stmt, ASTContext &
Context,
56 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
58 match(findAll(binaryOperator(
60 hasLHS(memberExpr(member(fieldDecl().bind(
"fieldDecl")))))),
62 for (
const auto &Match : Matches)
63 FieldDecls.erase(Match.getNodeAs<FieldDecl>(
"fieldDecl"));
66 StringRef getName(
const FieldDecl *Field) {
return Field->getName(); }
68 StringRef getName(
const RecordDecl *Record) {
70 if (
const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
71 return Typedef->getName();
72 return Record->getName();
77 template <
typename R,
typename T>
79 toCommaSeparatedString(
const R &OrderedDecls,
80 const SmallPtrSetImpl<const T *> &DeclsToInit) {
81 SmallVector<StringRef, 16> Names;
82 for (
const T *Decl : OrderedDecls) {
83 if (DeclsToInit.count(Decl))
84 Names.emplace_back(getName(Decl));
86 return llvm::join(Names.begin(), Names.end(),
", ");
89 SourceLocation getLocationForEndOfToken(
const ASTContext &Context,
91 return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
92 Context.getLangOpts());
96 enum class InitializerPlacement {
115 struct IntializerInsertion {
116 IntializerInsertion(InitializerPlacement
Placement,
117 const CXXCtorInitializer *
Where)
118 : Placement(Placement), Where(Where) {}
120 SourceLocation getLocation(
const ASTContext &Context,
121 const CXXConstructorDecl &Constructor)
const {
122 assert((
Where !=
nullptr ||
Placement == InitializerPlacement::New) &&
123 "Location should be relative to an existing initializer or this "
124 "insertion represents a new initializer list.");
125 SourceLocation Location;
127 case InitializerPlacement::New:
129 Context, Constructor.getBody()->getLocStart())
132 case InitializerPlacement::Before:
134 Context,
Where->getSourceRange().getBegin())
137 case InitializerPlacement::After:
138 Location =
Where->getRParenLoc();
141 return getLocationForEndOfToken(Context, Location);
144 std::string codeToInsert()
const {
145 assert(!
Initializers.empty() &&
"No initializers to insert");
147 llvm::raw_string_ostream Stream(Code);
151 case InitializerPlacement::New:
152 Stream <<
" : " << joined <<
"()";
154 case InitializerPlacement::Before:
155 Stream <<
" " << joined <<
"(),";
157 case InitializerPlacement::After:
158 Stream <<
", " << joined <<
"()";
170 const RecordDecl *getCanonicalRecordDecl(
const QualType &Type) {
171 if (
const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
172 return RT->getDecl();
176 template <
typename R,
typename T>
177 SmallVector<IntializerInsertion, 16>
178 computeInsertions(
const CXXConstructorDecl::init_const_range &Inits,
179 const R &OrderedDecls,
180 const SmallPtrSetImpl<const T *> &DeclsToInit) {
181 SmallVector<IntializerInsertion, 16> Insertions;
182 Insertions.emplace_back(InitializerPlacement::New,
nullptr);
184 typename R::const_iterator Decl = std::begin(OrderedDecls);
185 for (
const CXXCtorInitializer *Init : Inits) {
186 if (Init->isWritten()) {
187 if (Insertions.size() == 1)
188 Insertions.emplace_back(InitializerPlacement::Before, Init);
192 const auto *InitDecl =
193 Init->isAnyMemberInitializer()
194 ?
static_cast<const NamedDecl *
>(Init->getAnyMember())
195 : Init->getBaseClass()->getAsCXXRecordDecl();
198 for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
199 if (
const auto *D = dyn_cast<T>(*Decl)) {
200 if (DeclsToInit.count(D) > 0)
201 Insertions.back().Initializers.emplace_back(getName(D));
205 Insertions.emplace_back(InitializerPlacement::After, Init);
210 for (; Decl != std::end(OrderedDecls); ++Decl) {
211 if (
const auto *D = dyn_cast<T>(*Decl)) {
212 if (DeclsToInit.count(D) > 0)
213 Insertions.back().Initializers.emplace_back(getName(D));
221 void getInitializationsInOrder(
const CXXRecordDecl &ClassDecl,
222 SmallVectorImpl<const NamedDecl *> &Decls) {
224 for (
const auto &Base : ClassDecl.bases()) {
226 if (
const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
227 Decls.emplace_back(Decl);
230 forEachField(ClassDecl, ClassDecl.fields(),
false,
231 [&](
const FieldDecl *F) { Decls.push_back(F); });
234 template <
typename T>
235 void fixInitializerList(
const ASTContext &Context, DiagnosticBuilder &Diag,
236 const CXXConstructorDecl *Ctor,
237 const SmallPtrSetImpl<const T *> &DeclsToInit) {
239 if (Ctor->getLocStart().isMacroID())
242 SmallVector<const NamedDecl *, 16> OrderedDecls;
243 getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
245 for (
const auto &Insertion :
246 computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
247 if (!Insertion.Initializers.empty())
248 Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
249 Insertion.codeToInsert());
255 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef
Name,
258 IgnoreArrays(Options.get(
"IgnoreArrays", false)) {}
264 auto IsUserProvidedNonDelegatingConstructor =
265 allOf(isUserProvided(),
266 unless(anyOf(isInstantiated(), isDelegatingConstructor())));
267 auto IsNonTrivialDefaultConstructor = allOf(
268 isDefaultConstructor(), unless(isUserProvided()),
271 cxxConstructorDecl(isDefinition(),
272 anyOf(IsUserProvidedNonDelegatingConstructor,
273 IsNonTrivialDefaultConstructor))
281 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
282 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
283 unless(isImplicit()))),
284 unless(has(cxxConstructorDecl()))),
289 auto HasDefaultConstructor = hasInitializer(
290 cxxConstructExpr(unless(requiresZeroInitialization()),
291 hasDeclaration(cxxConstructorDecl(
292 isDefaultConstructor(), unless(isUserProvided())))));
294 varDecl(isDefinition(), HasDefaultConstructor,
295 hasAutomaticStorageDuration(),
296 hasType(recordDecl(has(fieldDecl()),
303 if (
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(
"ctor")) {
305 if (!Ctor->getBody())
307 checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
308 checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
309 }
else if (
const auto *Record =
310 Result.Nodes.getNodeAs<CXXRecordDecl>(
"record")) {
311 assert(Record->hasDefaultConstructor() &&
312 "Matched record should have a default constructor");
313 checkMissingMemberInitializer(*Result.Context, *Record,
nullptr);
314 checkMissingBaseClassInitializer(*Result.Context, *Record,
nullptr);
315 }
else if (
const auto *Var = Result.Nodes.getNodeAs<VarDecl>(
"var")) {
316 checkUninitializedTrivialType(*Result.Context, Var);
326 if (T->isIncompleteArrayType())
329 while (
const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
330 if (!ArrayT->getSize())
333 T = ArrayT->getElementType();
339 static bool isEmpty(ASTContext &Context,
const QualType &Type) {
340 if (
const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
341 return ClassDecl->isEmpty();
346 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
347 ASTContext &Context,
const CXXRecordDecl &ClassDecl,
348 const CXXConstructorDecl *Ctor) {
349 bool IsUnion = ClassDecl.isUnion();
351 if (IsUnion && ClassDecl.hasInClassInitializer())
355 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
356 forEachField(ClassDecl, ClassDecl.fields(),
false, [&](
const FieldDecl *F) {
357 if (!F->hasInClassInitializer() &&
360 !
isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
361 FieldsToInit.insert(F);
363 if (FieldsToInit.empty())
367 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
370 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
373 FieldsToInit.erase(Init->getAnyMember());
376 removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
381 SmallVector<const FieldDecl *, 16> OrderedFields;
382 forEachField(ClassDecl, ClassDecl.fields(),
false,
383 [&](
const FieldDecl *F) { OrderedFields.push_back(F); });
386 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
387 forEachField(ClassDecl, FieldsToInit,
false,
388 [&](
const FieldDecl *F) { AllFieldsToInit.insert(F); });
389 if (AllFieldsToInit.empty())
392 DiagnosticBuilder Diag =
393 diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(),
395 ?
"union constructor should initialize one of these fields: %0"
396 :
"constructor does not initialize these fields: %0")
397 << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
401 if (Ctor && Ctor->getLocStart().isMacroID())
406 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
407 forEachField(ClassDecl, FieldsToInit,
true, [&](
const FieldDecl *F) {
411 if (!F->getType()->isEnumeralType() && !F->isBitField())
412 FieldsToFix.insert(F);
414 if (FieldsToFix.empty())
418 if (Context.getLangOpts().CPlusPlus11) {
419 for (
const FieldDecl *Field : FieldsToFix) {
420 Diag << FixItHint::CreateInsertion(
421 getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
426 fixInitializerList(Context, Diag, Ctor, FieldsToFix);
430 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
431 const ASTContext &Context,
const CXXRecordDecl &ClassDecl,
432 const CXXConstructorDecl *Ctor) {
435 SmallVector<const RecordDecl *, 4> AllBases;
436 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
437 for (
const CXXBaseSpecifier &Base : ClassDecl.bases()) {
438 if (
const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
439 AllBases.emplace_back(BaseClassDecl);
440 if (!BaseClassDecl->field_empty() &&
443 BasesToInit.insert(BaseClassDecl);
447 if (BasesToInit.empty())
452 for (
const CXXCtorInitializer *Init : Ctor->inits()) {
453 if (Init->isBaseInitializer() && Init->isWritten())
454 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
458 if (BasesToInit.empty())
461 DiagnosticBuilder Diag =
462 diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(),
463 "constructor does not initialize these bases: %0")
464 << toCommaSeparatedString(AllBases, BasesToInit);
467 fixInitializerList(Context, Diag, Ctor, BasesToInit);
470 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
471 const ASTContext &Context,
const VarDecl *Var) {
472 DiagnosticBuilder Diag =
473 diag(Var->getLocStart(),
"uninitialized record type: %0") << Var;
475 Diag << FixItHint::CreateInsertion(
476 getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
477 Context.getLangOpts().CPlusPlus11 ?
"{}" :
" = {}");
AST_MATCHER(Type, isStrictlyInteger)
LangOptions getLangOpts() const
Returns the language options from the context.
static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T)
std::unique_ptr< ast_matchers::MatchFinder > Finder
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static bool isEmpty(ASTContext &Context, const QualType &Type)
Base class for all clang-tidy checks.
SmallVector< std::string, 4 > Initializers
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context)
Returns true if Type is trivially default constructible.
const CXXCtorInitializer * Where
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.
std::map< std::string, std::string > OptionMap
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
ClangTidyContext & Context
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
InitializerPlacement Placement
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Token getPreviousNonCommentToken(const ASTContext &Context, SourceLocation Location)
Returns previous non-comment token skipping over any comment text or tok::unknown if not found...