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 CharSourceRange StmtRange = 206 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 207 208 SmallString<128> Insertion( 209 {UseAssignment ? " = " : "{", 210 Lexer::getSourceText( 211 CharSourceRange(InitValue->getSourceRange(), true), 212 *Result.SourceManager, getLangOpts()), 213 UseAssignment ? "" : "}"}); 214 215 Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) 216 << FixItHint::CreateRemoval(StmtRange); 217 } 218 } else { 219 StringRef InsertPrefix = ""; 220 SourceLocation InsertPos; 221 bool AddComma = false; 222 bool InvalidFix = false; 223 unsigned Index = Field->getFieldIndex(); 224 const CXXCtorInitializer *LastInListInit = nullptr; 225 for (const CXXCtorInitializer *Init : Ctor->inits()) { 226 if (!Init->isWritten()) 227 continue; 228 if (Init->isMemberInitializer() && 229 Index < Init->getMember()->getFieldIndex()) { 230 InsertPos = Init->getSourceLocation(); 231 // There are initializers after the one we are inserting, so add a 232 // comma after this insertion in order to not break anything. 233 AddComma = true; 234 break; 235 } 236 LastInListInit = Init; 237 } 238 if (InsertPos.isInvalid()) { 239 if (LastInListInit) { 240 InsertPos = Lexer::getLocForEndOfToken( 241 LastInListInit->getRParenLoc(), 0, *Result.SourceManager, 242 getLangOpts()); 243 // Inserting after the last constructor initializer, so we need a 244 // comma. 245 InsertPrefix = ", "; 246 } else { 247 InsertPos = Lexer::getLocForEndOfToken( 248 Ctor->getTypeSourceInfo() 249 ->getTypeLoc() 250 .getAs<clang::FunctionTypeLoc>() 251 .getLocalRangeEnd(), 252 0, *Result.SourceManager, getLangOpts()); 253 254 // If this is first time in the loop, there are no initializers so 255 // `:` declares member initialization list. If this is a subsequent 256 // pass then we have already inserted a `:` so continue with a 257 // comma. 258 InsertPrefix = FirstToCtorInits ? " : " : ", "; 259 } 260 } 261 InvalidFix |= InsertPos.isMacroID(); 262 263 SourceLocation SemiColonEnd; 264 if (auto NextToken = Lexer::findNextToken( 265 S->getEndLoc(), *Result.SourceManager, getLangOpts())) 266 SemiColonEnd = NextToken->getEndLoc(); 267 else 268 InvalidFix = true; 269 270 auto Diag = 271 diag(S->getBeginLoc(), "%0 should be initialized in a member" 272 " initializer of the constructor") 273 << Field; 274 if (!InvalidFix) { 275 276 CharSourceRange StmtRange = 277 CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); 278 SmallString<128> Insertion( 279 {InsertPrefix, Field->getName(), "(", 280 Lexer::getSourceText( 281 CharSourceRange(InitValue->getSourceRange(), true), 282 *Result.SourceManager, getLangOpts()), 283 AddComma ? "), " : ")"}); 284 Diag << FixItHint::CreateInsertion(InsertPos, Insertion, 285 FirstToCtorInits) 286 << FixItHint::CreateRemoval(StmtRange); 287 FirstToCtorInits = false; 288 } 289 } 290 } 291 } 292 } 293 294 } // namespace cppcoreguidelines 295 } // namespace tidy 296 } // namespace clang 297