clang-tools 8.0.0

ProTypeMemberInitCheck.cpp
Go to the documentation of this file.
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
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
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 Constructor.getBody()->getBeginLoc(),
124 Context.getSourceManager(), Context.getLangOpts())
125 .getLocation();
126 break;
127 case InitializerPlacement::Before:
128 Location = utils::lexer::getPreviousToken(
129 Where->getSourceRange().getBegin(),
130 Context.getSourceManager(), Context.getLangOpts())
131 .getLocation();
132 break;
133 case InitializerPlacement::After:
134 Location = Where->getRParenLoc();
135 break;
136 }
137 return getLocationForEndOfToken(Context, Location);
138 }
139
140 std::string codeToInsert() const {
141 assert(!Initializers.empty() && "No initializers to insert");
142 std::string Code;
143 llvm::raw_string_ostream Stream(Code);
144 std::string joined =
145 llvm::join(Initializers.begin(), Initializers.end(), "(), ");
146 switch (Placement) {
147 case InitializerPlacement::New:
148 Stream << " : " << joined << "()";
149 break;
150 case InitializerPlacement::Before:
151 Stream << " " << joined << "(),";
152 break;
153 case InitializerPlacement::After:
154 Stream << ", " << joined << "()";
155 break;
156 }
157 return Stream.str();
158 }
159
160 InitializerPlacement Placement;
161 const CXXCtorInitializer *Where;
162 SmallVector<std::string, 4> Initializers;
163 };
164
165 // Convenience utility to get a RecordDecl from a QualType.
166 const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
167 if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
168 return RT->getDecl();
169 return nullptr;
170 }
171
172 template <typename R, typename T>
173 SmallVector<IntializerInsertion, 16>
174 computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
175 const R &OrderedDecls,
176 const SmallPtrSetImpl<const T *> &DeclsToInit) {
177 SmallVector<IntializerInsertion, 16> Insertions;
178 Insertions.emplace_back(InitializerPlacement::New, nullptr);
179
180 typename R::const_iterator Decl = std::begin(OrderedDecls);
181 for (const CXXCtorInitializer *Init : Inits) {
182 if (Init->isWritten()) {
183 if (Insertions.size() == 1)
184 Insertions.emplace_back(InitializerPlacement::Before, Init);
185
186 // Gets either the field or base class being initialized by the provided
187 // initializer.
188 const auto *InitDecl =
189 Init->isAnyMemberInitializer()
190 ? static_cast<const NamedDecl *>(Init->getAnyMember())
191 : Init->getBaseClass()->getAsCXXRecordDecl();
192
193 // Add all fields between current field up until the next intializer.
194 for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
195 if (const auto *D = dyn_cast<T>(*Decl)) {
196 if (DeclsToInit.count(D) > 0)
197 Insertions.back().Initializers.emplace_back(getName(D));
198 }
199 }
200
201 Insertions.emplace_back(InitializerPlacement::After, Init);
202 }
203 }
204
205 // Add remaining decls that require initialization.
206 for (; Decl != std::end(OrderedDecls); ++Decl) {
207 if (const auto *D = dyn_cast<T>(*Decl)) {
208 if (DeclsToInit.count(D) > 0)
209 Insertions.back().Initializers.emplace_back(getName(D));
210 }
211 }
212 return Insertions;
213 }
214
215 // Gets the list of bases and members that could possibly be initialized, in
216 // order as they appear in the class declaration.
217 void getInitializationsInOrder(const CXXRecordDecl &ClassDecl,
218 SmallVectorImpl<const NamedDecl *> &Decls) {
219 Decls.clear();
220 for (const auto &Base : ClassDecl.bases()) {
221 // Decl may be null if the base class is a template parameter.
222 if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) {
223 Decls.emplace_back(Decl);
224 }
225 }
226 forEachField(ClassDecl, ClassDecl.fields(),
227 [&](const FieldDecl *F) { Decls.push_back(F); });
228 }
229
230 template <typename T>
231 void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
232 const CXXConstructorDecl *Ctor,
233 const SmallPtrSetImpl<const T *> &DeclsToInit) {
234 // Do not propose fixes in macros since we cannot place them correctly.
235 if (Ctor->getBeginLoc().isMacroID())
236 return;
237
238 SmallVector<const NamedDecl *, 16> OrderedDecls;
239 getInitializationsInOrder(*Ctor->getParent(), OrderedDecls);
240
241 for (const auto &Insertion :
242 computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
243 if (!Insertion.Initializers.empty())
244 Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
245 Insertion.codeToInsert());
246 }
247 }
248
249 } // anonymous namespace
250
251 ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
252 ClangTidyContext *Context)
253 : ClangTidyCheck(Name, Context),
254 IgnoreArrays(Options.get("IgnoreArrays", false)) {}
255
256 void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
257 if (!getLangOpts().CPlusPlus)
258 return;
259
260 auto IsUserProvidedNonDelegatingConstructor =
261 allOf(isUserProvided(),
262 unless(anyOf(isInstantiated(), isDelegatingConstructor())));
263 auto IsNonTrivialDefaultConstructor = allOf(
264 isDefaultConstructor(), unless(isUserProvided()),
265 hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
266 Finder->addMatcher(
267 cxxConstructorDecl(isDefinition(),
268 anyOf(IsUserProvidedNonDelegatingConstructor,
269 IsNonTrivialDefaultConstructor))
270 .bind("ctor"),
271 this);
272
273 // Match classes with a default constructor that is defaulted or is not in the
274 // AST.
275 Finder->addMatcher(
276 cxxRecordDecl(
277 isDefinition(), unless(isInstantiated()), hasDefaultConstructor(),
278 anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(),
279 unless(isImplicit()))),
280 unless(has(cxxConstructorDecl()))),
281 unless(isTriviallyDefaultConstructible()))
282 .bind("record"),
283 this);
284
285 auto HasDefaultConstructor = hasInitializer(
286 cxxConstructExpr(unless(requiresZeroInitialization()),
287 hasDeclaration(cxxConstructorDecl(
288 isDefaultConstructor(), unless(isUserProvided())))));
289 Finder->addMatcher(
290 varDecl(isDefinition(), HasDefaultConstructor,
291 hasAutomaticStorageDuration(),
292 hasType(recordDecl(has(fieldDecl()),
293 isTriviallyDefaultConstructible())))
294 .bind("var"),
295 this);
296 }
297
298 void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
299 if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
300 // Skip declarations delayed by late template parsing without a body.
301 if (!Ctor->getBody())
302 return;
303 checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor);
304 checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor);
305 } else if (const auto *Record =
306 Result.Nodes.getNodeAs<CXXRecordDecl>("record")) {
307 assert(Record->hasDefaultConstructor() &&
308 "Matched record should have a default constructor");
309 checkMissingMemberInitializer(*Result.Context, *Record, nullptr);
310 checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr);
311 } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
312 checkUninitializedTrivialType(*Result.Context, Var);
313 }
314 }
315
316 void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
317 Options.store(Opts, "IgnoreArrays", IgnoreArrays);
318 }
319
320 // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp.
321 static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) {
322 if (T->isIncompleteArrayType())
323 return true;
324
325 while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) {
326 if (!ArrayT->getSize())
327 return true;
328
329 T = ArrayT->getElementType();
330 }
331
332 return false;
333 }
334
335 static bool isEmpty(ASTContext &Context, const QualType &Type) {
336 if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) {
337 return ClassDecl->isEmpty();
338 }
339 return isIncompleteOrZeroLengthArrayType(Context, Type);
340 }
341
342 void ProTypeMemberInitCheck::checkMissingMemberInitializer(
343 ASTContext &Context, const CXXRecordDecl &ClassDecl,
344 const CXXConstructorDecl *Ctor) {
345 bool IsUnion = ClassDecl.isUnion();
346
347 if (IsUnion && ClassDecl.hasInClassInitializer())
348 return;
349
350 // Gather all fields (direct and indirect) that need to be initialized.
351 SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
352 forEachField(ClassDecl, ClassDecl.fields(), [&](const FieldDecl *F) {
353 if (!F->hasInClassInitializer() &&
354 utils::type_traits::isTriviallyDefaultConstructible(F->getType(),
355 Context) &&
356 !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield())
357 FieldsToInit.insert(F);
358 });
359 if (FieldsToInit.empty())
360 return;
361
362 if (Ctor) {
363 for (const CXXCtorInitializer *Init : Ctor->inits()) {
364 // Remove any fields that were explicitly written in the initializer list
365 // or in-class.
366 if (Init->isAnyMemberInitializer() && Init->isWritten()) {
367 if (IsUnion)
368 return; // We can only initialize one member of a union.
369 FieldsToInit.erase(Init->getAnyMember());
370 }
371 }
372 removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit);
373 }
374
375 // Collect all fields in order, both direct fields and indirect fields from
376 // anonmyous record types.
377 SmallVector<const FieldDecl *, 16> OrderedFields;
378 forEachField(ClassDecl, ClassDecl.fields(),
379 [&](const FieldDecl *F) { OrderedFields.push_back(F); });
380
381 // Collect all the fields we need to initialize, including indirect fields.
382 SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
383 forEachField(ClassDecl, FieldsToInit,
384 [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
385 if (AllFieldsToInit.empty())
386 return;
387
388 DiagnosticBuilder Diag =
389 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
390 IsUnion
391 ? "union constructor should initialize one of these fields: %0"
392 : "constructor does not initialize these fields: %0")
393 << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
394
395 // Do not propose fixes for constructors in macros since we cannot place them
396 // correctly.
397 if (Ctor && Ctor->getBeginLoc().isMacroID())
398 return;
399
400 // Collect all fields but only suggest a fix for the first member of unions,
401 // as initializing more than one union member is an error.
402 SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
403 SmallPtrSet<const RecordDecl *, 4> UnionsSeen;
404 forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) {
405 if (!FieldsToInit.count(F))
406 return;
407 // Don't suggest fixes for enums because we don't know a good default.
408 // Don't suggest fixes for bitfields because in-class initialization is not
409 // possible until C++2a.
410 if (F->getType()->isEnumeralType() ||
411 (!getLangOpts().CPlusPlus2a && F->isBitField()))
412 return;
413 if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second)
414 FieldsToFix.insert(F);
415 });
416 if (FieldsToFix.empty())
417 return;
418
419 // Use in-class initialization if possible.
420 if (Context.getLangOpts().CPlusPlus11) {
421 for (const FieldDecl *Field : FieldsToFix) {
422 Diag << FixItHint::CreateInsertion(
423 getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
424 "{}");
425 }
426 } else if (Ctor) {
427 // Otherwise, rewrite the constructor's initializer list.
428 fixInitializerList(Context, Diag, Ctor, FieldsToFix);
429 }
430 }
431
432 void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
433 const ASTContext &Context, const CXXRecordDecl &ClassDecl,
434 const CXXConstructorDecl *Ctor) {
435
436 // Gather any base classes that need to be initialized.
437 SmallVector<const RecordDecl *, 4> AllBases;
438 SmallPtrSet<const RecordDecl *, 4> BasesToInit;
439 for (const CXXBaseSpecifier &Base : ClassDecl.bases()) {
440 if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
441 AllBases.emplace_back(BaseClassDecl);
442 if (!BaseClassDecl->field_empty() &&
443 utils::type_traits::isTriviallyDefaultConstructible(Base.getType(),
444 Context))
445 BasesToInit.insert(BaseClassDecl);
446 }
447 }
448
449 if (BasesToInit.empty())
450 return;
451
452 // Remove any bases that were explicitly written in the initializer list.
453 if (Ctor) {
454 if (Ctor->isImplicit())
455 return;
456
457 for (const CXXCtorInitializer *Init : Ctor->inits()) {
458 if (Init->isBaseInitializer() && Init->isWritten())
459 BasesToInit.erase(Init->getBaseClass()->getAsCXXRecordDecl());
460 }
461 }
462
463 if (BasesToInit.empty())
464 return;
465
466 DiagnosticBuilder Diag =
467 diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(),
468 "constructor does not initialize these bases: %0")
469 << toCommaSeparatedString(AllBases, BasesToInit);
470
471 if (Ctor)
472 fixInitializerList(Context, Diag, Ctor, BasesToInit);
473 }
474
475 void ProTypeMemberInitCheck::checkUninitializedTrivialType(
476 const ASTContext &Context, const VarDecl *Var) {
477 DiagnosticBuilder Diag =
478 diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var;
479
480 Diag << FixItHint::CreateInsertion(
481 getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
482 Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
483 }
484
485 } // namespace cppcoreguidelines
486 } // namespace tidy
487 } // namespace clang
clang::ast_matchers
Definition: AbseilMatcher.h:15
clang::tidy::matchers::AST_MATCHER
AST_MATCHER(BinaryOperator, isAssignmentOperator)
Definition: Matchers.h:20
clang::tidy::OptionsView::store
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.
Definition: ClangTidy.cpp:473
clang::tidy::ClangTidyCheck::getLangOpts
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
clang::tidy::cppcoreguidelines::isIncompleteOrZeroLengthArrayType
static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T)
Definition: ProTypeMemberInitCheck.cpp:321
clang::tidy::utils::lexer::getPreviousToken
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
Definition: LexerUtils.cpp:17
clang::doc::Record
llvm::SmallVector< uint64_t, 1024 > Record
Definition: BitcodeReader.cpp:19
clang::tidy::cppcoreguidelines::ProTypeMemberInitCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: ProTypeMemberInitCheck.cpp:298
clang::tidy::matchers
Definition: Matchers.h:18
clang::tidy::cppcoreguidelines::isEmpty
static bool isEmpty(ASTContext &Context, const QualType &Type)
Definition: ProTypeMemberInitCheck.cpp:335
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
clang::tidy::cppcoreguidelines::@236::InitializerPlacement
InitializerPlacement
Definition: ProTypeMemberInitCheck.cpp:90
Initializers
SmallVector< std::string, 4 > Initializers
Definition: ProTypeMemberInitCheck.cpp:162
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidy.h:183
clang::tidy::cppcoreguidelines::ProTypeMemberInitCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: ProTypeMemberInitCheck.cpp:316
clang::tidy::utils::type_traits::isTriviallyDefaultConstructible
bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context)
Returns true if Type is trivially default constructible.
Definition: TypeTraits.cpp:89
Where
const CXXCtorInitializer * Where
Definition: ProTypeMemberInitCheck.cpp:161
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:98
D
const Decl * D
Definition: XRefs.cpp:79
Result
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: ClangdServer.cpp:60
clang::tidy::cppcoreguidelines::ProTypeMemberInitCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ProTypeMemberInitCheck.cpp:256
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ChangeNamespace.cpp:18
ProTypeMemberInitCheck.h
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:100
Placement
InitializerPlacement Placement
Definition: ProTypeMemberInitCheck.cpp:160
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:82
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:438
Location
Definition: Modularize.cpp:384