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 namespace { 59 AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) { 60 return Node.getFieldIndex() >= Index; 61 } 62 } // namespace 63 64 // Checks if Field is initialised using a field that will be initialised after 65 // it. 66 // TODO: Probably should guard against function calls that could have side 67 // effects or if they do reference another field that's initialized before this 68 // field, but is modified before the assignment. 69 static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init, 70 const CXXConstructorDecl *Context) { 71 72 auto MemberMatcher = 73 memberExpr(hasObjectExpression(cxxThisExpr()), 74 member(fieldDecl(indexNotLessThan(Field->getFieldIndex())))); 75 76 auto DeclMatcher = declRefExpr( 77 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context))))); 78 79 return match(expr(anyOf(MemberMatcher, DeclMatcher, 80 hasDescendant(MemberMatcher), 81 hasDescendant(DeclMatcher))), 82 *Init, Field->getASTContext()) 83 .empty(); 84 } 85 86 static std::pair<const FieldDecl *, const Expr *> 87 isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, 88 const CXXConstructorDecl *Ctor) { 89 if (const auto *BO = dyn_cast<BinaryOperator>(S)) { 90 if (BO->getOpcode() != BO_Assign) 91 return std::make_pair(nullptr, nullptr); 92 93 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); 94 if (!ME) 95 return std::make_pair(nullptr, nullptr); 96 97 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 98 if (!Field) 99 return std::make_pair(nullptr, nullptr); 100 101 if (!isa<CXXThisExpr>(ME->getBase())) 102 return std::make_pair(nullptr, nullptr); 103 const Expr *Init = BO->getRHS()->IgnoreParenImpCasts(); 104 if (isSafeAssignment(Field, Init, Ctor)) 105 return std::make_pair(Field, Init); 106 } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { 107 if (COCE->getOperator() != OO_Equal) 108 return std::make_pair(nullptr, nullptr); 109 110 const auto *ME = 111 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); 112 if (!ME) 113 return std::make_pair(nullptr, nullptr); 114 115 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 116 if (!Field) 117 return std::make_pair(nullptr, nullptr); 118 119 if (!isa<CXXThisExpr>(ME->getBase())) 120 return std::make_pair(nullptr, nullptr); 121 const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts(); 122 if (isSafeAssignment(Field, Init, Ctor)) 123 return std::make_pair(Field, Init); 124 } 125 126 return std::make_pair(nullptr, nullptr); 127 } 128 129 PreferMemberInitializerCheck::PreferMemberInitializerCheck( 130 StringRef Name, ClangTidyContext *Context) 131 : ClangTidyCheck(Name, Context), 132 IsUseDefaultMemberInitEnabled( 133 Context->isCheckEnabled("modernize-use-default-member-init")), 134 UseAssignment(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 } 310 Diag << FixItHint::CreateRemoval( 311 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 312 FirstToCtorInits = false; 313 } 314 } 315 } 316 } 317 318 } // namespace cppcoreguidelines 319 } // namespace tidy 320 } // namespace clang 321