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::BinaryOperatorClass: { |
163 | const auto *BinOp1 = cast<BinaryOperator>(Val: E1); |
164 | const auto *BinOp2 = cast<BinaryOperator>(Val: E2); |
165 | return BinOp1->getOpcode() == BinOp2->getOpcode() && |
166 | sameValue(E1: BinOp1->getLHS(), E2: BinOp2->getLHS()) && |
167 | sameValue(E1: BinOp1->getRHS(), E2: BinOp2->getRHS()); |
168 | } |
169 | case Stmt::CharacterLiteralClass: |
170 | return cast<CharacterLiteral>(Val: E1)->getValue() == |
171 | cast<CharacterLiteral>(Val: E2)->getValue(); |
172 | case Stmt::CXXBoolLiteralExprClass: |
173 | return cast<CXXBoolLiteralExpr>(Val: E1)->getValue() == |
174 | cast<CXXBoolLiteralExpr>(Val: E2)->getValue(); |
175 | case Stmt::IntegerLiteralClass: |
176 | return cast<IntegerLiteral>(Val: E1)->getValue() == |
177 | cast<IntegerLiteral>(Val: E2)->getValue(); |
178 | case Stmt::FloatingLiteralClass: |
179 | return cast<FloatingLiteral>(Val: E1)->getValue().bitwiseIsEqual( |
180 | RHS: cast<FloatingLiteral>(Val: E2)->getValue()); |
181 | case Stmt::StringLiteralClass: |
182 | return cast<StringLiteral>(Val: E1)->getString() == |
183 | cast<StringLiteral>(Val: E2)->getString(); |
184 | case Stmt::DeclRefExprClass: |
185 | return cast<DeclRefExpr>(Val: E1)->getDecl() == cast<DeclRefExpr>(Val: E2)->getDecl(); |
186 | case Stmt::CStyleCastExprClass: |
187 | case Stmt::CXXStaticCastExprClass: |
188 | case Stmt::CXXFunctionalCastExprClass: |
189 | return sameValue(cast<ExplicitCastExpr>(Val: E1)->getSubExpr(), |
190 | cast<ExplicitCastExpr>(Val: E2)->getSubExpr()); |
191 | default: |
192 | return false; |
193 | } |
194 | } |
195 | |
196 | UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name, |
197 | ClangTidyContext *Context) |
198 | : ClangTidyCheck(Name, Context), |
199 | UseAssignment(Options.get(LocalName: "UseAssignment" , Default: false)), |
200 | IgnoreMacros(Options.getLocalOrGlobal(LocalName: "IgnoreMacros" , Default: true)) {} |
201 | |
202 | void UseDefaultMemberInitCheck::storeOptions( |
203 | ClangTidyOptions::OptionMap &Opts) { |
204 | Options.store(Options&: Opts, LocalName: "UseAssignment" , Value: UseAssignment); |
205 | Options.store(Options&: Opts, LocalName: "IgnoreMacros" , Value: IgnoreMacros); |
206 | } |
207 | |
208 | void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { |
209 | auto NumericLiteral = anyOf(integerLiteral(), floatLiteral()); |
210 | auto UnaryNumericLiteral = unaryOperator(hasAnyOperatorName("+" , "-" ), |
211 | hasUnaryOperand(InnerMatcher: NumericLiteral)); |
212 | |
213 | auto ConstExprRef = varDecl(anyOf(isConstexpr(), isStaticStorageClass())); |
214 | auto ImmutableRef = |
215 | declRefExpr(to(InnerMatcher: decl(anyOf(enumConstantDecl(), ConstExprRef)))); |
216 | |
217 | auto BinaryNumericExpr = binaryOperator( |
218 | hasOperands(Matcher1: anyOf(NumericLiteral, ImmutableRef, binaryOperator()), |
219 | Matcher2: anyOf(NumericLiteral, ImmutableRef, binaryOperator()))); |
220 | |
221 | auto InitBase = |
222 | anyOf(stringLiteral(), characterLiteral(), NumericLiteral, |
223 | UnaryNumericLiteral, cxxBoolLiteral(), cxxNullPtrLiteralExpr(), |
224 | implicitValueInitExpr(), ImmutableRef, BinaryNumericExpr); |
225 | |
226 | auto ExplicitCastExpr = castExpr(hasSourceExpression(InnerMatcher: InitBase)); |
227 | auto InitMatcher = anyOf(InitBase, ExplicitCastExpr); |
228 | |
229 | auto Init = |
230 | anyOf(initListExpr(anyOf(allOf(initCountIs(N: 1), hasInit(N: 0, InnerMatcher: InitMatcher)), |
231 | initCountIs(N: 0), hasType(InnerMatcher: arrayType()))), |
232 | InitBase, ExplicitCastExpr); |
233 | |
234 | Finder->addMatcher( |
235 | NodeMatch: cxxConstructorDecl(forEachConstructorInitializer( |
236 | InnerMatcher: cxxCtorInitializer( |
237 | forField(InnerMatcher: unless(anyOf( |
238 | getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(), |
239 | hasInClassInitializer(InnerMatcher: anything()), |
240 | hasParent(recordDecl(isUnion()))))), |
241 | withInitializer(InnerMatcher: Init)) |
242 | .bind(ID: "default" ))), |
243 | Action: this); |
244 | |
245 | Finder->addMatcher( |
246 | NodeMatch: cxxConstructorDecl(forEachConstructorInitializer( |
247 | InnerMatcher: cxxCtorInitializer(forField(InnerMatcher: hasInClassInitializer(InnerMatcher: anything())), |
248 | withInitializer(InnerMatcher: Init)) |
249 | .bind(ID: "existing" ))), |
250 | Action: this); |
251 | } |
252 | |
253 | void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) { |
254 | if (const auto *Default = |
255 | Result.Nodes.getNodeAs<CXXCtorInitializer>(ID: "default" )) |
256 | checkDefaultInit(Result, Init: Default); |
257 | else if (const auto *Existing = |
258 | Result.Nodes.getNodeAs<CXXCtorInitializer>(ID: "existing" )) |
259 | checkExistingInit(Result, Init: Existing); |
260 | else |
261 | llvm_unreachable("Bad Callback. No node provided." ); |
262 | } |
263 | |
264 | void UseDefaultMemberInitCheck::checkDefaultInit( |
265 | const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) { |
266 | const FieldDecl *Field = Init->getAnyMember(); |
267 | |
268 | // Check whether we have multiple hand-written constructors and bomb out, as |
269 | // it is hard to reconcile their sets of member initializers. |
270 | const auto *ClassDecl = cast<CXXRecordDecl>(Val: Field->getParent()); |
271 | if (llvm::count_if(ClassDecl->decls(), [](const Decl *D) { |
272 | if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(Val: D)) |
273 | D = FTD->getTemplatedDecl(); |
274 | if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Val: D)) |
275 | return !Ctor->isCopyOrMoveConstructor(); |
276 | return false; |
277 | }) > 1) |
278 | return; |
279 | |
280 | SourceLocation StartLoc = Field->getBeginLoc(); |
281 | if (StartLoc.isMacroID() && IgnoreMacros) |
282 | return; |
283 | |
284 | SourceLocation FieldEnd = |
285 | Lexer::getLocForEndOfToken(Loc: Field->getSourceRange().getEnd(), Offset: 0, |
286 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
287 | SourceLocation LParenEnd = Lexer::getLocForEndOfToken( |
288 | Loc: Init->getLParenLoc(), Offset: 0, SM: *Result.SourceManager, LangOpts: getLangOpts()); |
289 | CharSourceRange InitRange = |
290 | CharSourceRange::getCharRange(B: LParenEnd, E: Init->getRParenLoc()); |
291 | |
292 | const Expr *InitExpression = Init->getInit(); |
293 | const QualType InitType = InitExpression->getType(); |
294 | |
295 | const bool ValueInit = |
296 | isa<ImplicitValueInitExpr>(Val: InitExpression) && !isa<ArrayType>(Val: InitType); |
297 | const bool CanAssign = |
298 | UseAssignment && (!ValueInit || !InitType->isEnumeralType()); |
299 | const bool NeedsBraces = !CanAssign || isa<ArrayType>(Val: InitType); |
300 | |
301 | auto Diag = |
302 | diag(Field->getLocation(), "use default member initializer for %0" ) |
303 | << Field; |
304 | |
305 | if (CanAssign) |
306 | Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: " = " ); |
307 | if (NeedsBraces) |
308 | Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: "{" ); |
309 | |
310 | if (CanAssign && ValueInit) |
311 | Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: getValueOfValueInit(InitType)); |
312 | else |
313 | Diag << FixItHint::CreateInsertionFromRange(InsertionLoc: FieldEnd, FromRange: InitRange); |
314 | |
315 | if (NeedsBraces) |
316 | Diag << FixItHint::CreateInsertion(InsertionLoc: FieldEnd, Code: "}" ); |
317 | |
318 | Diag << FixItHint::CreateRemoval(RemoveRange: Init->getSourceRange()); |
319 | } |
320 | |
321 | void UseDefaultMemberInitCheck::checkExistingInit( |
322 | const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) { |
323 | const FieldDecl *Field = Init->getAnyMember(); |
324 | |
325 | if (!sameValue(E1: Field->getInClassInitializer(), E2: Init->getInit())) |
326 | return; |
327 | |
328 | diag(Loc: Init->getSourceLocation(), Description: "member initializer for %0 is redundant" ) |
329 | << Field << FixItHint::CreateRemoval(RemoveRange: Init->getSourceRange()); |
330 | } |
331 | |
332 | } // namespace clang::tidy::modernize |
333 | |