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