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(cxxConstructorDecl(hasBody(compoundStmt()), 145 unless(isInstantiated()), 146 unless(isDelegatingConstructor())) 147 .bind("ctor"), 148 this); 149 } 150 151 void PreferMemberInitializerCheck::check( 152 const MatchFinder::MatchResult &Result) { 153 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 154 const auto *Body = cast<CompoundStmt>(Ctor->getBody()); 155 156 const CXXRecordDecl *Class = Ctor->getParent(); 157 bool FirstToCtorInits = true; 158 159 for (const Stmt *S : Body->body()) { 160 if (S->getBeginLoc().isMacroID()) { 161 StringRef MacroName = Lexer::getImmediateMacroName( 162 S->getBeginLoc(), *Result.SourceManager, getLangOpts()); 163 if (MacroName.contains_insensitive("assert")) 164 return; 165 } 166 if (isControlStatement(S)) 167 return; 168 169 if (isNoReturnCallStatement(S)) 170 return; 171 172 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) { 173 if (isNoReturnCallStatement(CondOp->getLHS()) || 174 isNoReturnCallStatement(CondOp->getRHS())) 175 return; 176 } 177 178 const FieldDecl *Field = nullptr; 179 const Expr *InitValue = nullptr; 180 std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor); 181 if (Field) { 182 if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && 183 Ctor->isDefaultConstructor() && 184 (getLangOpts().CPlusPlus20 || !Field->isBitField()) && 185 !Field->hasInClassInitializer() && 186 (!isa<RecordDecl>(Class->getDeclContext()) || 187 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && 188 shouldBeDefaultMemberInitializer(InitValue)) { 189 190 bool InvalidFix = false; 191 SourceLocation FieldEnd = 192 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 193 *Result.SourceManager, getLangOpts()); 194 InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID(); 195 SourceLocation SemiColonEnd; 196 if (auto NextToken = Lexer::findNextToken( 197 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 198 SemiColonEnd = NextToken->getEndLoc(); 199 else 200 InvalidFix = true; 201 auto Diag = 202 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 203 " default member initializer") 204 << Field; 205 if (InvalidFix) 206 continue; 207 CharSourceRange StmtRange = 208 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 209 210 SmallString<128> Insertion( 211 {UseAssignment ? " = " : "{", 212 Lexer::getSourceText( 213 CharSourceRange(InitValue->getSourceRange(), true), 214 *Result.SourceManager, getLangOpts()), 215 UseAssignment ? "" : "}"}); 216 217 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) 218 << FixItHint::CreateRemoval(StmtRange); 219 220 } else { 221 StringRef InsertPrefix = ""; 222 bool HasInitAlready = false; 223 SourceLocation InsertPos; 224 SourceRange ReplaceRange; 225 bool AddComma = false; 226 bool InvalidFix = false; 227 unsigned Index = Field->getFieldIndex(); 228 const CXXCtorInitializer *LastInListInit = nullptr; 229 for (const CXXCtorInitializer *Init : Ctor->inits()) { 230 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 231 continue; 232 if (Init->getMember() == Field) { 233 HasInitAlready = true; 234 if (isa<ImplicitValueInitExpr>(Init->getInit())) 235 InsertPos = Init->getRParenLoc(); 236 else { 237 ReplaceRange = Init->getInit()->getSourceRange(); 238 } 239 break; 240 } 241 if (Init->isMemberInitializer() && 242 Index < Init->getMember()->getFieldIndex()) { 243 InsertPos = Init->getSourceLocation(); 244 // There are initializers after the one we are inserting, so add a 245 // comma after this insertion in order to not break anything. 246 AddComma = true; 247 break; 248 } 249 LastInListInit = Init; 250 } 251 if (HasInitAlready) { 252 if (InsertPos.isValid()) 253 InvalidFix |= InsertPos.isMacroID(); 254 else 255 InvalidFix |= ReplaceRange.getBegin().isMacroID() || 256 ReplaceRange.getEnd().isMacroID(); 257 } else { 258 if (InsertPos.isInvalid()) { 259 if (LastInListInit) { 260 InsertPos = Lexer::getLocForEndOfToken( 261 LastInListInit->getRParenLoc(), 0, *Result.SourceManager, 262 getLangOpts()); 263 // Inserting after the last constructor initializer, so we need a 264 // comma. 265 InsertPrefix = ", "; 266 } else { 267 InsertPos = Lexer::getLocForEndOfToken( 268 Ctor->getTypeSourceInfo() 269 ->getTypeLoc() 270 .getAs<clang::FunctionTypeLoc>() 271 .getLocalRangeEnd(), 272 0, *Result.SourceManager, getLangOpts()); 273 274 // If this is first time in the loop, there are no initializers so 275 // `:` declares member initialization list. If this is a 276 // subsequent pass then we have already inserted a `:` so continue 277 // with a comma. 278 InsertPrefix = FirstToCtorInits ? " : " : ", "; 279 } 280 } 281 InvalidFix |= InsertPos.isMacroID(); 282 } 283 284 SourceLocation SemiColonEnd; 285 if (auto NextToken = Lexer::findNextToken( 286 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 287 SemiColonEnd = NextToken->getEndLoc(); 288 else 289 InvalidFix = true; 290 291 auto Diag = 292 diag(S->getBeginLoc(), "%0 should be initialized in a member" 293 " initializer of the constructor") 294 << Field; 295 if (InvalidFix) 296 continue; 297 StringRef NewInit = Lexer::getSourceText( 298 CharSourceRange(InitValue->getSourceRange(), true), 299 *Result.SourceManager, getLangOpts()); 300 if (HasInitAlready) { 301 if (InsertPos.isValid()) 302 Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 303 else 304 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 305 } else { 306 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", 307 NewInit, AddComma ? "), " : ")"}); 308 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 309 FirstToCtorInits); 310 FirstToCtorInits = areDiagsSelfContained(); 311 } 312 Diag << FixItHint::CreateRemoval( 313 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 314 } 315 } 316 } 317 } 318 319 } // namespace clang::tidy::cppcoreguidelines 320