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 | |
14 | using namespace clang::ast_matchers; |
15 | |
16 | namespace clang::tidy::modernize { |
17 | |
18 | namespace { |
19 | AST_MATCHER_P(InitListExpr, initCountIs, unsigned, N) { |
20 | return Node.getNumInits() == N; |
21 | } |
22 | } // namespace |
23 | |
24 | static 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 | |
112 | static 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 | |
134 | static 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 | |
141 | static 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 | |
148 | static 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 | |
184 | UseDefaultMemberInitCheck::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 | |
190 | void 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 | |
196 | void 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 | |
231 | void 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 | |
242 | void 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 | |
299 | void 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 | |