clang-tools 24.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([](const ImplicitValueInitExpr *) { return true; })
31 .Case([](const ParenExpr *PE) {
32 return isExprAllowedInMemberInit(PE->getSubExpr());
33 })
34 .Case([](const UnaryOperator *UO) {
35 return isExprAllowedInMemberInit(UO->getSubExpr());
36 })
37 .Case([](const BinaryOperator *BO) {
38 return isExprAllowedInMemberInit(BO->getLHS()) &&
39 isExprAllowedInMemberInit(BO->getRHS());
40 })
41 .Case([](const CastExpr *CE) {
42 return isExprAllowedInMemberInit(CE->getSubExpr());
43 })
44 .Case([](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 DRE &&
83 (!isVisibleFromDefaultMemberInitializer(DRE->getDecl(), Field, SM) ||
84 !isVisibleFromDefaultMemberInitializer(DRE->getFoundDecl(), Field, SM)))
85 return DRE;
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 const auto Init = anyOf(
302 initListExpr(anyOf(allOf(initCountIs(1), hasInit(0, allowedInitExpr())),
303 initCountIs(0), hasType(arrayType()))),
304 allowedInitExpr());
305
306 const auto CandidateField = forField(unless(anyOf(
307 getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(),
308 hasInClassInitializer(anything()), hasParent(recordDecl(isUnion())))));
309
310 const auto DefaultInit =
311 cxxCtorInitializer(CandidateField, withInitializer(Init));
312 auto VisibleDefaultInit =
313 cxxCtorInitializer(DefaultInit, hasOnlyVisibleReferencedDecls())
314 .bind("visible-init");
315 ast_matchers::internal::Matcher<CXXCtorInitializer> DefaultInitMatcher =
316 VisibleDefaultInit;
317
318 if (!IgnoreNonVisibleReferences) {
319 DefaultInitMatcher = cxxCtorInitializer(anyOf(
320 VisibleDefaultInit,
321 cxxCtorInitializer(DefaultInit, unless(hasOnlyVisibleReferencedDecls()))
322 .bind("non-visible-init")));
323 }
324
325 Finder->addMatcher(
326 cxxConstructorDecl(forEachConstructorInitializer(DefaultInitMatcher)),
327 this);
328
329 Finder->addMatcher(
330 cxxConstructorDecl(forEachConstructorInitializer(
331 cxxCtorInitializer(forField(hasInClassInitializer(anything())),
332 withInitializer(Init))
333 .bind("existing"))),
334 this);
335}
336
337void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
338 if (const auto *Default =
339 Result.Nodes.getNodeAs<CXXCtorInitializer>("visible-init"))
340 checkDefaultInit(Result, Default, /*EmitFix=*/true);
341 else if (const auto *DefaultWithoutFix =
342 Result.Nodes.getNodeAs<CXXCtorInitializer>("non-visible-init"))
343 checkDefaultInit(Result, DefaultWithoutFix, /*EmitFix=*/false);
344 else if (const auto *Existing =
345 Result.Nodes.getNodeAs<CXXCtorInitializer>("existing"))
346 checkExistingInit(Result, Existing);
347 else
348 llvm_unreachable("Bad Callback. No node provided.");
349}
350
351void UseDefaultMemberInitCheck::checkDefaultInit(
352 const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init,
353 bool EmitFix) {
354 const FieldDecl *Field = Init->getAnyMember();
355
356 // Check whether we have multiple hand-written constructors and bomb out, as
357 // it is hard to reconcile their sets of member initializers.
358 const auto *ClassDecl = cast<CXXRecordDecl>(Field->getParent());
359 if (llvm::count_if(ClassDecl->decls(), [](const Decl *D) {
360 if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(D))
361 D = FTD->getTemplatedDecl();
362 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(D))
363 return !Ctor->isCopyOrMoveConstructor();
364 return false;
365 }) > 1)
366 return;
367
368 const SourceLocation StartLoc = Field->getBeginLoc();
369 if (StartLoc.isMacroID() && IgnoreMacros)
370 return;
371
372 const auto DiagDefaultMemberInitializer = [&] {
373 return diag(Field->getLocation(), "use default member initializer for %0")
374 << Field;
375 };
376
377 if (!EmitFix) {
378 DiagDefaultMemberInitializer();
379 diag(Init->getLParenLoc(),
380 "move the referenced declaration or definition before the field "
381 "declaration to use a default member initializer",
382 DiagnosticIDs::Note);
383 return;
384 }
385
386 const auto Diag = DiagDefaultMemberInitializer();
387
388 const SourceLocation FieldEnd =
389 Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
390 *Result.SourceManager, getLangOpts());
391 const SourceLocation LParenEnd = Lexer::getLocForEndOfToken(
392 Init->getLParenLoc(), 0, *Result.SourceManager, getLangOpts());
393 const CharSourceRange InitRange =
394 CharSourceRange::getCharRange(LParenEnd, Init->getRParenLoc());
395
396 const Expr *InitExpression = Init->getInit();
397 const QualType InitType = InitExpression->getType();
398
399 const bool ValueInit =
400 isa<ImplicitValueInitExpr>(InitExpression) && !isa<ArrayType>(InitType);
401 const bool CanAssign =
402 UseAssignment && (!ValueInit || !InitType->isEnumeralType());
403 const bool NeedsBraces = !CanAssign || isa<ArrayType>(InitType);
404
405 if (CanAssign)
406 Diag << FixItHint::CreateInsertion(FieldEnd, " = ");
407 if (NeedsBraces)
408 Diag << FixItHint::CreateInsertion(FieldEnd, "{");
409
410 if (CanAssign && ValueInit)
411 Diag << FixItHint::CreateInsertion(FieldEnd, getValueOfValueInit(InitType));
412 else
413 Diag << FixItHint::CreateInsertionFromRange(FieldEnd, InitRange);
414
415 if (NeedsBraces)
416 Diag << FixItHint::CreateInsertion(FieldEnd, "}");
417
418 Diag << FixItHint::CreateRemoval(Init->getSourceRange());
419}
420
421void UseDefaultMemberInitCheck::checkExistingInit(
422 const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
423 const FieldDecl *Field = Init->getAnyMember();
424
425 if (!sameValue(Field->getInClassInitializer(), Init->getInit()))
426 return;
427
428 diag(Init->getSourceLocation(), "member initializer for %0 is redundant")
429 << Field << FixItHint::CreateRemoval(Init->getSourceRange());
430}
431
432} // 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