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
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::modernize {
17
18namespace {
19AST_MATCHER_P(InitListExpr, initCountIs, unsigned, N) {
20 return Node.getNumInits() == N;
21}
22} // namespace
23
24static 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: 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
112static 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>(Val: E)->getNumInits() == 0;
119 case Stmt::CharacterLiteralClass:
120 return !cast<CharacterLiteral>(Val: E)->getValue();
121 case Stmt::CXXBoolLiteralExprClass:
122 return !cast<CXXBoolLiteralExpr>(Val: E)->getValue();
123 case Stmt::IntegerLiteralClass:
124 return !cast<IntegerLiteral>(Val: E)->getValue();
125 case Stmt::FloatingLiteralClass: {
126 llvm::APFloat Value = cast<FloatingLiteral>(Val: E)->getValue();
127 return Value.isZero() && !Value.isNegative();
128 }
129 default:
130 return false;
131 }
132}
133
134static const Expr *ignoreUnaryPlus(const Expr *E) {
135 auto *UnaryOp = dyn_cast<UnaryOperator>(Val: E);
136 if (UnaryOp && UnaryOp->getOpcode() == UO_Plus)
137 return UnaryOp->getSubExpr();
138 return E;
139}
140
141static const Expr *getInitializer(const Expr *E) {
142 auto *InitList = dyn_cast<InitListExpr>(Val: E);
143 if (InitList && InitList->getNumInits() == 1)
144 return InitList->getInit(Init: 0)->IgnoreParenImpCasts();
145 return E;
146}
147
148static bool sameValue(const Expr *E1, const Expr *E2) {
149 E1 = ignoreUnaryPlus(E: getInitializer(E: E1->IgnoreParenImpCasts()));
150 E2 = ignoreUnaryPlus(E: getInitializer(E: E2->IgnoreParenImpCasts()));
151
152 if (isZero(E: E1) && isZero(E: 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(E1: cast<UnaryOperator>(Val: E1)->getSubExpr(),
161 E2: cast<UnaryOperator>(Val: E2)->getSubExpr());
162 case Stmt::CharacterLiteralClass:
163 return cast<CharacterLiteral>(Val: E1)->getValue() ==
164 cast<CharacterLiteral>(Val: E2)->getValue();
165 case Stmt::CXXBoolLiteralExprClass:
166 return cast<CXXBoolLiteralExpr>(Val: E1)->getValue() ==
167 cast<CXXBoolLiteralExpr>(Val: E2)->getValue();
168 case Stmt::IntegerLiteralClass:
169 return cast<IntegerLiteral>(Val: E1)->getValue() ==
170 cast<IntegerLiteral>(Val: E2)->getValue();
171 case Stmt::FloatingLiteralClass:
172 return cast<FloatingLiteral>(Val: E1)->getValue().bitwiseIsEqual(
173 RHS: cast<FloatingLiteral>(Val: E2)->getValue());
174 case Stmt::StringLiteralClass:
175 return cast<StringLiteral>(Val: E1)->getString() ==
176 cast<StringLiteral>(Val: E2)->getString();
177 case Stmt::DeclRefExprClass:
178 return cast<DeclRefExpr>(Val: E1)->getDecl() == cast<DeclRefExpr>(Val: E2)->getDecl();
179 default:
180 return false;
181 }
182}
183
184UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name,
185 ClangTidyContext *Context)
186 : ClangTidyCheck(Name, Context),
187 UseAssignment(Options.get(LocalName: "UseAssignment", Default: false)),
188 IgnoreMacros(Options.getLocalOrGlobal(LocalName: "IgnoreMacros", Default: true)) {}
189
190void UseDefaultMemberInitCheck::storeOptions(
191 ClangTidyOptions::OptionMap &Opts) {
192 Options.store(Options&: Opts, LocalName: "UseAssignment", Value: UseAssignment);
193 Options.store(Options&: Opts, LocalName: "IgnoreMacros", Value: IgnoreMacros);
194}
195
196void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) {
197 auto InitBase =
198 anyOf(stringLiteral(), characterLiteral(), integerLiteral(),
199 unaryOperator(hasAnyOperatorName("+", "-"),
200 hasUnaryOperand(InnerMatcher: integerLiteral())),
201 floatLiteral(),
202 unaryOperator(hasAnyOperatorName("+", "-"),
203 hasUnaryOperand(InnerMatcher: floatLiteral())),
204 cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(),
205 declRefExpr(to(InnerMatcher: enumConstantDecl())));
206
207 auto Init =
208 anyOf(initListExpr(anyOf(allOf(initCountIs(N: 1), hasInit(N: 0, InnerMatcher: InitBase)),
209 initCountIs(N: 0), hasType(InnerMatcher: arrayType()))),
210 InitBase);
211
212 Finder->addMatcher(
213 NodeMatch: cxxConstructorDecl(forEachConstructorInitializer(
214 InnerMatcher: cxxCtorInitializer(
215 forField(InnerMatcher: unless(anyOf(
216 getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(),
217 hasInClassInitializer(InnerMatcher: anything()),
218 hasParent(recordDecl(isUnion()))))),
219 withInitializer(InnerMatcher: Init))
220 .bind(ID: "default"))),
221 Action: this);
222
223 Finder->addMatcher(
224 NodeMatch: cxxConstructorDecl(forEachConstructorInitializer(
225 InnerMatcher: cxxCtorInitializer(forField(InnerMatcher: hasInClassInitializer(InnerMatcher: anything())),
226 withInitializer(InnerMatcher: Init))
227 .bind(ID: "existing"))),
228 Action: this);
229}
230
231void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
232 if (const auto *Default =
233 Result.Nodes.getNodeAs<CXXCtorInitializer>(ID: "default"))
234 checkDefaultInit(Result, Init: Default);
235 else if (const auto *Existing =
236 Result.Nodes.getNodeAs<CXXCtorInitializer>(ID: "existing"))
237 checkExistingInit(Result, Init: Existing);
238 else
239 llvm_unreachable("Bad Callback. No node provided.");
240}
241
242void 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>(Val: Field->getParent());
249 if (llvm::count_if(ClassDecl->decls(), [](const Decl *D) {
250 if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(Val: D))
251 D = FTD->getTemplatedDecl();
252 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Val: 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(Loc: Field->getSourceRange().getEnd(), Offset: 0,
264 SM: *Result.SourceManager, LangOpts: getLangOpts());
265 SourceLocation LParenEnd = Lexer::getLocForEndOfToken(
266 Loc: Init->getLParenLoc(), Offset: 0, SM: *Result.SourceManager, LangOpts: getLangOpts());
267 CharSourceRange InitRange =
268 CharSourceRange::getCharRange(B: LParenEnd, E: Init->getRParenLoc());
269
270 const Expr *InitExpression = Init->getInit();
271 const QualType InitType = InitExpression->getType();
272
273 const bool ValueInit =
274 isa<ImplicitValueInitExpr>(Val: InitExpression) && !isa<ArrayType>(Val: InitType);
275 const bool CanAssign =
276 UseAssignment && (!ValueInit || !InitType->isEnumeralType());
277 const bool NeedsBraces = !CanAssign || isa<ArrayType>(Val: 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(InsertionLoc: FieldEnd, Code: " = ");
285 if (NeedsBraces)
286 Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: "{");
287
288 if (CanAssign && ValueInit)
289 Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: getValueOfValueInit(InitType));
290 else
291 Diag << FixItHint::CreateInsertionFromRange(InsertionLoc: FieldEnd, FromRange: InitRange);
292
293 if (NeedsBraces)
294 Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: "}");
295
296 Diag << FixItHint::CreateRemoval(RemoveRange: Init->getSourceRange());
297}
298
299void UseDefaultMemberInitCheck::checkExistingInit(
300 const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
301 const FieldDecl *Field = Init->getAnyMember();
302
303 if (!sameValue(E1: Field->getInClassInitializer(), E2: Init->getInit()))
304 return;
305
306 diag(Loc: Init->getSourceLocation(), Description: "member initializer for %0 is redundant")
307 << Field << FixItHint::CreateRemoval(RemoveRange: Init->getSourceRange());
308}
309
310} // namespace clang::tidy::modernize
311

source code of clang-tools-extra/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp