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