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 static std::pair<const FieldDecl *, const Expr *> 122 isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, 123 const CXXConstructorDecl *Ctor) { 124 if (const auto *BO = dyn_cast<BinaryOperator>(S)) { 125 if (BO->getOpcode() != BO_Assign) 126 return std::make_pair(nullptr, nullptr); 127 128 const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); 129 if (!ME) 130 return std::make_pair(nullptr, nullptr); 131 132 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 133 if (!Field) 134 return std::make_pair(nullptr, nullptr); 135 136 if (!isa<CXXThisExpr>(ME->getBase())) 137 return std::make_pair(nullptr, nullptr); 138 const Expr *Init = BO->getRHS()->IgnoreParenImpCasts(); 139 return std::make_pair(Field, Init); 140 } 141 if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { 142 if (COCE->getOperator() != OO_Equal) 143 return std::make_pair(nullptr, nullptr); 144 145 const auto *ME = 146 dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); 147 if (!ME) 148 return std::make_pair(nullptr, nullptr); 149 150 const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); 151 if (!Field) 152 return std::make_pair(nullptr, nullptr); 153 154 if (!isa<CXXThisExpr>(ME->getBase())) 155 return std::make_pair(nullptr, nullptr); 156 const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts(); 157 return std::make_pair(Field, Init); 158 } 159 return std::make_pair(nullptr, nullptr); 160 } 161 162 PreferMemberInitializerCheck::PreferMemberInitializerCheck( 163 StringRef Name, ClangTidyContext *Context) 164 : ClangTidyCheck(Name, Context), 165 IsUseDefaultMemberInitEnabled( 166 Context->isCheckEnabled("modernize-use-default-member-init")), 167 UseAssignment( 168 Options.get("UseAssignment", 169 OptionsView("modernize-use-default-member-init", 170 Context->getOptions().CheckOptions, Context) 171 .get("UseAssignment", false))) {} 172 173 void PreferMemberInitializerCheck::storeOptions( 174 ClangTidyOptions::OptionMap &Opts) { 175 Options.store(Opts, "UseAssignment", UseAssignment); 176 } 177 178 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { 179 Finder->addMatcher(cxxConstructorDecl(hasBody(compoundStmt()), 180 unless(isInstantiated()), 181 unless(isDelegatingConstructor())) 182 .bind("ctor"), 183 this); 184 } 185 186 void PreferMemberInitializerCheck::check( 187 const MatchFinder::MatchResult &Result) { 188 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 189 const auto *Body = cast<CompoundStmt>(Ctor->getBody()); 190 191 const CXXRecordDecl *Class = Ctor->getParent(); 192 bool FirstToCtorInits = true; 193 194 llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{}; 195 196 for (const CXXCtorInitializer *Init : Ctor->inits()) 197 if (FieldDecl *Field = Init->getMember()) 198 updateAssignmentLevel(Field, Init->getInit(), Ctor, AssignedFields); 199 200 for (const Stmt *S : Body->body()) { 201 if (S->getBeginLoc().isMacroID()) { 202 StringRef MacroName = Lexer::getImmediateMacroName( 203 S->getBeginLoc(), *Result.SourceManager, getLangOpts()); 204 if (MacroName.contains_insensitive("assert")) 205 return; 206 } 207 if (isControlStatement(S)) 208 return; 209 210 if (isNoReturnCallStatement(S)) 211 return; 212 213 if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) { 214 if (isNoReturnCallStatement(CondOp->getLHS()) || 215 isNoReturnCallStatement(CondOp->getRHS())) 216 return; 217 } 218 219 const FieldDecl *Field = nullptr; 220 const Expr *InitValue = nullptr; 221 std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor); 222 if (!Field) 223 continue; 224 updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields); 225 if (!canAdvanceAssignment(AssignedFields[Field])) 226 continue; 227 const bool IsInDefaultMemberInitializer = 228 IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && 229 Ctor->isDefaultConstructor() && 230 (getLangOpts().CPlusPlus20 || !Field->isBitField()) && 231 !Field->hasInClassInitializer() && 232 (!isa<RecordDecl>(Class->getDeclContext()) || 233 !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && 234 shouldBeDefaultMemberInitializer(InitValue); 235 if (IsInDefaultMemberInitializer) { 236 bool InvalidFix = false; 237 SourceLocation FieldEnd = 238 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, 239 *Result.SourceManager, getLangOpts()); 240 InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID(); 241 SourceLocation SemiColonEnd; 242 if (auto NextToken = Lexer::findNextToken( 243 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 244 SemiColonEnd = NextToken->getEndLoc(); 245 else 246 InvalidFix = true; 247 auto Diag = 248 diag(S->getBeginLoc(), "%0 should be initialized in an in-class" 249 " default member initializer") 250 << Field; 251 if (InvalidFix) 252 continue; 253 CharSourceRange StmtRange = 254 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 255 256 SmallString<128> Insertion( 257 {UseAssignment ? " = " : "{", 258 Lexer::getSourceText( 259 CharSourceRange(InitValue->getSourceRange(), true), 260 *Result.SourceManager, getLangOpts()), 261 UseAssignment ? "" : "}"}); 262 263 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) 264 << FixItHint::CreateRemoval(StmtRange); 265 266 } else { 267 StringRef InsertPrefix = ""; 268 bool HasInitAlready = false; 269 SourceLocation InsertPos; 270 SourceRange ReplaceRange; 271 bool AddComma = false; 272 bool InvalidFix = false; 273 unsigned Index = Field->getFieldIndex(); 274 const CXXCtorInitializer *LastInListInit = nullptr; 275 for (const CXXCtorInitializer *Init : Ctor->inits()) { 276 if (!Init->isWritten() || Init->isInClassMemberInitializer()) 277 continue; 278 if (Init->getMember() == Field) { 279 HasInitAlready = true; 280 if (isa<ImplicitValueInitExpr>(Init->getInit())) 281 InsertPos = Init->getRParenLoc(); 282 else { 283 ReplaceRange = Init->getInit()->getSourceRange(); 284 } 285 break; 286 } 287 if (Init->isMemberInitializer() && 288 Index < Init->getMember()->getFieldIndex()) { 289 InsertPos = Init->getSourceLocation(); 290 // There are initializers after the one we are inserting, so add a 291 // comma after this insertion in order to not break anything. 292 AddComma = true; 293 break; 294 } 295 LastInListInit = Init; 296 } 297 if (HasInitAlready) { 298 if (InsertPos.isValid()) 299 InvalidFix |= InsertPos.isMacroID(); 300 else 301 InvalidFix |= ReplaceRange.getBegin().isMacroID() || 302 ReplaceRange.getEnd().isMacroID(); 303 } else { 304 if (InsertPos.isInvalid()) { 305 if (LastInListInit) { 306 InsertPos = Lexer::getLocForEndOfToken( 307 LastInListInit->getRParenLoc(), 0, *Result.SourceManager, 308 getLangOpts()); 309 // Inserting after the last constructor initializer, so we need a 310 // comma. 311 InsertPrefix = ", "; 312 } else { 313 InsertPos = Lexer::getLocForEndOfToken( 314 Ctor->getTypeSourceInfo() 315 ->getTypeLoc() 316 .getAs<clang::FunctionTypeLoc>() 317 .getLocalRangeEnd(), 318 0, *Result.SourceManager, getLangOpts()); 319 320 // If this is first time in the loop, there are no initializers so 321 // `:` declares member initialization list. If this is a 322 // subsequent pass then we have already inserted a `:` so continue 323 // with a comma. 324 InsertPrefix = FirstToCtorInits ? " : " : ", "; 325 } 326 } 327 InvalidFix |= InsertPos.isMacroID(); 328 } 329 330 SourceLocation SemiColonEnd; 331 if (auto NextToken = Lexer::findNextToken( 332 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 333 SemiColonEnd = NextToken->getEndLoc(); 334 else 335 InvalidFix = true; 336 337 auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member" 338 " initializer of the constructor") 339 << Field; 340 if (InvalidFix) 341 continue; 342 StringRef NewInit = Lexer::getSourceText( 343 CharSourceRange(InitValue->getSourceRange(), true), 344 *Result.SourceManager, getLangOpts()); 345 if (HasInitAlready) { 346 if (InsertPos.isValid()) 347 Diag << FixItHint::CreateInsertion(InsertPos, NewInit); 348 else 349 Diag << FixItHint::CreateReplacement(ReplaceRange, NewInit); 350 } else { 351 SmallString<128> Insertion({InsertPrefix, Field->getName(), "(", 352 NewInit, AddComma ? "), " : ")"}); 353 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 354 FirstToCtorInits); 355 FirstToCtorInits = areDiagsSelfContained(); 356 } 357 Diag << FixItHint::CreateRemoval( 358 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd)); 359 } 360 } 361 } 362 363 } // namespace clang::tidy::cppcoreguidelines 364