xref: /llvm-project/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp (revision b0b491d458962136c696366b8cf535d54511baf3)
1 //===--- AvoidCStyleCastsCheck.cpp - clang-tidy -----------------*- C++ -*-===//
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 "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"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang::tidy::google::readability {
18 
registerMatchers(ast_matchers::MatchFinder * Finder)19 void AvoidCStyleCastsCheck::registerMatchers(
20     ast_matchers::MatchFinder *Finder) {
21   Finder->addMatcher(
22       cStyleCastExpr(
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           .bind("cast"),
28       this);
29 
30   Finder->addMatcher(
31       cxxFunctionalCastExpr(
32           hasDestinationType(hasCanonicalType(anyOf(
33               builtinType(), references(qualType()), pointsTo(qualType())))),
34           unless(
35               hasSourceExpression(anyOf(cxxConstructExpr(), initListExpr()))))
36           .bind("cast"),
37       this);
38 }
39 
needsConstCast(QualType SourceType,QualType DestType)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());
48     }
49   }
50   return false;
51 }
52 
pointedUnqualifiedTypesAreEqual(QualType T1,QualType T2)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();
58   }
59   return T1.getUnqualifiedType() == T2.getUnqualifiedType();
60 }
61 
getReplaceRange(const ExplicitCastExpr * Expr)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");
71 }
72 
getDestTypeString(const SourceManager & SM,const LangOptions & LangOpts,const ExplicitCastExpr * Expr)73 static StringRef getDestTypeString(const SourceManager &SM,
74                                    const LangOptions &LangOpts,
75                                    const ExplicitCastExpr *Expr) {
76   SourceLocation BeginLoc;
77   SourceLocation EndLoc;
78 
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);
85   } else
86     llvm_unreachable("Unsupported CastExpr");
87 
88   return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),
89                               SM, LangOpts);
90 }
91 
check(const MatchFinder::MatchResult & Result)92 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
93   const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
94 
95   // Ignore casts in macros.
96   if (CastExpr->getExprLoc().isMacroID())
97     return;
98 
99   // Casting to void is an idiomatic way to mute "unused variable" and similar
100   // warnings.
101   if (CastExpr->getCastKind() == CK_ToVoid)
102     return;
103 
104   auto IsFunction = [](QualType T) {
105     T = T.getCanonicalType().getNonReferenceType();
106     return T->isFunctionType() || T->isFunctionPointerType() ||
107            T->isMemberFunctionPointerType();
108   };
109 
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();
116 
117   CharSourceRange ReplaceRange = getReplaceRange(CastExpr);
118 
119   bool FnToFnCast =
120       IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);
121 
122   const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() &&
123       DestTypeAsWritten->isRecordType() &&
124       !DestTypeAsWritten->isElaboratedTypeSpecifier();
125 
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();
136     if (Src == Dst) {
137       diag(CastExpr->getBeginLoc(), "redundant cast to the same type")
138           << FixItHint::CreateRemoval(ReplaceRange);
139       return;
140     }
141   }
142 
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)
146     return;
147   // Ignore code inside extern "C" {} blocks.
148   if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context)
149            .empty())
150     return;
151   // Ignore code in .c files and headers included from them, even if they are
152   // compiled as C++.
153   if (getCurrentMainFile().ends_with(".c"))
154     return;
155 
156   SourceManager &SM = *Result.SourceManager;
157 
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()))
161           .ends_with(".c"))
162     return;
163 
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);
167 
168   auto Diag =
169       diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
170 
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,
177                                      getLangOpts()),
178           ")");
179     }
180     Diag << FixItHint::CreateReplacement(ReplaceRange, CastText);
181   };
182   auto ReplaceWithNamedCast = [&](StringRef CastType) {
183     Diag << CastType;
184     ReplaceWithCast((CastType + "<" + DestTypeString + ">").str());
185   };
186   auto ReplaceWithConstructorCall = [&]() {
187     Diag << "constructor call syntax";
188     // FIXME: Validate DestTypeString, maybe.
189     ReplaceWithCast(DestTypeString.str());
190   };
191   // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
192   switch (CastExpr->getCastKind()) {
193   case CK_FunctionToPointerDecay:
194     ReplaceWithNamedCast("static_cast");
195     return;
196   case CK_ConstructorConversion:
197     if (ConstructorCast) {
198       ReplaceWithConstructorCall();
199     } else {
200       ReplaceWithNamedCast("static_cast");
201     }
202     return;
203   case CK_NoOp:
204     if (FnToFnCast) {
205       ReplaceWithNamedCast("static_cast");
206       return;
207     }
208     if (SourceType == DestType) {
209       Diag << "static_cast (if needed, the cast may be redundant)";
210       ReplaceWithCast(("static_cast<" + DestTypeString + ">").str());
211       return;
212     }
213     if (needsConstCast(SourceType, DestType) &&
214         pointedUnqualifiedTypesAreEqual(SourceType, DestType)) {
215       ReplaceWithNamedCast("const_cast");
216       return;
217     }
218     if (ConstructorCast) {
219       ReplaceWithConstructorCall();
220       return;
221     }
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");
228         return;
229       }
230       break;
231     }
232     [[fallthrough]];
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
236     // still retained.
237     if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) &&
238         (DestType->isBuiltinType() || DestType->isEnumeralType())) {
239       ReplaceWithNamedCast("static_cast");
240       return;
241     }
242     break;
243   case CK_BitCast:
244     // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
245     if (!needsConstCast(SourceType, DestType)) {
246       if (SourceType->isVoidPointerType())
247         ReplaceWithNamedCast("static_cast");
248       else
249         ReplaceWithNamedCast("reinterpret_cast");
250       return;
251     }
252     break;
253   default:
254     break;
255   }
256 
257   Diag << "static_cast/const_cast/reinterpret_cast";
258 }
259 
260 } // namespace clang::tidy::google::readability
261