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/AST/Decl.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/Lex/Lexer.h" 14 #include "llvm/ADT/DenseMap.h" 15 16 using namespace clang::ast_matchers; 17 18 namespace clang::tidy::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 60 AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) { 61 return Node.getFieldIndex() >= Index; 62 } 63 64 enum class AssignedLevel { 65 // Field is not assigned. 66 None, 67 // Field is assigned. 68 Default, 69 // Assignment of field has side effect: 70 // - assign to reference. 71 // FIXME: support other side effect. 72 HasSideEffect, 73 // Assignment of field has data dependence. 74 HasDependence, 75 }; 76 77 } // namespace 78 79 static bool canAdvanceAssignment(AssignedLevel Level) { 80 return Level == AssignedLevel::None || Level == AssignedLevel::Default; 81 } 82 83 // Checks if Field is initialised using a field that will be initialised after 84 // it. 85 // TODO: Probably should guard against function calls that could have side 86 // effects or if they do reference another field that's initialized before 87 // this field, but is modified before the assignment. 88 static void updateAssignmentLevel( 89 const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor, 90 llvm::DenseMap<const FieldDecl *, AssignedLevel> &AssignedFields) { 91 auto It = AssignedFields.find(Field); 92 if (It == AssignedFields.end()) 93 It = AssignedFields.insert({Field, AssignedLevel::None}).first; 94 95 if (!canAdvanceAssignment(It->second)) 96 // fast path for already decided field. 97 return; 98 99 if (Field->getType().getCanonicalType()->isReferenceType()) { 100 // assign to reference type twice cannot be simplified to once. 101 It->second = AssignedLevel::HasSideEffect; 102 return; 103 } 104 105 auto MemberMatcher = 106 memberExpr(hasObjectExpression(cxxThisExpr()), 107 member(fieldDecl(indexNotLessThan(Field->getFieldIndex())))); 108 auto DeclMatcher = declRefExpr( 109 to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor))))); 110 const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher, 111 hasDescendant(MemberMatcher), 112 hasDescendant(DeclMatcher))), 113 *Init, Field->getASTContext()) 114 .empty(); 115 if (HasDependence) { 116 It->second = AssignedLevel::HasDependence; 117 return; 118 } 119 } 120 121 struct AssignmentPair { 122 const FieldDecl *Field; 123 const Expr *Init; 124 }; 125 126 static std::optional<AssignmentPair> 127 isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, 128 const CXXConstructorDecl *Ctor) { 129 if (const auto *BO = dyn_cast<BinaryOperator>(S)) { 130 if (BO->getOpcode() != BO_Assign) 131 return {}; 132 133 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); 134 if (!ME) 135 return {}; 136 137 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 138 if (!Field) 139 return {}; 140 141 if (!isa<CXXThisExpr>(ME->getBase())) 142 return {}; 143 const Expr *Init = BO->getRHS()->IgnoreParenImpCasts(); 144 return AssignmentPair{Field, Init}; 145 } 146 if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { 147 if (COCE->getOperator() != OO_Equal) 148 return {}; 149 150 const auto *ME = 151 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); 152 if (!ME) 153 return {}; 154 155 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 156 if (!Field) 157 return {}; 158 159 if (!isa<CXXThisExpr>(ME->getBase())) 160 return {}; 161 const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts(); 162 return AssignmentPair{Field, Init}; 163 } 164 return {}; 165 } 166 167 PreferMemberInitializerCheck::PreferMemberInitializerCheck( 168 StringRef Name, ClangTidyContext *Context) 169 : ClangTidyCheck(Name, Context), 170 IsUseDefaultMemberInitEnabled( 171 Context->isCheckEnabled("modernize-use-default-member-init")), 172 UseAssignment( 173 Options.get("UseAssignment", 174 OptionsView("modernize-use-default-member-init", 175 Context->getOptions().CheckOptions, Context) 176 .get("UseAssignment", false))) {} 177 178 void PreferMemberInitializerCheck::storeOptions( 179 ClangTidyOptions::OptionMap &Opts) { 180 Options.store(Opts, "UseAssignment", UseAssignment); 181 } 182 183 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { 184 Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()), 185 unless(isInstantiated()), 186 unless(isDelegatingConstructor())) 187 .bind("ctor"), 188 this); 189 } 190 191 void PreferMemberInitializerCheck::check( 192 const MatchFinder::MatchResult &Result) { 193 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 194 const auto *Body = cast<CompoundStmt>(Ctor->getBody()); 195 196 const CXXRecordDecl *Class = Ctor->getParent(); 197 bool FirstToCtorInits = true; 198 199 llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{}; 200 201 for (const CXXCtorInitializer *Init : Ctor->inits()) 202 if (FieldDecl *Field = Init->getMember()) 203 updateAssignmentLevel(Field, Init->getInit(), Ctor, AssignedFields); 204 205 for (const Stmt *S : Body->body()) { 206 if (S->getBeginLoc().isMacroID()) { 207 StringRef MacroName = Lexer::getImmediateMacroName( 208 S->getBeginLoc(), *Result.SourceManager, getLangOpts()); 209 if (MacroName.contains_insensitive("assert")) 210 return; 211 } 212 if (isControlStatement(S)) 213 return; 214 215 if (isNoReturnCallStatement(S)) 216 return; 217 218 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) { 219 if (isNoReturnCallStatement(CondOp->getLHS()) || 220 isNoReturnCallStatement(CondOp->getRHS())) 221 return; 222 } 223 224 std::optional<AssignmentPair> AssignmentToMember = 225 isAssignmentToMemberOf(Class, S, Ctor); 226 if (!AssignmentToMember) 227 continue; 228 const FieldDecl *Field = AssignmentToMember->Field; 229 const Expr *InitValue = AssignmentToMember->Init; 230 updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields); 231 if (!canAdvanceAssignment(AssignedFields[Field])) 232 continue; 233 const bool IsInDefaultMemberInitializer = 234 IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && 235 Ctor->isDefaultConstructor() && 236 (getLangOpts().CPlusPlus20 || !Field->isBitField()) && 237 !Field->hasInClassInitializer() && 238 (!isa<RecordDecl>(Class->getDeclContext()) || 239 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && 240 shouldBeDefaultMemberInitializer(InitValue); 241 if (IsInDefaultMemberInitializer) { 242 bool InvalidFix = false; 243 SourceLocation FieldEnd = 244 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 245 *Result.SourceManager, getLangOpts()); 246 InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID(); 247 SourceLocation SemiColonEnd; 248 if (auto NextToken = Lexer::findNextToken( 249 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 250 SemiColonEnd = NextToken->getEndLoc(); 251 else 252 InvalidFix = true; 253 auto Diag = 254 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 255 " default member initializer") 256 << Field; 257 if (InvalidFix) 258 continue; 259 CharSourceRange StmtRange = 260 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 261 262 SmallString<128> Insertion( 263 {UseAssignment ? " = " : "{", 264 Lexer::getSourceText(Result.SourceManager->getExpansionRange( 265 InitValue->getSourceRange()), 266 *Result.SourceManager, getLangOpts()), 267 UseAssignment ? "" : "}"}); 268 269 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) 270 << FixItHint::CreateRemoval(StmtRange); 271 272 } else { 273 StringRef InsertPrefix = ""; 274 bool HasInitAlready = false; 275 SourceLocation InsertPos; 276 SourceRange ReplaceRange; 277 bool AddComma = false; 278 bool InvalidFix = false; 279 unsigned Index = Field->getFieldIndex(); 280 const CXXCtorInitializer *LastInListInit = nullptr; 281 for (const CXXCtorInitializer *Init : Ctor->inits()) { 282 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 283 continue; 284 if (Init->getMember() == Field) { 285 HasInitAlready = true; 286 if (isa<ImplicitValueInitExpr>(Init->getInit())) 287 InsertPos = Init->getRParenLoc(); 288 else { 289 ReplaceRange = Init->getInit()->getSourceRange(); 290 } 291 break; 292 } 293 if (Init->isMemberInitializer() && 294 Index < Init->getMember()->getFieldIndex()) { 295 InsertPos = Init->getSourceLocation(); 296 // There are initializers after the one we are inserting, so add a 297 // comma after this insertion in order to not break anything. 298 AddComma = true; 299 break; 300 } 301 LastInListInit = Init; 302 } 303 if (HasInitAlready) { 304 if (InsertPos.isValid()) 305 InvalidFix |= InsertPos.isMacroID(); 306 else 307 InvalidFix |= ReplaceRange.getBegin().isMacroID() || 308 ReplaceRange.getEnd().isMacroID(); 309 } else { 310 if (InsertPos.isInvalid()) { 311 if (LastInListInit) { 312 InsertPos = Lexer::getLocForEndOfToken( 313 LastInListInit->getRParenLoc(), 0, *Result.SourceManager, 314 getLangOpts()); 315 // Inserting after the last constructor initializer, so we need a 316 // comma. 317 InsertPrefix = ", "; 318 } else { 319 InsertPos = Lexer::getLocForEndOfToken( 320 Ctor->getTypeSourceInfo() 321 ->getTypeLoc() 322 .getAs<clang::FunctionTypeLoc>() 323 .getLocalRangeEnd(), 324 0, *Result.SourceManager, getLangOpts()); 325 326 // If this is first time in the loop, there are no initializers so 327 // `:` declares member initialization list. If this is a 328 // subsequent pass then we have already inserted a `:` so continue 329 // with a comma. 330 InsertPrefix = FirstToCtorInits ? " : " : ", "; 331 } 332 } 333 InvalidFix |= InsertPos.isMacroID(); 334 } 335 336 SourceLocation SemiColonEnd; 337 if (auto NextToken = Lexer::findNextToken( 338 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 339 SemiColonEnd = NextToken->getEndLoc(); 340 else 341 InvalidFix = true; 342 343 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member" 344 " initializer of the constructor") 345 << Field; 346 if (InvalidFix) 347 continue; 348 StringRef NewInit = Lexer::getSourceText( 349 Result.SourceManager->getExpansionRange(InitValue->getSourceRange()), 350 *Result.SourceManager, getLangOpts()); 351 if (HasInitAlready) { 352 if (InsertPos.isValid()) 353 Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 354 else 355 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 356 } else { 357 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", 358 NewInit, AddComma ? "), " : ")"}); 359 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 360 FirstToCtorInits); 361 FirstToCtorInits = areDiagsSelfContained(); 362 } 363 Diag << FixItHint::CreateRemoval( 364 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 365 } 366 } 367 } 368 369 } // namespace clang::tidy::cppcoreguidelines 370