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::tidy::cppcoreguidelines { 17 18 static bool isControlStatement(const Stmt *S) { 19 return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt, 20 GotoStmt, CXXTryStmt, CXXThrowExpr>(S); 21 } 22 23 static bool isNoReturnCallStatement(const Stmt *S) { 24 const auto *Call = dyn_cast<CallExpr>(S); 25 if (!Call) 26 return false; 27 28 const FunctionDecl *Func = Call->getDirectCallee(); 29 if (!Func) 30 return false; 31 32 return Func->isNoReturn(); 33 } 34 35 static bool isLiteral(const Expr *E) { 36 return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral, 37 CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E); 38 } 39 40 static bool isUnaryExprOfLiteral(const Expr *E) { 41 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) 42 return isLiteral(UnOp->getSubExpr()); 43 return false; 44 } 45 46 static bool shouldBeDefaultMemberInitializer(const Expr *Value) { 47 if (isLiteral(Value) || isUnaryExprOfLiteral(Value)) 48 return true; 49 50 if (const auto *DRE = dyn_cast<DeclRefExpr>(Value)) 51 return isa<EnumConstantDecl>(DRE->getDecl()); 52 53 return false; 54 } 55 56 namespace { 57 AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) { 58 return Node.getFieldIndex() >= Index; 59 } 60 } // namespace 61 62 // Checks if Field is initialised using a field that will be initialised after 63 // it. 64 // TODO: Probably should guard against function calls that could have side 65 // effects or if they do reference another field that's initialized before this 66 // field, but is modified before the assignment. 67 static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init, 68 const CXXConstructorDecl *Context) { 69 70 auto MemberMatcher = 71 memberExpr(hasObjectExpression(cxxThisExpr()), 72 member(fieldDecl(indexNotLessThan(Field->getFieldIndex())))); 73 74 auto DeclMatcher = declRefExpr( 75 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context))))); 76 77 return match(expr(anyOf(MemberMatcher, DeclMatcher, 78 hasDescendant(MemberMatcher), 79 hasDescendant(DeclMatcher))), 80 *Init, Field->getASTContext()) 81 .empty(); 82 } 83 84 static std::pair<const FieldDecl *, const Expr *> 85 isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, 86 const CXXConstructorDecl *Ctor) { 87 if (const auto *BO = dyn_cast<BinaryOperator>(S)) { 88 if (BO->getOpcode() != BO_Assign) 89 return std::make_pair(nullptr, nullptr); 90 91 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); 92 if (!ME) 93 return std::make_pair(nullptr, nullptr); 94 95 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 96 if (!Field) 97 return std::make_pair(nullptr, nullptr); 98 99 if (!isa<CXXThisExpr>(ME->getBase())) 100 return std::make_pair(nullptr, nullptr); 101 const Expr *Init = BO->getRHS()->IgnoreParenImpCasts(); 102 if (isSafeAssignment(Field, Init, Ctor)) 103 return std::make_pair(Field, Init); 104 } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { 105 if (COCE->getOperator() != OO_Equal) 106 return std::make_pair(nullptr, nullptr); 107 108 const auto *ME = 109 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); 110 if (!ME) 111 return std::make_pair(nullptr, nullptr); 112 113 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 114 if (!Field) 115 return std::make_pair(nullptr, nullptr); 116 117 if (!isa<CXXThisExpr>(ME->getBase())) 118 return std::make_pair(nullptr, nullptr); 119 const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts(); 120 if (isSafeAssignment(Field, Init, Ctor)) 121 return std::make_pair(Field, Init); 122 } 123 124 return std::make_pair(nullptr, nullptr); 125 } 126 127 PreferMemberInitializerCheck::PreferMemberInitializerCheck( 128 StringRef Name, ClangTidyContext *Context) 129 : ClangTidyCheck(Name, Context), 130 IsUseDefaultMemberInitEnabled( 131 Context->isCheckEnabled("modernize-use-default-member-init")), 132 UseAssignment( 133 Options.get("UseAssignment", 134 OptionsView("modernize-use-default-member-init", 135 Context->getOptions().CheckOptions, Context) 136 .get("UseAssignment", false))) {} 137 138 void PreferMemberInitializerCheck::storeOptions( 139 ClangTidyOptions::OptionMap &Opts) { 140 Options.store(Opts, "UseAssignment", UseAssignment); 141 } 142 143 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { 144 Finder->addMatcher( 145 cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated())) 146 .bind("ctor"), 147 this); 148 } 149 150 void PreferMemberInitializerCheck::check( 151 const MatchFinder::MatchResult &Result) { 152 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 153 const auto *Body = cast<CompoundStmt>(Ctor->getBody()); 154 155 const CXXRecordDecl *Class = Ctor->getParent(); 156 bool FirstToCtorInits = true; 157 158 for (const Stmt *S : Body->body()) { 159 if (S->getBeginLoc().isMacroID()) { 160 StringRef MacroName = Lexer::getImmediateMacroName( 161 S->getBeginLoc(), *Result.SourceManager, getLangOpts()); 162 if (MacroName.contains_insensitive("assert")) 163 return; 164 } 165 if (isControlStatement(S)) 166 return; 167 168 if (isNoReturnCallStatement(S)) 169 return; 170 171 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) { 172 if (isNoReturnCallStatement(CondOp->getLHS()) || 173 isNoReturnCallStatement(CondOp->getRHS())) 174 return; 175 } 176 177 const FieldDecl *Field; 178 const Expr *InitValue; 179 std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor); 180 if (Field) { 181 if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && 182 Ctor->isDefaultConstructor() && 183 (getLangOpts().CPlusPlus20 || !Field->isBitField()) && 184 !Field->hasInClassInitializer() && 185 (!isa<RecordDecl>(Class->getDeclContext()) || 186 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && 187 shouldBeDefaultMemberInitializer(InitValue)) { 188 189 bool InvalidFix = false; 190 SourceLocation FieldEnd = 191 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 192 *Result.SourceManager, getLangOpts()); 193 InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID(); 194 SourceLocation SemiColonEnd; 195 if (auto NextToken = Lexer::findNextToken( 196 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 197 SemiColonEnd = NextToken->getEndLoc(); 198 else 199 InvalidFix = true; 200 auto Diag = 201 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 202 " default member initializer") 203 << Field; 204 if (InvalidFix) 205 continue; 206 CharSourceRange StmtRange = 207 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 208 209 SmallString<128> Insertion( 210 {UseAssignment ? " = " : "{", 211 Lexer::getSourceText( 212 CharSourceRange(InitValue->getSourceRange(), true), 213 *Result.SourceManager, getLangOpts()), 214 UseAssignment ? "" : "}"}); 215 216 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) 217 << FixItHint::CreateRemoval(StmtRange); 218 219 } else { 220 StringRef InsertPrefix = ""; 221 bool HasInitAlready = false; 222 SourceLocation InsertPos; 223 SourceRange ReplaceRange; 224 bool AddComma = false; 225 bool InvalidFix = false; 226 unsigned Index = Field->getFieldIndex(); 227 const CXXCtorInitializer *LastInListInit = nullptr; 228 for (const CXXCtorInitializer *Init : Ctor->inits()) { 229 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 230 continue; 231 if (Init->getMember() == Field) { 232 HasInitAlready = true; 233 if (isa<ImplicitValueInitExpr>(Init->getInit())) 234 InsertPos = Init->getRParenLoc(); 235 else { 236 ReplaceRange = Init->getInit()->getSourceRange(); 237 } 238 break; 239 } 240 if (Init->isMemberInitializer() && 241 Index < Init->getMember()->getFieldIndex()) { 242 InsertPos = Init->getSourceLocation(); 243 // There are initializers after the one we are inserting, so add a 244 // comma after this insertion in order to not break anything. 245 AddComma = true; 246 break; 247 } 248 LastInListInit = Init; 249 } 250 if (HasInitAlready) { 251 if (InsertPos.isValid()) 252 InvalidFix |= InsertPos.isMacroID(); 253 else 254 InvalidFix |= ReplaceRange.getBegin().isMacroID() || 255 ReplaceRange.getEnd().isMacroID(); 256 } else { 257 if (InsertPos.isInvalid()) { 258 if (LastInListInit) { 259 InsertPos = Lexer::getLocForEndOfToken( 260 LastInListInit->getRParenLoc(), 0, *Result.SourceManager, 261 getLangOpts()); 262 // Inserting after the last constructor initializer, so we need a 263 // comma. 264 InsertPrefix = ", "; 265 } else { 266 InsertPos = Lexer::getLocForEndOfToken( 267 Ctor->getTypeSourceInfo() 268 ->getTypeLoc() 269 .getAs<clang::FunctionTypeLoc>() 270 .getLocalRangeEnd(), 271 0, *Result.SourceManager, getLangOpts()); 272 273 // If this is first time in the loop, there are no initializers so 274 // `:` declares member initialization list. If this is a 275 // subsequent pass then we have already inserted a `:` so continue 276 // with a comma. 277 InsertPrefix = FirstToCtorInits ? " : " : ", "; 278 } 279 } 280 InvalidFix |= InsertPos.isMacroID(); 281 } 282 283 SourceLocation SemiColonEnd; 284 if (auto NextToken = Lexer::findNextToken( 285 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 286 SemiColonEnd = NextToken->getEndLoc(); 287 else 288 InvalidFix = true; 289 290 auto Diag = 291 diag(S->getBeginLoc(), "%0 should be initialized in a member" 292 " initializer of the constructor") 293 << Field; 294 if (InvalidFix) 295 continue; 296 StringRef NewInit = Lexer::getSourceText( 297 CharSourceRange(InitValue->getSourceRange(), true), 298 *Result.SourceManager, getLangOpts()); 299 if (HasInitAlready) { 300 if (InsertPos.isValid()) 301 Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 302 else 303 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 304 } else { 305 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", 306 NewInit, AddComma ? "), " : ")"}); 307 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 308 FirstToCtorInits); 309 FirstToCtorInits = areDiagsSelfContained(); 310 } 311 Diag << FixItHint::CreateRemoval( 312 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 313 } 314 } 315 } 316 } 317 318 } // namespace clang::tidy::cppcoreguidelines 319