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