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