1 //===--- PreferMemberInitializerCheck.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 "PreferMemberInitializerCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "clang/Lex/Lexer.h" 13 14 using namespace clang::ast_matchers; 15 16 namespace clang { 17 namespace tidy { 18 namespace cppcoreguidelines { 19 20 static bool isControlStatement(const Stmt *S) { 21 return isa<IfStmt>(S) || isa<SwitchStmt>(S) || isa<ForStmt>(S) || 22 isa<WhileStmt>(S) || isa<DoStmt>(S) || isa<ReturnStmt>(S) || 23 isa<GotoStmt>(S) || isa<CXXTryStmt>(S) || isa<CXXThrowExpr>(S); 24 } 25 26 static bool isNoReturnCallStatement(const Stmt *S) { 27 const auto *Call = dyn_cast<CallExpr>(S); 28 if (!Call) 29 return false; 30 31 const FunctionDecl *Func = Call->getDirectCallee(); 32 if (!Func) 33 return false; 34 35 return Func->isNoReturn(); 36 } 37 38 static bool isLiteral(const Expr *E) { 39 return isa<StringLiteral>(E) || isa<CharacterLiteral>(E) || 40 isa<IntegerLiteral>(E) || isa<FloatingLiteral>(E) || 41 isa<CXXBoolLiteralExpr>(E) || isa<CXXNullPtrLiteralExpr>(E); 42 } 43 44 static bool isUnaryExprOfLiteral(const Expr *E) { 45 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) 46 return isLiteral(UnOp->getSubExpr()); 47 return false; 48 } 49 50 static bool shouldBeDefaultMemberInitializer(const Expr *Value) { 51 if (isLiteral(Value) || isUnaryExprOfLiteral(Value)) 52 return true; 53 54 if (const auto *DRE = dyn_cast<DeclRefExpr>(Value)) 55 return isa<EnumConstantDecl>(DRE->getDecl()); 56 57 return false; 58 } 59 60 static const std::pair<const FieldDecl *, const Expr *> 61 isAssignmentToMemberOf(const RecordDecl *Rec, const Stmt *S) { 62 if (const auto *BO = dyn_cast<BinaryOperator>(S)) { 63 if (BO->getOpcode() != BO_Assign) 64 return std::make_pair(nullptr, nullptr); 65 66 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); 67 if (!ME) 68 return std::make_pair(nullptr, nullptr); 69 70 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 71 if (!Field) 72 return std::make_pair(nullptr, nullptr); 73 74 if (isa<CXXThisExpr>(ME->getBase())) 75 return std::make_pair(Field, BO->getRHS()->IgnoreParenImpCasts()); 76 } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { 77 if (COCE->getOperator() != OO_Equal) 78 return std::make_pair(nullptr, nullptr); 79 80 const auto *ME = 81 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); 82 if (!ME) 83 return std::make_pair(nullptr, nullptr); 84 85 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 86 if (!Field) 87 return std::make_pair(nullptr, nullptr); 88 89 if (isa<CXXThisExpr>(ME->getBase())) 90 return std::make_pair(Field, COCE->getArg(1)->IgnoreParenImpCasts()); 91 } 92 93 return std::make_pair(nullptr, nullptr); 94 } 95 96 PreferMemberInitializerCheck::PreferMemberInitializerCheck( 97 StringRef Name, ClangTidyContext *Context) 98 : ClangTidyCheck(Name, Context), 99 IsUseDefaultMemberInitEnabled( 100 Context->isCheckEnabled("modernize-use-default-member-init")), 101 UseAssignment(OptionsView("modernize-use-default-member-init", 102 Context->getOptions().CheckOptions) 103 .get("UseAssignment", false)) {} 104 105 void PreferMemberInitializerCheck::storeOptions( 106 ClangTidyOptions::OptionMap &Opts) { 107 Options.store(Opts, "UseAssignment", UseAssignment); 108 } 109 110 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { 111 Finder->addMatcher( 112 cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated())) 113 .bind("ctor"), 114 this); 115 } 116 117 void PreferMemberInitializerCheck::check( 118 const MatchFinder::MatchResult &Result) { 119 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 120 const auto *Body = cast<CompoundStmt>(Ctor->getBody()); 121 122 const CXXRecordDecl *Class = Ctor->getParent(); 123 SourceLocation InsertPos; 124 bool FirstToCtorInits = true; 125 126 for (const auto *S : Body->body()) { 127 if (isControlStatement(S)) 128 return; 129 130 if (isNoReturnCallStatement(S)) 131 return; 132 133 const FieldDecl *Field; 134 const Expr *InitValue; 135 std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S); 136 if (Field) { 137 if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && 138 Ctor->isDefaultConstructor() && 139 (getLangOpts().CPlusPlus20 || !Field->isBitField()) && 140 (!isa<RecordDecl>(Class->getDeclContext()) || 141 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && 142 shouldBeDefaultMemberInitializer(InitValue)) { 143 auto Diag = 144 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 145 " default member initializer") 146 << Field; 147 148 SourceLocation FieldEnd = 149 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 150 *Result.SourceManager, getLangOpts()); 151 Diag << FixItHint::CreateInsertion(FieldEnd, 152 UseAssignment ? " = " : "{") 153 << FixItHint::CreateInsertionFromRange( 154 FieldEnd, 155 CharSourceRange(InitValue->getSourceRange(), true)) 156 << FixItHint::CreateInsertion(FieldEnd, UseAssignment ? "" : "}"); 157 158 SourceLocation SemiColonEnd = 159 Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager, 160 getLangOpts()) 161 ->getEndLoc(); 162 CharSourceRange StmtRange = 163 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 164 165 Diag << FixItHint::CreateRemoval(StmtRange); 166 } else { 167 auto Diag = 168 diag(S->getBeginLoc(), "%0 should be initialized in a member" 169 " initializer of the constructor") 170 << Field; 171 172 bool AddComma = false; 173 if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) { 174 SourceLocation BodyPos = Ctor->getBody()->getBeginLoc(); 175 SourceLocation NextPos = Ctor->getBeginLoc(); 176 do { 177 InsertPos = NextPos; 178 NextPos = Lexer::findNextToken(NextPos, *Result.SourceManager, 179 getLangOpts()) 180 ->getLocation(); 181 } while (NextPos != BodyPos); 182 InsertPos = Lexer::getLocForEndOfToken( 183 InsertPos, 0, *Result.SourceManager, getLangOpts()); 184 185 Diag << FixItHint::CreateInsertion(InsertPos, " : "); 186 } else { 187 bool Found = false; 188 for (const auto *Init : Ctor->inits()) { 189 if (Result.SourceManager->isBeforeInTranslationUnit( 190 Field->getLocation(), Init->getMember()->getLocation())) { 191 InsertPos = Init->getSourceLocation(); 192 Found = true; 193 break; 194 } 195 } 196 197 if (!Found) { 198 if (Ctor->getNumCtorInitializers()) { 199 InsertPos = Lexer::getLocForEndOfToken( 200 (*Ctor->init_rbegin())->getSourceRange().getEnd(), 0, 201 *Result.SourceManager, getLangOpts()); 202 } 203 Diag << FixItHint::CreateInsertion(InsertPos, ", "); 204 } else { 205 AddComma = true; 206 } 207 } 208 Diag << FixItHint::CreateInsertion(InsertPos, Field->getName()) 209 << FixItHint::CreateInsertion(InsertPos, "(") 210 << FixItHint::CreateInsertionFromRange( 211 InsertPos, 212 CharSourceRange(InitValue->getSourceRange(), true)) 213 << FixItHint::CreateInsertion(InsertPos, ")"); 214 if (AddComma) 215 Diag << FixItHint::CreateInsertion(InsertPos, ", "); 216 217 SourceLocation SemiColonEnd = 218 Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager, 219 getLangOpts()) 220 ->getEndLoc(); 221 CharSourceRange StmtRange = 222 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 223 224 Diag << FixItHint::CreateRemoval(StmtRange); 225 FirstToCtorInits = false; 226 } 227 } 228 } 229 } 230 231 } // namespace cppcoreguidelines 232 } // namespace tidy 233 } // namespace clang 234