1 //===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "AvoidCStyleCastsCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Lex/Lexer.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::google::readability
{
19 void AvoidCStyleCastsCheck::registerMatchers(
20 ast_matchers::MatchFinder
*Finder
) {
23 // Filter out (EnumType)IntegerLiteral construct, which is generated
24 // for non-type template arguments of enum types.
25 // FIXME: Remove this once this is fixed in the AST.
26 unless(hasParent(substNonTypeTemplateParmExpr())))
31 cxxFunctionalCastExpr(
32 hasDestinationType(hasCanonicalType(anyOf(
33 builtinType(), references(qualType()), pointsTo(qualType())))),
35 hasSourceExpression(anyOf(cxxConstructExpr(), initListExpr()))))
40 static bool needsConstCast(QualType SourceType
, QualType DestType
) {
41 while ((SourceType
->isPointerType() && DestType
->isPointerType()) ||
42 (SourceType
->isReferenceType() && DestType
->isReferenceType())) {
43 SourceType
= SourceType
->getPointeeType();
44 DestType
= DestType
->getPointeeType();
45 if (SourceType
.isConstQualified() && !DestType
.isConstQualified()) {
46 return (SourceType
->isPointerType() == DestType
->isPointerType()) &&
47 (SourceType
->isReferenceType() == DestType
->isReferenceType());
53 static bool pointedUnqualifiedTypesAreEqual(QualType T1
, QualType T2
) {
54 while ((T1
->isPointerType() && T2
->isPointerType()) ||
55 (T1
->isReferenceType() && T2
->isReferenceType())) {
56 T1
= T1
->getPointeeType();
57 T2
= T2
->getPointeeType();
59 return T1
.getUnqualifiedType() == T2
.getUnqualifiedType();
62 static clang::CharSourceRange
getReplaceRange(const ExplicitCastExpr
*Expr
) {
63 if (const auto *CastExpr
= dyn_cast
<CStyleCastExpr
>(Expr
))
64 return CharSourceRange::getCharRange(
65 CastExpr
->getLParenLoc(),
66 CastExpr
->getSubExprAsWritten()->getBeginLoc());
67 if (const auto *CastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(Expr
))
68 return CharSourceRange::getCharRange(CastExpr
->getBeginLoc(),
69 CastExpr
->getLParenLoc());
70 llvm_unreachable("Unsupported CastExpr");
73 static StringRef
getDestTypeString(const SourceManager
&SM
,
74 const LangOptions
&LangOpts
,
75 const ExplicitCastExpr
*Expr
) {
76 SourceLocation BeginLoc
;
77 SourceLocation EndLoc
;
79 if (const auto *CastExpr
= dyn_cast
<CStyleCastExpr
>(Expr
)) {
80 BeginLoc
= CastExpr
->getLParenLoc().getLocWithOffset(1);
81 EndLoc
= CastExpr
->getRParenLoc().getLocWithOffset(-1);
82 } else if (const auto *CastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(Expr
)) {
83 BeginLoc
= CastExpr
->getBeginLoc();
84 EndLoc
= CastExpr
->getLParenLoc().getLocWithOffset(-1);
86 llvm_unreachable("Unsupported CastExpr");
88 return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc
, EndLoc
),
92 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult
&Result
) {
93 const auto *CastExpr
= Result
.Nodes
.getNodeAs
<ExplicitCastExpr
>("cast");
95 // Ignore casts in macros.
96 if (CastExpr
->getExprLoc().isMacroID())
99 // Casting to void is an idiomatic way to mute "unused variable" and similar
101 if (CastExpr
->getCastKind() == CK_ToVoid
)
104 auto IsFunction
= [](QualType T
) {
105 T
= T
.getCanonicalType().getNonReferenceType();
106 return T
->isFunctionType() || T
->isFunctionPointerType() ||
107 T
->isMemberFunctionPointerType();
110 const QualType DestTypeAsWritten
=
111 CastExpr
->getTypeAsWritten().getUnqualifiedType();
112 const QualType SourceTypeAsWritten
=
113 CastExpr
->getSubExprAsWritten()->getType().getUnqualifiedType();
114 const QualType SourceType
= SourceTypeAsWritten
.getCanonicalType();
115 const QualType DestType
= DestTypeAsWritten
.getCanonicalType();
117 CharSourceRange ReplaceRange
= getReplaceRange(CastExpr
);
120 IsFunction(SourceTypeAsWritten
) && IsFunction(DestTypeAsWritten
);
122 const bool ConstructorCast
= !CastExpr
->getTypeAsWritten().hasQualifiers() &&
123 DestTypeAsWritten
->isRecordType() &&
124 !DestTypeAsWritten
->isElaboratedTypeSpecifier();
126 if (CastExpr
->getCastKind() == CK_NoOp
&& !FnToFnCast
) {
127 // Function pointer/reference casts may be needed to resolve ambiguities in
128 // case of overloaded functions, so detection of redundant casts is trickier
129 // in this case. Don't emit "redundant cast" warnings for function
130 // pointer/reference types.
131 QualType Src
= SourceTypeAsWritten
, Dst
= DestTypeAsWritten
;
132 if (const auto *ElTy
= dyn_cast
<ElaboratedType
>(Src
))
133 Src
= ElTy
->getNamedType();
134 if (const auto *ElTy
= dyn_cast
<ElaboratedType
>(Dst
))
135 Dst
= ElTy
->getNamedType();
137 diag(CastExpr
->getBeginLoc(), "redundant cast to the same type")
138 << FixItHint::CreateRemoval(ReplaceRange
);
143 // The rest of this check is only relevant to C++.
144 // We also disable it for Objective-C++.
145 if (!getLangOpts().CPlusPlus
|| getLangOpts().ObjC
)
147 // Ignore code inside extern "C" {} blocks.
148 if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr
, *Result
.Context
)
151 // Ignore code in .c files and headers included from them, even if they are
153 if (getCurrentMainFile().ends_with(".c"))
156 SourceManager
&SM
= *Result
.SourceManager
;
158 // Ignore code in .c files #included in other files (which shouldn't be done,
159 // but people still do this for test and other purposes).
160 if (SM
.getFilename(SM
.getSpellingLoc(CastExpr
->getBeginLoc()))
164 // Leave type spelling exactly as it was (unlike
165 // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
166 StringRef DestTypeString
= getDestTypeString(SM
, getLangOpts(), CastExpr
);
169 diag(CastExpr
->getBeginLoc(), "C-style casts are discouraged; use %0");
171 auto ReplaceWithCast
= [&](std::string CastText
) {
172 const Expr
*SubExpr
= CastExpr
->getSubExprAsWritten()->IgnoreImpCasts();
173 if (!isa
<ParenExpr
>(SubExpr
) && !isa
<CXXFunctionalCastExpr
>(CastExpr
)) {
174 CastText
.push_back('(');
175 Diag
<< FixItHint::CreateInsertion(
176 Lexer::getLocForEndOfToken(SubExpr
->getEndLoc(), 0, SM
,
180 Diag
<< FixItHint::CreateReplacement(ReplaceRange
, CastText
);
182 auto ReplaceWithNamedCast
= [&](StringRef CastType
) {
184 ReplaceWithCast((CastType
+ "<" + DestTypeString
+ ">").str());
186 auto ReplaceWithConstructorCall
= [&]() {
187 Diag
<< "constructor call syntax";
188 // FIXME: Validate DestTypeString, maybe.
189 ReplaceWithCast(DestTypeString
.str());
191 // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
192 switch (CastExpr
->getCastKind()) {
193 case CK_FunctionToPointerDecay
:
194 ReplaceWithNamedCast("static_cast");
196 case CK_ConstructorConversion
:
197 if (ConstructorCast
) {
198 ReplaceWithConstructorCall();
200 ReplaceWithNamedCast("static_cast");
205 ReplaceWithNamedCast("static_cast");
208 if (SourceType
== DestType
) {
209 Diag
<< "static_cast (if needed, the cast may be redundant)";
210 ReplaceWithCast(("static_cast<" + DestTypeString
+ ">").str());
213 if (needsConstCast(SourceType
, DestType
) &&
214 pointedUnqualifiedTypesAreEqual(SourceType
, DestType
)) {
215 ReplaceWithNamedCast("const_cast");
218 if (ConstructorCast
) {
219 ReplaceWithConstructorCall();
222 if (DestType
->isReferenceType()) {
223 QualType Dest
= DestType
.getNonReferenceType();
224 QualType Source
= SourceType
.getNonReferenceType();
225 if (Source
== Dest
.withConst() ||
226 SourceType
.getNonReferenceType() == DestType
.getNonReferenceType()) {
227 ReplaceWithNamedCast("const_cast");
233 case clang::CK_IntegralCast
:
234 // Convert integral and no-op casts between builtin types and enums to
235 // static_cast. A cast from enum to integer may be unnecessary, but it's
237 if ((SourceType
->isBuiltinType() || SourceType
->isEnumeralType()) &&
238 (DestType
->isBuiltinType() || DestType
->isEnumeralType())) {
239 ReplaceWithNamedCast("static_cast");
244 // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
245 if (!needsConstCast(SourceType
, DestType
)) {
246 if (SourceType
->isVoidPointerType())
247 ReplaceWithNamedCast("static_cast");
249 ReplaceWithNamedCast("reinterpret_cast");
257 Diag
<< "static_cast/const_cast/reinterpret_cast";
260 } // namespace clang::tidy::google::readability