1 //===--- ProTypeMemberInitCheck.cpp - clang-tidy---------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "ProTypeMemberInitCheck.h"
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"
17
18 using namespace clang::ast_matchers;
19 using namespace clang::tidy::matchers;
20 using llvm::SmallPtrSet;
21 using llvm::SmallPtrSetImpl;
22
23 namespace clang {
24 namespace tidy {
25 namespace cppcoreguidelines {
26
27 namespace {
28
AST_MATCHER(CXXRecordDecl,hasDefaultConstructor)29 AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) {
30 return Node.hasDefaultConstructor();
31 }
32
33 // Iterate over all the fields in a record type, both direct and indirect (e.g.
34 // if the record contains an anonymous struct).
35 template <typename T, typename Func>
forEachField(const RecordDecl & Record,const T & Fields,Func && Fn)36 void forEachField(const RecordDecl &Record, const T &Fields, Func &&Fn) {
37 for (const FieldDecl *F : Fields) {
38 if (F->isAnonymousStructOrUnion()) {
39 if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl())
40 forEachField(*R, R->fields(), Fn);
41 } else {
42 Fn(F);
43 }
44 }
45 }
46
removeFieldsInitializedInBody(const Stmt & Stmt,ASTContext & Context,SmallPtrSetImpl<const FieldDecl * > & FieldDecls)47 void removeFieldsInitializedInBody(
48 const Stmt &Stmt, ASTContext &Context,
49 SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
50 auto Matches =
51 match(findAll(binaryOperator(
52 hasOperatorName("="),
53 hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))),
54 Stmt, Context);
55 for (const auto &Match : Matches)
56 FieldDecls.erase(Match.getNodeAs<FieldDecl>("fieldDecl"));
57 }
58
getName(const FieldDecl * Field)59 StringRef getName(const FieldDecl *Field) { return Field->getName(); }
60
getName(const RecordDecl * Record)61 StringRef getName(const RecordDecl *Record) {
62 // Get the typedef name if this is a C-style anonymous struct and typedef.
63 if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
64 return Typedef->getName();
65 return Record->getName();
66 }
67
68 // Creates comma separated list of decls requiring initialization in order of
69 // declaration.
70 template <typename R, typename T>
71 std::string
toCommaSeparatedString(const R & OrderedDecls,const SmallPtrSetImpl<const T * > & DeclsToInit)72 toCommaSeparatedString(const R &OrderedDecls,
73 const SmallPtrSetImpl<const T *> &DeclsToInit) {
74 SmallVector<StringRef, 16> Names;
75 for (const T *Decl : OrderedDecls) {
76 if (DeclsToInit.count(Decl))
77 Names.emplace_back(getName(Decl));
78 }
79 return llvm::join(Names.begin(), Names.end(), ", ");
80 }
81
getLocationForEndOfToken(const ASTContext & Context,SourceLocation Location)82 SourceLocation getLocationForEndOfToken(const ASTContext &Context,
83 SourceLocation Location) {
84 return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
85 Context.getLangOpts());
86 }
87
88 // There are 3 kinds of insertion placements:
89 enum class InitializerPlacement {
90 // 1. The fields are inserted after an existing CXXCtorInitializer stored in
91 // Where. This will be the case whenever there is a written initializer before
92 // the fields available.
93 After,
94
95 // 2. The fields are inserted before the first existing initializer stored in
96 // Where.
97 Before,
98
99 // 3. There are no written initializers and the fields will be inserted before
100 // the constructor's body creating a new initializer list including the ':'.
101 New
102 };
103
104 // An InitializerInsertion contains a list of fields and/or base classes to
105 // insert into the initializer list of a constructor. We use this to ensure
106 // proper absolute ordering according to the class declaration relative to the
107 // (perhaps improper) ordering in the existing initializer list, if any.
108 struct IntializerInsertion {
IntializerInsertionclang::tidy::cppcoreguidelines::__anon2f173d760111::IntializerInsertion109 IntializerInsertion(InitializerPlacement Placement,
110 const CXXCtorInitializer *Where)
111 : Placement(Placement), Where(Where) {}
112
getLocationclang::tidy::cppcoreguidelines::__anon2f173d760111::IntializerInsertion113 SourceLocation getLocation(const ASTContext &Context,
114 const CXXConstructorDecl &Constructor) const {
115 assert((Where != nullptr || Placement == InitializerPlacement::New) &&
116 "Location should be relative to an existing initializer or this "
117 "insertion represents a new initializer list.");
118 SourceLocation Location;
119 switch (Placement) {
120 case InitializerPlacement::New:
121 Location = utils::lexer::getPreviousToken(
122 Constructor.getBody()->getBeginLoc(),
123 Context.getSourceManager(), Context.getLangOpts())
124 .getLocation();
125 break;
126 case InitializerPlacement::Before:
127 Location = utils::lexer::getPreviousToken(
128 Where->getSourceRange().getBegin(),
129 Context.getSourceManager(), Context.getLangOpts())
130 .getLocation();
131 break;
132 case InitializerPlacement::After:
133 Location = Where->getRParenLoc();
134 break;
135 }
136 return getLocationForEndOfToken(Context, Location);
137 }
138
codeToInsertclang::tidy::cppcoreguidelines::__anon2f173d760111::IntializerInsertion139 std::string codeToInsert() const {
140 assert(!Initializers.empty() && "No initializers to insert");
141 std::string Code;
142 llvm::raw_string_ostream Stream(Code);
143 std::string joined =
144 llvm::join(Initializers.begin(), Initializers.end(), "(), ");
145 switch (Placement) {
146 case InitializerPlacement::New:
147 Stream << " : " << joined << "()";
148 break;
149 case InitializerPlacement::Before:
150 Stream << " " << joined << "(),";
151 break;
152 case InitializerPlacement::After:
153 Stream << ", " << joined << "()";
154 break;
155 }
156 return Stream.str();
157 }
158
159 InitializerPlacement Placement;
160 const CXXCtorInitializer *Where;
161 SmallVector<std::string, 4> Initializers;
162 };
163
164 // Convenience utility to get a RecordDecl from a QualType.
getCanonicalRecordDecl(const QualType & Type)165 const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
166 if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
167 return RT->getDecl();
168 return nullptr;
169 }
170
171 template <typename R, typename T>
172 SmallVector<IntializerInsertion, 16>
computeInsertions(const CXXConstructorDecl::init_const_range & Inits,const R & OrderedDecls,const SmallPtrSetImpl<const T * > & DeclsToInit)173 computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
174 const R &OrderedDecls,
175 const SmallPtrSetImpl<const T *> &DeclsToInit) {
176 SmallVector<IntializerInsertion, 16> Insertions;
177 Insertions.emplace_back(InitializerPlacement::New, nullptr);
178
179 typename R::const_iterator Decl = std::begin(OrderedDecls);
180 for (const CXXCtorInitializer *Init : Inits) {
181 if (Init->isWritten()) {
182 if (Insertions.size() == 1)
183 Insertions.emplace_back(InitializerPlacement::Before, Init);
184
185 // Gets either the field or base class being initialized by the provided
186 // initializer.
187 const auto *InitDecl =
188 Init->isAnyMemberInitializer()
189 ? static_cast<const NamedDecl *>(Init->getAnyMember())
190 : Init->getBaseClass()->getAsCXXRecordDecl();
191
192 // Add all fields between current field up until the next initializer.
193 for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
194 if (const auto *D = dyn_cast<T>(*Decl)) {
195 if (DeclsToInit.count(D) > 0)
196 Insertions.back().Initializers.emplace_back(getName(D));
197 }
198 }
199
200 Insertions.emplace_back(InitializerPlacement::After, Init);
201 }
202 }
203
204 // Add remaining decls that require initialization.
205 for (; Decl != std::end(OrderedDecls); ++Decl) {
206 if (const auto *D = dyn_cast<T>(*Decl)) {
207 if (DeclsToInit.count(D) > 0)
208 Insertions.back().Initializers.emplace_back(getName(D));
209 }
210 }
211 return Insertions;
212 }
213
214 // Gets the list of bases and members that could possibly be initialized, in
215 // order as they appear in the class declaration.
getInitializationsInOrder(const CXXRecordDecl & ClassDecl,SmallVectorImpl<const NamedDecl * > & Decls)216 void getInitializationsInOrder(const CXXRecordDecl &ClassDecl,
217 SmallVectorImpl<const NamedDecl *> &Decls) {
218 Decls.clear();
219 for (const auto &Base : ClassDecl.bases()) {
220 // Decl may be null if the base class is a template parameter.
221 if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
222 Decls.emplace_back(Decl);
223 }
224 }
225 forEachField(ClassDecl, ClassDecl.fields(),
226 [&](const FieldDecl *F) { Decls.push_back(F); });
227 }
228
229 template <typename T>
fixInitializerList(const ASTContext & Context,DiagnosticBuilder & Diag,const CXXConstructorDecl * Ctor,const SmallPtrSetImpl<const T * > & DeclsToInit)230 void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
231 const CXXConstructorDecl *Ctor,
232 const SmallPtrSetImpl<const T *> &DeclsToInit) {
233 // Do not propose fixes in macros since we cannot place them correctly.
234 if (Ctor->getBeginLoc().isMacroID())
235 return;
236
237 SmallVector<const NamedDecl *, 16> OrderedDecls;
238 getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
239
240 for (const auto &Insertion :
241 computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
242 if (!Insertion.Initializers.empty())
243 Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
244 Insertion.codeToInsert());
245 }
246 }
247
248 } // anonymous namespace
249
ProTypeMemberInitCheck(StringRef Name,ClangTidyContext * Context)250 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
251 ClangTidyContext *Context)
252 : ClangTidyCheck(Name, Context),
253 IgnoreArrays(Options.get("IgnoreArrays", false)),
254 UseAssignment(Options.getLocalOrGlobal("UseAssignment", false)) {}
255
registerMatchers(MatchFinder * Finder)256 void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
257 auto IsUserProvidedNonDelegatingConstructor =
258 allOf(isUserProvided(),
259 unless(anyOf(isInstantiated(), isDelegatingConstructor())));
260 auto IsNonTrivialDefaultConstructor = allOf(
261 isDefaultConstructor(), unless(isUserProvided()),
262 hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
263 Finder->addMatcher(
264 cxxConstructorDecl(isDefinition(),
265 anyOf(IsUserProvidedNonDelegatingConstructor,
266 IsNonTrivialDefaultConstructor))
267 .bind("ctor"),
268 this);
269
270 // Match classes with a default constructor that is defaulted or is not in the
271 // AST.
272 Finder->addMatcher(
273 cxxRecordDecl(
274 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
275 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
276 unless(isImplicit()))),
277 unless(has(cxxConstructorDecl()))),
278 unless(isTriviallyDefaultConstructible()))
279 .bind("record"),
280 this);
281
282 auto HasDefaultConstructor = hasInitializer(
283 cxxConstructExpr(unless(requiresZeroInitialization()),
284 hasDeclaration(cxxConstructorDecl(
285 isDefaultConstructor(), unless(isUserProvided())))));
286 Finder->addMatcher(
287 varDecl(isDefinition(), HasDefaultConstructor,
288 hasAutomaticStorageDuration(),
289 hasType(recordDecl(has(fieldDecl()),
290 isTriviallyDefaultConstructible())))
291 .bind("var"),
292 this);
293 }
294
check(const MatchFinder::MatchResult & Result)295 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
296 if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
297 // Skip declarations delayed by late template parsing without a body.
298 if (!Ctor->getBody())
299 return;
300 checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
301 checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
302 } else if (const auto *Record =
303 Result.Nodes.getNodeAs<CXXRecordDecl>("record")) {
304 assert(Record->hasDefaultConstructor() &&
305 "Matched record should have a default constructor");
306 checkMissingMemberInitializer(*Result.Context, *Record, nullptr);
307 checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr);
308 } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
309 checkUninitializedTrivialType(*Result.Context, Var);
310 }
311 }
312
storeOptions(ClangTidyOptions::OptionMap & Opts)313 void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
314 Options.store(Opts, "IgnoreArrays", IgnoreArrays);
315 Options.store(Opts, "UseAssignment", UseAssignment);
316 }
317
318 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
isIncompleteOrZeroLengthArrayType(ASTContext & Context,QualType T)319 static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) {
320 if (T->isIncompleteArrayType())
321 return true;
322
323 while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
324 if (!ArrayT->getSize())
325 return true;
326
327 T = ArrayT->getElementType();
328 }
329
330 return false;
331 }
332
isEmpty(ASTContext & Context,const QualType & Type)333 static bool isEmpty(ASTContext &Context, const QualType &Type) {
334 if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
335 return ClassDecl->isEmpty();
336 }
337 return isIncompleteOrZeroLengthArrayType(Context, Type);
338 }
339
getInitializer(QualType QT,bool UseAssignment)340 static const char *getInitializer(QualType QT, bool UseAssignment) {
341 const char *DefaultInitializer = "{}";
342 if (!UseAssignment)
343 return DefaultInitializer;
344
345 if (QT->isPointerType())
346 return " = nullptr";
347
348 const BuiltinType *BT =
349 dyn_cast<BuiltinType>(QT.getCanonicalType().getTypePtr());
350 if (!BT)
351 return DefaultInitializer;
352
353 switch (BT->getKind()) {
354 case BuiltinType::Bool:
355 return " = false";
356 case BuiltinType::Float:
357 return " = 0.0F";
358 case BuiltinType::Double:
359 return " = 0.0";
360 case BuiltinType::LongDouble:
361 return " = 0.0L";
362 case BuiltinType::SChar:
363 case BuiltinType::Char_S:
364 case BuiltinType::WChar_S:
365 case BuiltinType::Char16:
366 case BuiltinType::Char32:
367 case BuiltinType::Short:
368 case BuiltinType::Int:
369 return " = 0";
370 case BuiltinType::UChar:
371 case BuiltinType::Char_U:
372 case BuiltinType::WChar_U:
373 case BuiltinType::UShort:
374 case BuiltinType::UInt:
375 return " = 0U";
376 case BuiltinType::Long:
377 return " = 0L";
378 case BuiltinType::ULong:
379 return " = 0UL";
380 case BuiltinType::LongLong:
381 return " = 0LL";
382 case BuiltinType::ULongLong:
383 return " = 0ULL";
384
385 default:
386 return DefaultInitializer;
387 }
388 }
389
checkMissingMemberInitializer(ASTContext & Context,const CXXRecordDecl & ClassDecl,const CXXConstructorDecl * Ctor)390 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
391 ASTContext &Context, const CXXRecordDecl &ClassDecl,
392 const CXXConstructorDecl *Ctor) {
393 bool IsUnion = ClassDecl.isUnion();
394
395 if (IsUnion && ClassDecl.hasInClassInitializer())
396 return;
397
398 // Gather all fields (direct and indirect) that need to be initialized.
399 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
400 forEachField(ClassDecl, ClassDecl.fields(), [&](const FieldDecl *F) {
401 if (!F->hasInClassInitializer() &&
402 utils::type_traits::isTriviallyDefaultConstructible(F->getType(),
403 Context) &&
404 !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
405 FieldsToInit.insert(F);
406 });
407 if (FieldsToInit.empty())
408 return;
409
410 if (Ctor) {
411 for (const CXXCtorInitializer *Init : Ctor->inits()) {
412 // Remove any fields that were explicitly written in the initializer list
413 // or in-class.
414 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
415 if (IsUnion)
416 return; // We can only initialize one member of a union.
417 FieldsToInit.erase(Init->getAnyMember());
418 }
419 }
420 removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
421 }
422
423 // Collect all fields in order, both direct fields and indirect fields from
424 // anonymous record types.
425 SmallVector<const FieldDecl *, 16> OrderedFields;
426 forEachField(ClassDecl, ClassDecl.fields(),
427 [&](const FieldDecl *F) { OrderedFields.push_back(F); });
428
429 // Collect all the fields we need to initialize, including indirect fields.
430 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
431 forEachField(ClassDecl, FieldsToInit,
432 [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
433 if (AllFieldsToInit.empty())
434 return;
435
436 DiagnosticBuilder Diag =
437 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
438 IsUnion
439 ? "union constructor should initialize one of these fields: %0"
440 : "constructor does not initialize these fields: %0")
441 << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
442
443 // Do not propose fixes for constructors in macros since we cannot place them
444 // correctly.
445 if (Ctor && Ctor->getBeginLoc().isMacroID())
446 return;
447
448 // Collect all fields but only suggest a fix for the first member of unions,
449 // as initializing more than one union member is an error.
450 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
451 SmallPtrSet<const RecordDecl *, 4> UnionsSeen;
452 forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) {
453 if (!FieldsToInit.count(F))
454 return;
455 // Don't suggest fixes for enums because we don't know a good default.
456 // Don't suggest fixes for bitfields because in-class initialization is not
457 // possible until C++20.
458 if (F->getType()->isEnumeralType() ||
459 (!getLangOpts().CPlusPlus20 && F->isBitField()))
460 return;
461 if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second)
462 FieldsToFix.insert(F);
463 });
464 if (FieldsToFix.empty())
465 return;
466
467 // Use in-class initialization if possible.
468 if (Context.getLangOpts().CPlusPlus11) {
469 for (const FieldDecl *Field : FieldsToFix) {
470 Diag << FixItHint::CreateInsertion(
471 getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
472 getInitializer(Field->getType(), UseAssignment));
473 }
474 } else if (Ctor) {
475 // Otherwise, rewrite the constructor's initializer list.
476 fixInitializerList(Context, Diag, Ctor, FieldsToFix);
477 }
478 }
479
checkMissingBaseClassInitializer(const ASTContext & Context,const CXXRecordDecl & ClassDecl,const CXXConstructorDecl * Ctor)480 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
481 const ASTContext &Context, const CXXRecordDecl &ClassDecl,
482 const CXXConstructorDecl *Ctor) {
483
484 // Gather any base classes that need to be initialized.
485 SmallVector<const RecordDecl *, 4> AllBases;
486 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
487 for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
488 if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
489 AllBases.emplace_back(BaseClassDecl);
490 if (!BaseClassDecl->field_empty() &&
491 utils::type_traits::isTriviallyDefaultConstructible(Base.getType(),
492 Context))
493 BasesToInit.insert(BaseClassDecl);
494 }
495 }
496
497 if (BasesToInit.empty())
498 return;
499
500 // Remove any bases that were explicitly written in the initializer list.
501 if (Ctor) {
502 if (Ctor->isImplicit())
503 return;
504
505 for (const CXXCtorInitializer *Init : Ctor->inits()) {
506 if (Init->isBaseInitializer() && Init->isWritten())
507 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
508 }
509 }
510
511 if (BasesToInit.empty())
512 return;
513
514 DiagnosticBuilder Diag =
515 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
516 "constructor does not initialize these bases: %0")
517 << toCommaSeparatedString(AllBases, BasesToInit);
518
519 if (Ctor)
520 fixInitializerList(Context, Diag, Ctor, BasesToInit);
521 }
522
checkUninitializedTrivialType(const ASTContext & Context,const VarDecl * Var)523 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
524 const ASTContext &Context, const VarDecl *Var) {
525 DiagnosticBuilder Diag =
526 diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
527
528 Diag << FixItHint::CreateInsertion(
529 getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
530 Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
531 }
532
533 } // namespace cppcoreguidelines
534 } // namespace tidy
535 } // namespace clang
536