1 //===--- UseDefaultMemberInitCheck.cpp - clang-tidy------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "UseDefaultMemberInitCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::modernize
{
19 AST_MATCHER_P(InitListExpr
, initCountIs
, unsigned, N
) {
20 return Node
.getNumInits() == N
;
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
:
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
:
42 case BuiltinType::WChar_U
:
43 case BuiltinType::WChar_S
:
45 case BuiltinType::Char16
:
47 case BuiltinType::Char32
:
53 case Type::STK_Floating
:
54 switch (InitType
->castAs
<BuiltinType
>()->getKind()) {
55 case BuiltinType::Half
:
56 case BuiltinType::Float
:
62 case Type::STK_FloatingComplex
:
63 case Type::STK_IntegralComplex
:
64 return getValueOfValueInit(
65 InitType
->castAs
<ComplexType
>()->getElementType());
67 case Type::STK_FixedPoint
:
68 switch (InitType
->castAs
<BuiltinType
>()->getKind()) {
69 case BuiltinType::ShortAccum
:
70 case BuiltinType::SatShortAccum
:
72 case BuiltinType::Accum
:
73 case BuiltinType::SatAccum
:
75 case BuiltinType::LongAccum
:
76 case BuiltinType::SatLongAccum
:
78 case BuiltinType::UShortAccum
:
79 case BuiltinType::SatUShortAccum
:
81 case BuiltinType::UAccum
:
82 case BuiltinType::SatUAccum
:
84 case BuiltinType::ULongAccum
:
85 case BuiltinType::SatULongAccum
:
87 case BuiltinType::ShortFract
:
88 case BuiltinType::SatShortFract
:
90 case BuiltinType::Fract
:
91 case BuiltinType::SatFract
:
93 case BuiltinType::LongFract
:
94 case BuiltinType::SatLongFract
:
96 case BuiltinType::UShortFract
:
97 case BuiltinType::SatUShortFract
:
99 case BuiltinType::UFract
:
100 case BuiltinType::SatUFract
:
102 case BuiltinType::ULongFract
:
103 case BuiltinType::SatULongFract
:
106 llvm_unreachable("Unhandled fixed point BuiltinType");
109 llvm_unreachable("Invalid scalar type kind");
112 static bool isZero(const Expr
*E
) {
113 switch (E
->getStmtClass()) {
114 case Stmt::CXXNullPtrLiteralExprClass
:
115 case Stmt::ImplicitValueInitExprClass
:
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();
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();
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();
148 static bool sameValue(const Expr
*E1
, const Expr
*E2
) {
149 E1
= ignoreUnaryPlus(getInitializer(E1
->IgnoreParenImpCasts()));
150 E2
= ignoreUnaryPlus(getInitializer(E2
->IgnoreParenImpCasts()));
152 if (isZero(E1
) && isZero(E2
))
155 if (E1
->getStmtClass() != E2
->getStmtClass())
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();
184 UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name
,
185 ClangTidyContext
*Context
)
186 : ClangTidyCheck(Name
, Context
),
187 UseAssignment(Options
.get("UseAssignment", false)),
188 IgnoreMacros(Options
.getLocalOrGlobal("IgnoreMacros", true)) {}
190 void UseDefaultMemberInitCheck::storeOptions(
191 ClangTidyOptions::OptionMap
&Opts
) {
192 Options
.store(Opts
, "UseAssignment", UseAssignment
);
193 Options
.store(Opts
, "IgnoreMacros", IgnoreMacros
);
196 void UseDefaultMemberInitCheck::registerMatchers(MatchFinder
*Finder
) {
198 anyOf(stringLiteral(), characterLiteral(), integerLiteral(),
199 unaryOperator(hasAnyOperatorName("+", "-"),
200 hasUnaryOperand(integerLiteral())),
202 unaryOperator(hasAnyOperatorName("+", "-"),
203 hasUnaryOperand(floatLiteral())),
204 cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(),
205 declRefExpr(to(enumConstantDecl())));
208 anyOf(initListExpr(anyOf(allOf(initCountIs(1), hasInit(0, InitBase
)),
209 initCountIs(0), hasType(arrayType()))),
213 cxxConstructorDecl(forEachConstructorInitializer(
215 forField(unless(anyOf(
216 getLangOpts().CPlusPlus20
? unless(anything()) : isBitField(),
217 hasInClassInitializer(anything()),
218 hasParent(recordDecl(isUnion()))))),
219 withInitializer(Init
))
224 cxxConstructorDecl(forEachConstructorInitializer(
225 cxxCtorInitializer(forField(hasInClassInitializer(anything())),
226 withInitializer(Init
))
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
);
239 llvm_unreachable("Bad Callback. No node provided.");
242 void UseDefaultMemberInitCheck::checkDefaultInit(
243 const MatchFinder::MatchResult
&Result
, const CXXCtorInitializer
*Init
) {
244 const FieldDecl
*Field
= Init
->getAnyMember();
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();
258 SourceLocation StartLoc
= Field
->getBeginLoc();
259 if (StartLoc
.isMacroID() && IgnoreMacros
)
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());
270 const Expr
*InitExpression
= Init
->getInit();
271 const QualType InitType
= InitExpression
->getType();
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
);
280 diag(Field
->getLocation(), "use default member initializer for %0")
284 Diag
<< FixItHint::CreateInsertion(FieldEnd
, " = ");
286 Diag
<< FixItHint::CreateInsertion(FieldEnd
, "{");
288 if (CanAssign
&& ValueInit
)
289 Diag
<< FixItHint::CreateInsertion(FieldEnd
, getValueOfValueInit(InitType
));
291 Diag
<< FixItHint::CreateInsertionFromRange(FieldEnd
, InitRange
);
294 Diag
<< FixItHint::CreateInsertion(FieldEnd
, "}");
296 Diag
<< FixItHint::CreateRemoval(Init
->getSourceRange());
299 void UseDefaultMemberInitCheck::checkExistingInit(
300 const MatchFinder::MatchResult
&Result
, const CXXCtorInitializer
*Init
) {
301 const FieldDecl
*Field
= Init
->getAnyMember();
303 if (!sameValue(Field
->getInClassInitializer(), Init
->getInit()))
306 diag(Init
->getSourceLocation(), "member initializer for %0 is redundant")
307 << Field
<< FixItHint::CreateRemoval(Init
->getSourceRange());
310 } // namespace clang::tidy::modernize