clang-tools 19.0.0git
UseDefaultMemberInitCheck.cpp
Go to the documentation of this file.
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
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:
65 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>(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();
128 }
129 default:
130 return false;
131 }
132}
133
134static const Expr *ignoreUnaryPlus(const Expr *E) {
135 auto *UnaryOp = dyn_cast<UnaryOperator>(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>(E);
143 if (InitList && InitList->getNumInits() == 1)
144 return InitList->getInit(0)->IgnoreParenImpCasts();
145 return E;
146}
147
148static bool sameValue(const Expr *E1, const Expr *E2) {
149 E1 = ignoreUnaryPlus(getInitializer(E1->IgnoreParenImpCasts()));
150 E2 = ignoreUnaryPlus(getInitializer(E2->IgnoreParenImpCasts()));
151
152 if (isZero(E1) && isZero(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(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();
179 default:
180 return false;
181 }
182}
183
185 ClangTidyContext *Context)
186 : ClangTidyCheck(Name, Context),
187 UseAssignment(Options.get("UseAssignment", false)),
188 IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
189
192 Options.store(Opts, "UseAssignment", UseAssignment);
193 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
194}
195
197 auto InitBase =
198 anyOf(stringLiteral(), characterLiteral(), integerLiteral(),
199 unaryOperator(hasAnyOperatorName("+", "-"),
200 hasUnaryOperand(integerLiteral())),
201 floatLiteral(),
202 unaryOperator(hasAnyOperatorName("+", "-"),
203 hasUnaryOperand(floatLiteral())),
204 cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(),
205 declRefExpr(to(enumConstantDecl())));
206
207 auto Init =
208 anyOf(initListExpr(anyOf(allOf(initCountIs(1), hasInit(0, InitBase)),
209 initCountIs(0), hasType(arrayType()))),
210 InitBase);
211
212 Finder->addMatcher(
213 cxxConstructorDecl(forEachConstructorInitializer(
214 cxxCtorInitializer(
215 forField(unless(anyOf(
216 getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(),
217 hasInClassInitializer(anything()),
218 hasParent(recordDecl(isUnion()))))),
219 withInitializer(Init))
220 .bind("default"))),
221 this);
222
223 Finder->addMatcher(
224 cxxConstructorDecl(forEachConstructorInitializer(
225 cxxCtorInitializer(forField(hasInClassInitializer(anything())),
226 withInitializer(Init))
227 .bind("existing"))),
228 this);
229}
230
231void 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);
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>(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();
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(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());
269
270 const Expr *InitExpression = Init->getInit();
271 const QualType InitType = InitExpression->getType();
272
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);
278
279 auto Diag =
280 diag(Field->getLocation(), "use default member initializer for %0")
281 << Field;
282
283 if (CanAssign)
284 Diag << FixItHint::CreateInsertion(FieldEnd, " = ");
285 if (NeedsBraces)
286 Diag << FixItHint::CreateInsertion(FieldEnd, "{");
287
288 if (CanAssign && ValueInit)
289 Diag << FixItHint::CreateInsertion(FieldEnd, getValueOfValueInit(InitType));
290 else
291 Diag << FixItHint::CreateInsertionFromRange(FieldEnd, InitRange);
292
293 if (NeedsBraces)
294 Diag << FixItHint::CreateInsertion(FieldEnd, "}");
295
296 Diag << FixItHint::CreateRemoval(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(Field->getInClassInitializer(), Init->getInit()))
304 return;
305
306 diag(Init->getSourceLocation(), "member initializer for %0 is redundant")
307 << Field << FixItHint::CreateRemoval(Init->getSourceRange());
308}
309
310} // namespace clang::tidy::modernize
const Expr * E
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
const FieldDecl * Field
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
UseDefaultMemberInitCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
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 const Expr * ignoreUnaryPlus(const Expr *E)
llvm::StringMap< ClangTidyValue > OptionMap