xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp (revision 311091e2b007ebe0da9877953a9a56a51102e60d)
1 //===--- UseDefaultMemberInitCheck.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 "UseDefaultMemberInitCheck.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::tidy::modernize {
17 
18 namespace {
AST_MATCHER_P(InitListExpr,initCountIs,unsigned,N)19 AST_MATCHER_P(InitListExpr, initCountIs, unsigned, N) {
20   return Node.getNumInits() == N;
21 }
22 } // namespace
23 
getValueOfValueInit(const QualType InitType)24 static StringRef getValueOfValueInit(const QualType InitType) {
25   switch (InitType->getScalarTypeKind()) {
26   case Type::STK_CPointer:
27   case Type::STK_BlockPointer:
28   case Type::STK_ObjCObjectPointer:
29   case Type::STK_MemberPointer:
30     return "nullptr";
31 
32   case Type::STK_Bool:
33     return "false";
34 
35   case Type::STK_Integral:
36     switch (InitType->castAs<BuiltinType>()->getKind()) {
37     case BuiltinType::Char_U:
38     case BuiltinType::UChar:
39     case BuiltinType::Char_S:
40     case BuiltinType::SChar:
41       return "'\\0'";
42     case BuiltinType::WChar_U:
43     case BuiltinType::WChar_S:
44       return "L'\\0'";
45     case BuiltinType::Char16:
46       return "u'\\0'";
47     case BuiltinType::Char32:
48       return "U'\\0'";
49     default:
50       return "0";
51     }
52 
53   case Type::STK_Floating:
54     switch (InitType->castAs<BuiltinType>()->getKind()) {
55     case BuiltinType::Half:
56     case BuiltinType::Float:
57       return "0.0f";
58     default:
59       return "0.0";
60     }
61 
62   case Type::STK_FloatingComplex:
63   case Type::STK_IntegralComplex:
64     return getValueOfValueInit(
65         InitType->castAs<ComplexType>()->getElementType());
66 
67   case Type::STK_FixedPoint:
68     switch (InitType->castAs<BuiltinType>()->getKind()) {
69     case BuiltinType::ShortAccum:
70     case BuiltinType::SatShortAccum:
71       return "0.0hk";
72     case BuiltinType::Accum:
73     case BuiltinType::SatAccum:
74       return "0.0k";
75     case BuiltinType::LongAccum:
76     case BuiltinType::SatLongAccum:
77       return "0.0lk";
78     case BuiltinType::UShortAccum:
79     case BuiltinType::SatUShortAccum:
80       return "0.0uhk";
81     case BuiltinType::UAccum:
82     case BuiltinType::SatUAccum:
83       return "0.0uk";
84     case BuiltinType::ULongAccum:
85     case BuiltinType::SatULongAccum:
86       return "0.0ulk";
87     case BuiltinType::ShortFract:
88     case BuiltinType::SatShortFract:
89       return "0.0hr";
90     case BuiltinType::Fract:
91     case BuiltinType::SatFract:
92       return "0.0r";
93     case BuiltinType::LongFract:
94     case BuiltinType::SatLongFract:
95       return "0.0lr";
96     case BuiltinType::UShortFract:
97     case BuiltinType::SatUShortFract:
98       return "0.0uhr";
99     case BuiltinType::UFract:
100     case BuiltinType::SatUFract:
101       return "0.0ur";
102     case BuiltinType::ULongFract:
103     case BuiltinType::SatULongFract:
104       return "0.0ulr";
105     default:
106       llvm_unreachable("Unhandled fixed point BuiltinType");
107     }
108   }
109   llvm_unreachable("Invalid scalar type kind");
110 }
111 
isZero(const Expr * E)112 static bool isZero(const Expr *E) {
113   switch (E->getStmtClass()) {
114   case Stmt::CXXNullPtrLiteralExprClass:
115   case Stmt::ImplicitValueInitExprClass:
116     return true;
117   case Stmt::InitListExprClass:
118     return cast<InitListExpr>(E)->getNumInits() == 0;
119   case Stmt::CharacterLiteralClass:
120     return !cast<CharacterLiteral>(E)->getValue();
121   case Stmt::CXXBoolLiteralExprClass:
122     return !cast<CXXBoolLiteralExpr>(E)->getValue();
123   case Stmt::IntegerLiteralClass:
124     return !cast<IntegerLiteral>(E)->getValue();
125   case Stmt::FloatingLiteralClass: {
126     llvm::APFloat Value = cast<FloatingLiteral>(E)->getValue();
127     return Value.isZero() && !Value.isNegative();
128   }
129   default:
130     return false;
131   }
132 }
133 
ignoreUnaryPlus(const Expr * E)134 static const Expr *ignoreUnaryPlus(const Expr *E) {
135   auto *UnaryOp = dyn_cast<UnaryOperator>(E);
136   if (UnaryOp && UnaryOp->getOpcode() == UO_Plus)
137     return UnaryOp->getSubExpr();
138   return E;
139 }
140 
getInitializer(const Expr * E)141 static const Expr *getInitializer(const Expr *E) {
142   auto *InitList = dyn_cast<InitListExpr>(E);
143   if (InitList && InitList->getNumInits() == 1)
144     return InitList->getInit(0)->IgnoreParenImpCasts();
145   return E;
146 }
147 
sameValue(const Expr * E1,const Expr * E2)148 static bool sameValue(const Expr *E1, const Expr *E2) {
149   E1 = ignoreUnaryPlus(getInitializer(E1->IgnoreParenImpCasts()));
150   E2 = ignoreUnaryPlus(getInitializer(E2->IgnoreParenImpCasts()));
151 
152   if (isZero(E1) && isZero(E2))
153     return true;
154 
155   if (E1->getStmtClass() != E2->getStmtClass())
156     return false;
157 
158   switch (E1->getStmtClass()) {
159   case Stmt::UnaryOperatorClass:
160     return sameValue(cast<UnaryOperator>(E1)->getSubExpr(),
161                      cast<UnaryOperator>(E2)->getSubExpr());
162   case Stmt::CharacterLiteralClass:
163     return cast<CharacterLiteral>(E1)->getValue() ==
164            cast<CharacterLiteral>(E2)->getValue();
165   case Stmt::CXXBoolLiteralExprClass:
166     return cast<CXXBoolLiteralExpr>(E1)->getValue() ==
167            cast<CXXBoolLiteralExpr>(E2)->getValue();
168   case Stmt::IntegerLiteralClass:
169     return cast<IntegerLiteral>(E1)->getValue() ==
170            cast<IntegerLiteral>(E2)->getValue();
171   case Stmt::FloatingLiteralClass:
172     return cast<FloatingLiteral>(E1)->getValue().bitwiseIsEqual(
173         cast<FloatingLiteral>(E2)->getValue());
174   case Stmt::StringLiteralClass:
175     return cast<StringLiteral>(E1)->getString() ==
176            cast<StringLiteral>(E2)->getString();
177   case Stmt::DeclRefExprClass:
178     return cast<DeclRefExpr>(E1)->getDecl() == cast<DeclRefExpr>(E2)->getDecl();
179   default:
180     return false;
181   }
182 }
183 
UseDefaultMemberInitCheck(StringRef Name,ClangTidyContext * Context)184 UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name,
185                                                      ClangTidyContext *Context)
186     : ClangTidyCheck(Name, Context),
187       UseAssignment(Options.get("UseAssignment", false)),
188       IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
189 
storeOptions(ClangTidyOptions::OptionMap & Opts)190 void UseDefaultMemberInitCheck::storeOptions(
191     ClangTidyOptions::OptionMap &Opts) {
192   Options.store(Opts, "UseAssignment", UseAssignment);
193   Options.store(Opts, "IgnoreMacros", IgnoreMacros);
194 }
195 
registerMatchers(MatchFinder * Finder)196 void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) {
197   auto InitBase =
198       anyOf(stringLiteral(), characterLiteral(), integerLiteral(),
199             unaryOperator(hasAnyOperatorName("+", "-"),
200                           hasUnaryOperand(integerLiteral())),
201             floatLiteral(),
202             unaryOperator(hasAnyOperatorName("+", "-"),
203                           hasUnaryOperand(floatLiteral())),
204             cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(),
205             declRefExpr(to(enumConstantDecl())));
206 
207   auto Init =
208       anyOf(initListExpr(anyOf(allOf(initCountIs(1), hasInit(0, InitBase)),
209                                initCountIs(0), hasType(arrayType()))),
210             InitBase);
211 
212   Finder->addMatcher(
213       cxxConstructorDecl(forEachConstructorInitializer(
214           cxxCtorInitializer(
215               forField(unless(anyOf(
216                   getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(),
217                   hasInClassInitializer(anything()),
218                   hasParent(recordDecl(isUnion()))))),
219               withInitializer(Init))
220               .bind("default"))),
221       this);
222 
223   Finder->addMatcher(
224       cxxConstructorDecl(forEachConstructorInitializer(
225           cxxCtorInitializer(forField(hasInClassInitializer(anything())),
226                              withInitializer(Init))
227               .bind("existing"))),
228       this);
229 }
230 
check(const MatchFinder::MatchResult & Result)231 void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
232   if (const auto *Default =
233           Result.Nodes.getNodeAs<CXXCtorInitializer>("default"))
234     checkDefaultInit(Result, Default);
235   else if (const auto *Existing =
236                Result.Nodes.getNodeAs<CXXCtorInitializer>("existing"))
237     checkExistingInit(Result, Existing);
238   else
239     llvm_unreachable("Bad Callback. No node provided.");
240 }
241 
checkDefaultInit(const MatchFinder::MatchResult & Result,const CXXCtorInitializer * Init)242 void UseDefaultMemberInitCheck::checkDefaultInit(
243     const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
244   const FieldDecl *Field = Init->getAnyMember();
245 
246   // Check whether we have multiple hand-written constructors and bomb out, as
247   // it is hard to reconcile their sets of member initializers.
248   const auto *ClassDecl = cast<CXXRecordDecl>(Field->getParent());
249   if (llvm::count_if(ClassDecl->decls(), [](const Decl *D) {
250         if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(D))
251           D = FTD->getTemplatedDecl();
252         if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(D))
253           return !Ctor->isCopyOrMoveConstructor();
254         return false;
255       }) > 1)
256     return;
257 
258   SourceLocation StartLoc = Field->getBeginLoc();
259   if (StartLoc.isMacroID() && IgnoreMacros)
260     return;
261 
262   SourceLocation FieldEnd =
263       Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
264                                  *Result.SourceManager, getLangOpts());
265   SourceLocation LParenEnd = Lexer::getLocForEndOfToken(
266       Init->getLParenLoc(), 0, *Result.SourceManager, getLangOpts());
267   CharSourceRange InitRange =
268       CharSourceRange::getCharRange(LParenEnd, Init->getRParenLoc());
269 
270   const Expr *InitExpression = Init->getInit();
271   const QualType InitType = InitExpression->getType();
272 
273   const bool ValueInit =
274       isa<ImplicitValueInitExpr>(InitExpression) && !isa<ArrayType>(InitType);
275   const bool CanAssign =
276       UseAssignment && (!ValueInit || !InitType->isEnumeralType());
277   const bool NeedsBraces = !CanAssign || isa<ArrayType>(InitType);
278 
279   auto Diag =
280       diag(Field->getLocation(), "use default member initializer for %0")
281       << Field;
282 
283   if (CanAssign)
284     Diag << FixItHint::CreateInsertion(FieldEnd, " = ");
285   if (NeedsBraces)
286     Diag << FixItHint::CreateInsertion(FieldEnd, "{");
287 
288   if (CanAssign && ValueInit)
289     Diag << FixItHint::CreateInsertion(FieldEnd, getValueOfValueInit(InitType));
290   else
291     Diag << FixItHint::CreateInsertionFromRange(FieldEnd, InitRange);
292 
293   if (NeedsBraces)
294     Diag << FixItHint::CreateInsertion(FieldEnd, "}");
295 
296   Diag << FixItHint::CreateRemoval(Init->getSourceRange());
297 }
298 
checkExistingInit(const MatchFinder::MatchResult & Result,const CXXCtorInitializer * Init)299 void UseDefaultMemberInitCheck::checkExistingInit(
300     const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
301   const FieldDecl *Field = Init->getAnyMember();
302 
303   if (!sameValue(Field->getInClassInitializer(), Init->getInit()))
304     return;
305 
306   diag(Init->getSourceLocation(), "member initializer for %0 is redundant")
307       << Field << FixItHint::CreateRemoval(Init->getSourceRange());
308 }
309 
310 } // namespace clang::tidy::modernize
311