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