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())),
27 // Avoid matches in template instantiations.
28 unless(isInTemplateInstantiation()))
32 cxxFunctionalCastExpr(unless(hasDescendant(cxxConstructExpr())),
33 unless(hasDescendant(initListExpr())))
38 static bool needsConstCast(QualType SourceType
, QualType DestType
) {
39 while ((SourceType
->isPointerType() && DestType
->isPointerType()) ||
40 (SourceType
->isReferenceType() && DestType
->isReferenceType())) {
41 SourceType
= SourceType
->getPointeeType();
42 DestType
= DestType
->getPointeeType();
43 if (SourceType
.isConstQualified() && !DestType
.isConstQualified()) {
44 return (SourceType
->isPointerType() == DestType
->isPointerType()) &&
45 (SourceType
->isReferenceType() == DestType
->isReferenceType());
51 static bool pointedUnqualifiedTypesAreEqual(QualType T1
, QualType T2
) {
52 while ((T1
->isPointerType() && T2
->isPointerType()) ||
53 (T1
->isReferenceType() && T2
->isReferenceType())) {
54 T1
= T1
->getPointeeType();
55 T2
= T2
->getPointeeType();
57 return T1
.getUnqualifiedType() == T2
.getUnqualifiedType();
60 static clang::CharSourceRange
getReplaceRange(const ExplicitCastExpr
*Expr
) {
61 if (const auto *CastExpr
= dyn_cast
<CStyleCastExpr
>(Expr
))
62 return CharSourceRange::getCharRange(
63 CastExpr
->getLParenLoc(),
64 CastExpr
->getSubExprAsWritten()->getBeginLoc());
65 if (const auto *CastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(Expr
))
66 return CharSourceRange::getCharRange(CastExpr
->getBeginLoc(),
67 CastExpr
->getLParenLoc());
68 llvm_unreachable("Unsupported CastExpr");
71 static StringRef
getDestTypeString(const SourceManager
&SM
,
72 const LangOptions
&LangOpts
,
73 const ExplicitCastExpr
*Expr
) {
74 SourceLocation BeginLoc
;
75 SourceLocation EndLoc
;
77 if (const auto *CastExpr
= dyn_cast
<CStyleCastExpr
>(Expr
)) {
78 BeginLoc
= CastExpr
->getLParenLoc().getLocWithOffset(1);
79 EndLoc
= CastExpr
->getRParenLoc().getLocWithOffset(-1);
80 } else if (const auto *CastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(Expr
)) {
81 BeginLoc
= CastExpr
->getBeginLoc();
82 EndLoc
= CastExpr
->getLParenLoc().getLocWithOffset(-1);
84 llvm_unreachable("Unsupported CastExpr");
86 return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc
, EndLoc
),
90 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult
&Result
) {
91 const auto *CastExpr
= Result
.Nodes
.getNodeAs
<ExplicitCastExpr
>("cast");
93 // Ignore casts in macros.
94 if (CastExpr
->getExprLoc().isMacroID())
97 // Casting to void is an idiomatic way to mute "unused variable" and similar
99 if (CastExpr
->getCastKind() == CK_ToVoid
)
102 auto IsFunction
= [](QualType T
) {
103 T
= T
.getCanonicalType().getNonReferenceType();
104 return T
->isFunctionType() || T
->isFunctionPointerType() ||
105 T
->isMemberFunctionPointerType();
108 const QualType DestTypeAsWritten
=
109 CastExpr
->getTypeAsWritten().getUnqualifiedType();
110 const QualType SourceTypeAsWritten
=
111 CastExpr
->getSubExprAsWritten()->getType().getUnqualifiedType();
112 const QualType SourceType
= SourceTypeAsWritten
.getCanonicalType();
113 const QualType DestType
= DestTypeAsWritten
.getCanonicalType();
115 CharSourceRange ReplaceRange
= getReplaceRange(CastExpr
);
118 IsFunction(SourceTypeAsWritten
) && IsFunction(DestTypeAsWritten
);
120 const bool ConstructorCast
= !CastExpr
->getTypeAsWritten().hasQualifiers() &&
121 DestTypeAsWritten
->isRecordType() &&
122 !DestTypeAsWritten
->isElaboratedTypeSpecifier();
124 if (CastExpr
->getCastKind() == CK_NoOp
&& !FnToFnCast
) {
125 // Function pointer/reference casts may be needed to resolve ambiguities in
126 // case of overloaded functions, so detection of redundant casts is trickier
127 // in this case. Don't emit "redundant cast" warnings for function
128 // pointer/reference types.
129 QualType Src
= SourceTypeAsWritten
, Dst
= DestTypeAsWritten
;
130 if (const auto *ElTy
= dyn_cast
<ElaboratedType
>(Src
))
131 Src
= ElTy
->getNamedType();
132 if (const auto *ElTy
= dyn_cast
<ElaboratedType
>(Dst
))
133 Dst
= ElTy
->getNamedType();
135 diag(CastExpr
->getBeginLoc(), "redundant cast to the same type")
136 << FixItHint::CreateRemoval(ReplaceRange
);
141 // The rest of this check is only relevant to C++.
142 // We also disable it for Objective-C++.
143 if (!getLangOpts().CPlusPlus
|| getLangOpts().ObjC
)
145 // Ignore code inside extern "C" {} blocks.
146 if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr
, *Result
.Context
)
149 // Ignore code in .c files and headers included from them, even if they are
151 if (getCurrentMainFile().endswith(".c"))
154 SourceManager
&SM
= *Result
.SourceManager
;
156 // Ignore code in .c files #included in other files (which shouldn't be done,
157 // but people still do this for test and other purposes).
158 if (SM
.getFilename(SM
.getSpellingLoc(CastExpr
->getBeginLoc())).endswith(".c"))
161 // Leave type spelling exactly as it was (unlike
162 // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
163 StringRef DestTypeString
= getDestTypeString(SM
, getLangOpts(), CastExpr
);
166 diag(CastExpr
->getBeginLoc(), "C-style casts are discouraged; use %0");
168 auto ReplaceWithCast
= [&](std::string CastText
) {
169 const Expr
*SubExpr
= CastExpr
->getSubExprAsWritten()->IgnoreImpCasts();
170 if (!isa
<ParenExpr
>(SubExpr
) && !isa
<CXXFunctionalCastExpr
>(CastExpr
)) {
171 CastText
.push_back('(');
172 Diag
<< FixItHint::CreateInsertion(
173 Lexer::getLocForEndOfToken(SubExpr
->getEndLoc(), 0, SM
,
177 Diag
<< FixItHint::CreateReplacement(ReplaceRange
, CastText
);
179 auto ReplaceWithNamedCast
= [&](StringRef CastType
) {
181 ReplaceWithCast((CastType
+ "<" + DestTypeString
+ ">").str());
183 auto ReplaceWithConstructorCall
= [&]() {
184 Diag
<< "constructor call syntax";
185 // FIXME: Validate DestTypeString, maybe.
186 ReplaceWithCast(DestTypeString
.str());
188 // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
189 switch (CastExpr
->getCastKind()) {
190 case CK_FunctionToPointerDecay
:
191 ReplaceWithNamedCast("static_cast");
193 case CK_ConstructorConversion
:
194 if (ConstructorCast
) {
195 ReplaceWithConstructorCall();
197 ReplaceWithNamedCast("static_cast");
202 ReplaceWithNamedCast("static_cast");
205 if (SourceType
== DestType
) {
206 Diag
<< "static_cast (if needed, the cast may be redundant)";
207 ReplaceWithCast(("static_cast<" + DestTypeString
+ ">").str());
210 if (needsConstCast(SourceType
, DestType
) &&
211 pointedUnqualifiedTypesAreEqual(SourceType
, DestType
)) {
212 ReplaceWithNamedCast("const_cast");
215 if (ConstructorCast
) {
216 ReplaceWithConstructorCall();
219 if (DestType
->isReferenceType()) {
220 QualType Dest
= DestType
.getNonReferenceType();
221 QualType Source
= SourceType
.getNonReferenceType();
222 if (Source
== Dest
.withConst() ||
223 SourceType
.getNonReferenceType() == DestType
.getNonReferenceType()) {
224 ReplaceWithNamedCast("const_cast");
230 case clang::CK_IntegralCast
:
231 // Convert integral and no-op casts between builtin types and enums to
232 // static_cast. A cast from enum to integer may be unnecessary, but it's
234 if ((SourceType
->isBuiltinType() || SourceType
->isEnumeralType()) &&
235 (DestType
->isBuiltinType() || DestType
->isEnumeralType())) {
236 ReplaceWithNamedCast("static_cast");
241 // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
242 if (!needsConstCast(SourceType
, DestType
)) {
243 if (SourceType
->isVoidPointerType())
244 ReplaceWithNamedCast("static_cast");
246 ReplaceWithNamedCast("reinterpret_cast");
254 Diag
<< "static_cast/const_cast/reinterpret_cast";
257 } // namespace clang::tidy::google::readability