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 continue; 183 const bool IsInDefaultMemberInitializer = 184 IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && 185 Ctor->isDefaultConstructor() && 186 (getLangOpts().CPlusPlus20 || !Field->isBitField()) && 187 !Field->hasInClassInitializer() && 188 (!isa<RecordDecl>(Class->getDeclContext()) || 189 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && 190 shouldBeDefaultMemberInitializer(InitValue); 191 if (IsInDefaultMemberInitializer) { 192 bool InvalidFix = false; 193 SourceLocation FieldEnd = 194 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 195 *Result.SourceManager, getLangOpts()); 196 InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID(); 197 SourceLocation SemiColonEnd; 198 if (auto NextToken = Lexer::findNextToken( 199 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 200 SemiColonEnd = NextToken->getEndLoc(); 201 else 202 InvalidFix = true; 203 auto Diag = 204 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 205 " default member initializer") 206 << Field; 207 if (InvalidFix) 208 continue; 209 CharSourceRange StmtRange = 210 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 211 212 SmallString<128> Insertion( 213 {UseAssignment ? " = " : "{", 214 Lexer::getSourceText( 215 CharSourceRange(InitValue->getSourceRange(), true), 216 *Result.SourceManager, getLangOpts()), 217 UseAssignment ? "" : "}"}); 218 219 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) 220 << FixItHint::CreateRemoval(StmtRange); 221 222 } else { 223 StringRef InsertPrefix = ""; 224 bool HasInitAlready = false; 225 SourceLocation InsertPos; 226 SourceRange ReplaceRange; 227 bool AddComma = false; 228 bool InvalidFix = false; 229 unsigned Index = Field->getFieldIndex(); 230 const CXXCtorInitializer *LastInListInit = nullptr; 231 for (const CXXCtorInitializer *Init : Ctor->inits()) { 232 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 233 continue; 234 if (Init->getMember() == Field) { 235 HasInitAlready = true; 236 if (isa<ImplicitValueInitExpr>(Init->getInit())) 237 InsertPos = Init->getRParenLoc(); 238 else { 239 ReplaceRange = Init->getInit()->getSourceRange(); 240 } 241 break; 242 } 243 if (Init->isMemberInitializer() && 244 Index < Init->getMember()->getFieldIndex()) { 245 InsertPos = Init->getSourceLocation(); 246 // There are initializers after the one we are inserting, so add a 247 // comma after this insertion in order to not break anything. 248 AddComma = true; 249 break; 250 } 251 LastInListInit = Init; 252 } 253 if (HasInitAlready) { 254 if (InsertPos.isValid()) 255 InvalidFix |= InsertPos.isMacroID(); 256 else 257 InvalidFix |= ReplaceRange.getBegin().isMacroID() || 258 ReplaceRange.getEnd().isMacroID(); 259 } else { 260 if (InsertPos.isInvalid()) { 261 if (LastInListInit) { 262 InsertPos = Lexer::getLocForEndOfToken( 263 LastInListInit->getRParenLoc(), 0, *Result.SourceManager, 264 getLangOpts()); 265 // Inserting after the last constructor initializer, so we need a 266 // comma. 267 InsertPrefix = ", "; 268 } else { 269 InsertPos = Lexer::getLocForEndOfToken( 270 Ctor->getTypeSourceInfo() 271 ->getTypeLoc() 272 .getAs<clang::FunctionTypeLoc>() 273 .getLocalRangeEnd(), 274 0, *Result.SourceManager, getLangOpts()); 275 276 // If this is first time in the loop, there are no initializers so 277 // `:` declares member initialization list. If this is a 278 // subsequent pass then we have already inserted a `:` so continue 279 // with a comma. 280 InsertPrefix = FirstToCtorInits ? " : " : ", "; 281 } 282 } 283 InvalidFix |= InsertPos.isMacroID(); 284 } 285 286 SourceLocation SemiColonEnd; 287 if (auto NextToken = Lexer::findNextToken( 288 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 289 SemiColonEnd = NextToken->getEndLoc(); 290 else 291 InvalidFix = true; 292 293 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member" 294 " initializer of the constructor") 295 << Field; 296 if (InvalidFix) 297 continue; 298 StringRef NewInit = Lexer::getSourceText( 299 CharSourceRange(InitValue->getSourceRange(), true), 300 *Result.SourceManager, getLangOpts()); 301 if (HasInitAlready) { 302 if (InsertPos.isValid()) 303 Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 304 else 305 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 306 } else { 307 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", 308 NewInit, AddComma ? "), " : ")"}); 309 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 310 FirstToCtorInits); 311 FirstToCtorInits = areDiagsSelfContained(); 312 } 313 Diag << FixItHint::CreateRemoval( 314 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 315 } 316 } 317 } 318 319 } // namespace clang::tidy::cppcoreguidelines 320