• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isControlStatement(const Stmt * S)20 static bool isControlStatement(const Stmt *S) {
21   return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
22              GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
23 }
24 
isNoReturnCallStatement(const Stmt * S)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 
isLiteral(const Expr * E)37 static bool isLiteral(const Expr *E) {
38   return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
39              CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
40 }
41 
isUnaryExprOfLiteral(const Expr * E)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 
shouldBeDefaultMemberInitializer(const Expr * Value)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 *>
isAssignmentToMemberOf(const RecordDecl * Rec,const Stmt * S)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 
PreferMemberInitializerCheck(StringRef Name,ClangTidyContext * Context)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, Context)
101                         .get("UseAssignment", false)) {}
102 
storeOptions(ClangTidyOptions::OptionMap & Opts)103 void PreferMemberInitializerCheck::storeOptions(
104     ClangTidyOptions::OptionMap &Opts) {
105   Options.store(Opts, "UseAssignment", UseAssignment);
106 }
107 
registerMatchers(MatchFinder * Finder)108 void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {
109   Finder->addMatcher(
110       cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
111           .bind("ctor"),
112       this);
113 }
114 
check(const MatchFinder::MatchResult & Result)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
247