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