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, Context) 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 155 SourceLocation FieldEnd = 156 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 157 *Result.SourceManager, getLangOpts()); 158 SmallString<128> Insertion( 159 {UseAssignment ? " = " : "{", 160 Lexer::getSourceText( 161 CharSourceRange(InitValue->getSourceRange(), true), 162 *Result.SourceManager, getLangOpts()), 163 UseAssignment ? "" : "}"}); 164 165 SourceLocation SemiColonEnd = 166 Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager, 167 getLangOpts()) 168 ->getEndLoc(); 169 CharSourceRange StmtRange = 170 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 171 172 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 173 " default member initializer") 174 << Field << FixItHint::CreateInsertion(FieldEnd, Insertion) 175 << FixItHint::CreateRemoval(StmtRange); 176 } else { 177 SmallString<128> Insertion; 178 bool AddComma = false; 179 if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) { 180 SourceLocation BodyPos = Ctor->getBody()->getBeginLoc(); 181 SourceLocation NextPos = Ctor->getBeginLoc(); 182 do { 183 InsertPos = NextPos; 184 NextPos = Lexer::findNextToken(NextPos, *Result.SourceManager, 185 getLangOpts()) 186 ->getLocation(); 187 } while (NextPos != BodyPos); 188 InsertPos = Lexer::getLocForEndOfToken( 189 InsertPos, 0, *Result.SourceManager, getLangOpts()); 190 191 Insertion = " : "; 192 } else { 193 bool Found = false; 194 unsigned Index = Field->getFieldIndex(); 195 for (const auto *Init : Ctor->inits()) { 196 if (Init->isMemberInitializer()) { 197 if (Index < Init->getMember()->getFieldIndex()) { 198 InsertPos = Init->getSourceLocation(); 199 Found = true; 200 break; 201 } 202 } 203 } 204 205 if (!Found) { 206 if (Ctor->getNumCtorInitializers()) { 207 InsertPos = Lexer::getLocForEndOfToken( 208 (*Ctor->init_rbegin())->getSourceRange().getEnd(), 0, 209 *Result.SourceManager, getLangOpts()); 210 } 211 Insertion = ", "; 212 } else { 213 AddComma = true; 214 } 215 } 216 Insertion.append( 217 {Field->getName(), "(", 218 Lexer::getSourceText( 219 CharSourceRange(InitValue->getSourceRange(), true), 220 *Result.SourceManager, getLangOpts()), 221 AddComma ? "), " : ")"}); 222 223 SourceLocation SemiColonEnd = 224 Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager, 225 getLangOpts()) 226 ->getEndLoc(); 227 CharSourceRange StmtRange = 228 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 229 230 diag(S->getBeginLoc(), "%0 should be initialized in a member" 231 " initializer of the constructor") 232 << Field 233 << FixItHint::CreateInsertion(InsertPos, Insertion, 234 FirstToCtorInits) 235 << FixItHint::CreateRemoval(StmtRange); 236 FirstToCtorInits = false; 237 } 238 } 239 } 240 } 241 242 } // namespace cppcoreguidelines 243 } // namespace tidy 244 } // namespace clang 245