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