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