|
| 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 | +static const std::pair<const FieldDecl *, const Expr *> |
| 59 | +isAssignmentToMemberOf(const RecordDecl *Rec, const Stmt *S) { |
| 60 | + if (const auto *BO = dyn_cast<BinaryOperator>(S)) { |
| 61 | + if (BO->getOpcode() != BO_Assign) |
| 62 | + return std::make_pair(nullptr, nullptr); |
| 63 | + |
| 64 | + const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts()); |
| 65 | + if (!ME) |
| 66 | + return std::make_pair(nullptr, nullptr); |
| 67 | + |
| 68 | + const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); |
| 69 | + if (!Field) |
| 70 | + return std::make_pair(nullptr, nullptr); |
| 71 | + |
| 72 | + if (isa<CXXThisExpr>(ME->getBase())) |
| 73 | + return std::make_pair(Field, BO->getRHS()->IgnoreParenImpCasts()); |
| 74 | + } else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) { |
| 75 | + if (COCE->getOperator() != OO_Equal) |
| 76 | + return std::make_pair(nullptr, nullptr); |
| 77 | + |
| 78 | + const auto *ME = |
| 79 | + dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts()); |
| 80 | + if (!ME) |
| 81 | + return std::make_pair(nullptr, nullptr); |
| 82 | + |
| 83 | + const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); |
| 84 | + if (!Field) |
| 85 | + return std::make_pair(nullptr, nullptr); |
| 86 | + |
| 87 | + if (isa<CXXThisExpr>(ME->getBase())) |
| 88 | + return std::make_pair(Field, COCE->getArg(1)->IgnoreParenImpCasts()); |
| 89 | + } |
| 90 | + |
| 91 | + return std::make_pair(nullptr, nullptr); |
| 92 | +} |
| 93 | + |
| 94 | +PreferMemberInitializerCheck::PreferMemberInitializerCheck( |
| 95 | + StringRef Name, ClangTidyContext *Context) |
| 96 | + : ClangTidyCheck(Name, Context), |
| 97 | + IsUseDefaultMemberInitEnabled( |
| 98 | + Context->isCheckEnabled("modernize-use-default-member-init")), |
| 99 | + UseAssignment(OptionsView("modernize-use-default-member-init", |
| 100 | + Context->getOptions().CheckOptions) |
| 101 | + .get("UseAssignment", false)) {} |
| 102 | + |
| 103 | +void PreferMemberInitializerCheck::storeOptions( |
| 104 | + ClangTidyOptions::OptionMap &Opts) { |
| 105 | + Options.store(Opts, "UseAssignment", UseAssignment); |
| 106 | +} |
| 107 | + |
| 108 | +void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) { |
| 109 | + Finder->addMatcher( |
| 110 | + cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated())) |
| 111 | + .bind("ctor"), |
| 112 | + this); |
| 113 | +} |
| 114 | + |
| 115 | +void PreferMemberInitializerCheck::check( |
| 116 | + const MatchFinder::MatchResult &Result) { |
| 117 | + const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); |
| 118 | + const auto *Body = cast<CompoundStmt>(Ctor->getBody()); |
| 119 | + |
| 120 | + const CXXRecordDecl *Class = Ctor->getParent(); |
| 121 | + SourceLocation InsertPos; |
| 122 | + bool FirstToCtorInits = true; |
| 123 | + |
| 124 | + for (const Stmt *S : Body->body()) { |
| 125 | + if (S->getBeginLoc().isMacroID()) { |
| 126 | + StringRef MacroName = |
| 127 | + Lexer::getImmediateMacroName(S->getBeginLoc(), *Result.SourceManager, |
| 128 | + getLangOpts()); |
| 129 | + if (MacroName.contains_lower("assert")) |
| 130 | + return; |
| 131 | + } |
| 132 | + if (isControlStatement(S)) |
| 133 | + return; |
| 134 | + |
| 135 | + if (isNoReturnCallStatement(S)) |
| 136 | + return; |
| 137 | + |
| 138 | + if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) { |
| 139 | + if (isNoReturnCallStatement(CondOp->getLHS()) || |
| 140 | + isNoReturnCallStatement(CondOp->getRHS())) |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + const FieldDecl *Field; |
| 145 | + const Expr *InitValue; |
| 146 | + std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S); |
| 147 | + if (Field) { |
| 148 | + if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && |
| 149 | + Ctor->isDefaultConstructor() && |
| 150 | + (getLangOpts().CPlusPlus20 || !Field->isBitField()) && |
| 151 | + (!isa<RecordDecl>(Class->getDeclContext()) || |
| 152 | + !cast<RecordDecl>(Class->getDeclContext())->isUnion()) && |
| 153 | + shouldBeDefaultMemberInitializer(InitValue)) { |
| 154 | + auto Diag = |
| 155 | + diag(S->getBeginLoc(), "%0 should be initialized in an in-class" |
| 156 | + " default member initializer") |
| 157 | + << Field; |
| 158 | + |
| 159 | + SourceLocation FieldEnd = |
| 160 | + Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, |
| 161 | + *Result.SourceManager, getLangOpts()); |
| 162 | + Diag << FixItHint::CreateInsertion(FieldEnd, |
| 163 | + UseAssignment ? " = " : "{") |
| 164 | + << FixItHint::CreateInsertionFromRange( |
| 165 | + FieldEnd, |
| 166 | + CharSourceRange(InitValue->getSourceRange(), true)) |
| 167 | + << FixItHint::CreateInsertion(FieldEnd, UseAssignment ? "" : "}"); |
| 168 | + |
| 169 | + SourceLocation SemiColonEnd = |
| 170 | + Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager, |
| 171 | + getLangOpts()) |
| 172 | + ->getEndLoc(); |
| 173 | + CharSourceRange StmtRange = |
| 174 | + CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); |
| 175 | + |
| 176 | + Diag << FixItHint::CreateRemoval(StmtRange); |
| 177 | + } else { |
| 178 | + auto Diag = |
| 179 | + diag(S->getBeginLoc(), "%0 should be initialized in a member" |
| 180 | + " initializer of the constructor") |
| 181 | + << Field; |
| 182 | + |
| 183 | + bool AddComma = false; |
| 184 | + if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) { |
| 185 | + SourceLocation BodyPos = Ctor->getBody()->getBeginLoc(); |
| 186 | + SourceLocation NextPos = Ctor->getBeginLoc(); |
| 187 | + do { |
| 188 | + InsertPos = NextPos; |
| 189 | + NextPos = Lexer::findNextToken(NextPos, *Result.SourceManager, |
| 190 | + getLangOpts()) |
| 191 | + ->getLocation(); |
| 192 | + } while (NextPos != BodyPos); |
| 193 | + InsertPos = Lexer::getLocForEndOfToken( |
| 194 | + InsertPos, 0, *Result.SourceManager, getLangOpts()); |
| 195 | + |
| 196 | + Diag << FixItHint::CreateInsertion(InsertPos, " : "); |
| 197 | + } else { |
| 198 | + bool Found = false; |
| 199 | + for (const auto *Init : Ctor->inits()) { |
| 200 | + if (Init->isMemberInitializer()) { |
| 201 | + if (Result.SourceManager->isBeforeInTranslationUnit( |
| 202 | + Field->getLocation(), Init->getMember()->getLocation())) { |
| 203 | + InsertPos = Init->getSourceLocation(); |
| 204 | + Found = true; |
| 205 | + break; |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + if (!Found) { |
| 211 | + if (Ctor->getNumCtorInitializers()) { |
| 212 | + InsertPos = Lexer::getLocForEndOfToken( |
| 213 | + (*Ctor->init_rbegin())->getSourceRange().getEnd(), 0, |
| 214 | + *Result.SourceManager, getLangOpts()); |
| 215 | + } |
| 216 | + Diag << FixItHint::CreateInsertion(InsertPos, ", "); |
| 217 | + } else { |
| 218 | + AddComma = true; |
| 219 | + } |
| 220 | + } |
| 221 | + Diag << FixItHint::CreateInsertion(InsertPos, Field->getName()) |
| 222 | + << FixItHint::CreateInsertion(InsertPos, "(") |
| 223 | + << FixItHint::CreateInsertionFromRange( |
| 224 | + InsertPos, |
| 225 | + CharSourceRange(InitValue->getSourceRange(), true)) |
| 226 | + << FixItHint::CreateInsertion(InsertPos, ")"); |
| 227 | + if (AddComma) |
| 228 | + Diag << FixItHint::CreateInsertion(InsertPos, ", "); |
| 229 | + |
| 230 | + SourceLocation SemiColonEnd = |
| 231 | + Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager, |
| 232 | + getLangOpts()) |
| 233 | + ->getEndLoc(); |
| 234 | + CharSourceRange StmtRange = |
| 235 | + CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); |
| 236 | + |
| 237 | + Diag << FixItHint::CreateRemoval(StmtRange); |
| 238 | + FirstToCtorInits = false; |
| 239 | + } |
| 240 | + } |
| 241 | + } |
| 242 | +} |
| 243 | + |
| 244 | +} // namespace cppcoreguidelines |
| 245 | +} // namespace tidy |
| 246 | +} // namespace clang |
0 commit comments