1 | //===--- RedundantCastingCheck.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 "RedundantCastingCheck.h" |
10 | #include "../utils/FixItHintUtils.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | #include "clang/Lex/Lexer.h" |
14 | |
15 | using namespace clang::ast_matchers; |
16 | |
17 | namespace clang::tidy::readability { |
18 | |
19 | static bool areTypesEqual(QualType S, QualType D) { |
20 | if (S == D) |
21 | return true; |
22 | |
23 | const auto *TS = S->getAs<TypedefType>(); |
24 | const auto *TD = D->getAs<TypedefType>(); |
25 | if (TS != TD) |
26 | return false; |
27 | |
28 | QualType PtrS = S->getPointeeType(); |
29 | QualType PtrD = D->getPointeeType(); |
30 | |
31 | if (!PtrS.isNull() && !PtrD.isNull()) |
32 | return areTypesEqual(S: PtrS, D: PtrD); |
33 | |
34 | const DeducedType *DT = S->getContainedDeducedType(); |
35 | if (DT && DT->isDeduced()) |
36 | return D == DT->getDeducedType(); |
37 | |
38 | return false; |
39 | } |
40 | |
41 | static bool areTypesEqual(QualType TypeS, QualType TypeD, |
42 | bool IgnoreTypeAliases) { |
43 | const QualType CTypeS = TypeS.getCanonicalType(); |
44 | const QualType CTypeD = TypeD.getCanonicalType(); |
45 | if (CTypeS != CTypeD) |
46 | return false; |
47 | |
48 | return IgnoreTypeAliases || areTypesEqual(S: TypeS.getLocalUnqualifiedType(), |
49 | D: TypeD.getLocalUnqualifiedType()); |
50 | } |
51 | |
52 | static bool areBinaryOperatorOperandsTypesEqualToOperatorResultType( |
53 | const Expr *E, bool IgnoreTypeAliases) { |
54 | if (!E) |
55 | return true; |
56 | const Expr *WithoutImplicitAndParen = E->IgnoreParenImpCasts(); |
57 | if (!WithoutImplicitAndParen) |
58 | return true; |
59 | if (const auto *B = dyn_cast<BinaryOperator>(Val: WithoutImplicitAndParen)) { |
60 | const QualType Type = WithoutImplicitAndParen->getType(); |
61 | if (Type.isNull()) |
62 | return true; |
63 | |
64 | const QualType NonReferenceType = Type.getNonReferenceType(); |
65 | const QualType LHSType = B->getLHS()->IgnoreImplicit()->getType(); |
66 | if (LHSType.isNull() || !areTypesEqual(TypeS: LHSType.getNonReferenceType(), |
67 | TypeD: NonReferenceType, IgnoreTypeAliases)) |
68 | return false; |
69 | const QualType RHSType = B->getRHS()->IgnoreImplicit()->getType(); |
70 | if (RHSType.isNull() || !areTypesEqual(TypeS: RHSType.getNonReferenceType(), |
71 | TypeD: NonReferenceType, IgnoreTypeAliases)) |
72 | return false; |
73 | } |
74 | return true; |
75 | } |
76 | |
77 | static const Decl *getSourceExprDecl(const Expr *SourceExpr) { |
78 | const Expr *CleanSourceExpr = SourceExpr->IgnoreParenImpCasts(); |
79 | if (const auto *E = dyn_cast<DeclRefExpr>(Val: CleanSourceExpr)) { |
80 | return E->getDecl(); |
81 | } |
82 | |
83 | if (const auto *E = dyn_cast<CallExpr>(Val: CleanSourceExpr)) { |
84 | return E->getCalleeDecl(); |
85 | } |
86 | |
87 | if (const auto *E = dyn_cast<MemberExpr>(Val: CleanSourceExpr)) { |
88 | return E->getMemberDecl(); |
89 | } |
90 | return nullptr; |
91 | } |
92 | |
93 | RedundantCastingCheck::RedundantCastingCheck(StringRef Name, |
94 | ClangTidyContext *Context) |
95 | : ClangTidyCheck(Name, Context), |
96 | IgnoreMacros(Options.getLocalOrGlobal(LocalName: "IgnoreMacros" , Default: true)), |
97 | IgnoreTypeAliases(Options.getLocalOrGlobal(LocalName: "IgnoreTypeAliases" , Default: false)) {} |
98 | |
99 | void RedundantCastingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
100 | Options.store(Options&: Opts, LocalName: "IgnoreMacros" , Value: IgnoreMacros); |
101 | Options.store(Options&: Opts, LocalName: "IgnoreTypeAliases" , Value: IgnoreTypeAliases); |
102 | } |
103 | |
104 | void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) { |
105 | auto SimpleType = qualType(hasCanonicalType( |
106 | InnerMatcher: qualType(anyOf(builtinType(), references(InnerMatcher: builtinType()), |
107 | references(InnerMatcher: pointsTo(InnerMatcher: qualType())), pointsTo(InnerMatcher: qualType()))))); |
108 | |
109 | auto BitfieldMemberExpr = memberExpr(member(InnerMatcher: fieldDecl(isBitField()))); |
110 | |
111 | Finder->addMatcher( |
112 | NodeMatch: explicitCastExpr( |
113 | unless(hasCastKind(Kind: CK_ConstructorConversion)), |
114 | unless(hasCastKind(Kind: CK_UserDefinedConversion)), |
115 | unless(cxxFunctionalCastExpr(hasDestinationType(InnerMatcher: unless(SimpleType)))), |
116 | |
117 | hasDestinationType(InnerMatcher: qualType().bind(ID: "dstType" )), |
118 | hasSourceExpression(InnerMatcher: anyOf( |
119 | expr(unless(initListExpr()), unless(BitfieldMemberExpr), |
120 | hasType(InnerMatcher: qualType().bind(ID: "srcType" ))) |
121 | .bind(ID: "source" ), |
122 | initListExpr(unless(hasInit(N: 1, InnerMatcher: expr())), |
123 | hasInit(N: 0, InnerMatcher: expr(unless(BitfieldMemberExpr), |
124 | hasType(InnerMatcher: qualType().bind(ID: "srcType" ))) |
125 | .bind(ID: "source" )))))) |
126 | .bind(ID: "cast" ), |
127 | Action: this); |
128 | } |
129 | |
130 | void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) { |
131 | const auto *SourceExpr = Result.Nodes.getNodeAs<Expr>(ID: "source" ); |
132 | auto TypeD = *Result.Nodes.getNodeAs<QualType>(ID: "dstType" ); |
133 | |
134 | if (SourceExpr->getValueKind() == VK_LValue && |
135 | TypeD.getCanonicalType()->isRValueReferenceType()) |
136 | return; |
137 | |
138 | const auto TypeS = |
139 | Result.Nodes.getNodeAs<QualType>(ID: "srcType" )->getNonReferenceType(); |
140 | TypeD = TypeD.getNonReferenceType(); |
141 | |
142 | if (!areTypesEqual(TypeS, TypeD, IgnoreTypeAliases)) |
143 | return; |
144 | |
145 | if (!areBinaryOperatorOperandsTypesEqualToOperatorResultType( |
146 | E: SourceExpr, IgnoreTypeAliases)) |
147 | return; |
148 | |
149 | const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>(ID: "cast" ); |
150 | if (IgnoreMacros && |
151 | (CastExpr->getBeginLoc().isMacroID() || |
152 | CastExpr->getEndLoc().isMacroID() || CastExpr->getExprLoc().isMacroID())) |
153 | return; |
154 | |
155 | { |
156 | auto Diag = diag(CastExpr->getExprLoc(), |
157 | "redundant explicit casting to the same type %0 as the " |
158 | "sub-expression, remove this casting" ); |
159 | Diag << TypeD; |
160 | |
161 | const SourceManager &SM = *Result.SourceManager; |
162 | const SourceLocation SourceExprBegin = |
163 | SM.getExpansionLoc(Loc: SourceExpr->getBeginLoc()); |
164 | const SourceLocation SourceExprEnd = |
165 | SM.getExpansionLoc(Loc: SourceExpr->getEndLoc()); |
166 | |
167 | if (SourceExprBegin != CastExpr->getBeginLoc()) |
168 | Diag << FixItHint::CreateRemoval(RemoveRange: SourceRange( |
169 | CastExpr->getBeginLoc(), SourceExprBegin.getLocWithOffset(Offset: -1))); |
170 | |
171 | const SourceLocation NextToken = Lexer::getLocForEndOfToken( |
172 | Loc: SourceExprEnd, Offset: 0U, SM, LangOpts: Result.Context->getLangOpts()); |
173 | |
174 | if (SourceExprEnd != CastExpr->getEndLoc()) { |
175 | Diag << FixItHint::CreateRemoval( |
176 | RemoveRange: SourceRange(NextToken, CastExpr->getEndLoc())); |
177 | } |
178 | |
179 | if (utils::fixit::areParensNeededForStatement(*SourceExpr)) { |
180 | Diag << FixItHint::CreateInsertion(InsertionLoc: SourceExprBegin, Code: "(" ) |
181 | << FixItHint::CreateInsertion(InsertionLoc: NextToken, Code: ")" ); |
182 | } |
183 | } |
184 | |
185 | const auto *SourceExprDecl = getSourceExprDecl(SourceExpr); |
186 | if (!SourceExprDecl) |
187 | return; |
188 | |
189 | if (const auto *D = dyn_cast<CXXConstructorDecl>(Val: SourceExprDecl)) { |
190 | diag(D->getLocation(), |
191 | "source type originates from the invocation of this constructor" , |
192 | DiagnosticIDs::Note); |
193 | return; |
194 | } |
195 | |
196 | if (const auto *D = dyn_cast<FunctionDecl>(Val: SourceExprDecl)) { |
197 | diag(D->getLocation(), |
198 | "source type originates from the invocation of this " |
199 | "%select{function|method}0" , |
200 | DiagnosticIDs::Note) |
201 | << isa<CXXMethodDecl>(Val: D) << D->getReturnTypeSourceRange(); |
202 | return; |
203 | } |
204 | |
205 | if (const auto *D = dyn_cast<FieldDecl>(Val: SourceExprDecl)) { |
206 | diag(D->getLocation(), |
207 | "source type originates from referencing this member" , |
208 | DiagnosticIDs::Note) |
209 | << SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc()); |
210 | return; |
211 | } |
212 | |
213 | if (const auto *D = dyn_cast<ParmVarDecl>(Val: SourceExprDecl)) { |
214 | diag(D->getLocation(), |
215 | "source type originates from referencing this parameter" , |
216 | DiagnosticIDs::Note) |
217 | << SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc()); |
218 | return; |
219 | } |
220 | |
221 | if (const auto *D = dyn_cast<VarDecl>(Val: SourceExprDecl)) { |
222 | diag(D->getLocation(), |
223 | "source type originates from referencing this variable" , |
224 | DiagnosticIDs::Note) |
225 | << SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc()); |
226 | return; |
227 | } |
228 | |
229 | if (const auto *D = dyn_cast<EnumConstantDecl>(Val: SourceExprDecl)) { |
230 | diag(D->getLocation(), |
231 | "source type originates from referencing this enum constant" , |
232 | DiagnosticIDs::Note); |
233 | return; |
234 | } |
235 | |
236 | if (const auto *D = dyn_cast<BindingDecl>(Val: SourceExprDecl)) { |
237 | diag(D->getLocation(), |
238 | "source type originates from referencing this bound variable" , |
239 | DiagnosticIDs::Note); |
240 | return; |
241 | } |
242 | |
243 | if (const auto *D = dyn_cast<NonTypeTemplateParmDecl>(Val: SourceExprDecl)) { |
244 | diag(D->getLocation(), |
245 | "source type originates from referencing this non-type template " |
246 | "parameter" , |
247 | DiagnosticIDs::Note); |
248 | return; |
249 | } |
250 | } |
251 | |
252 | } // namespace clang::tidy::readability |
253 | |