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 {
18 namespace tidy {
19 namespace google {
20 namespace readability {
21 
registerMatchers(ast_matchers::MatchFinder * Finder)22 void AvoidCStyleCastsCheck::registerMatchers(
23     ast_matchers::MatchFinder *Finder) {
24   Finder->addMatcher(
25       cStyleCastExpr(
26           // Filter out (EnumType)IntegerLiteral construct, which is generated
27           // for non-type template arguments of enum types.
28           // FIXME: Remove this once this is fixed in the AST.
29           unless(hasParent(substNonTypeTemplateParmExpr())),
30           // Avoid matches in template instantiations.
31           unless(isInTemplateInstantiation()))
32           .bind("cast"),
33       this);
34   Finder->addMatcher(
35       cxxFunctionalCastExpr(unless(hasDescendant(cxxConstructExpr())),
36                             unless(hasDescendant(initListExpr())))
37           .bind("cast"),
38       this);
39 }
40 
needsConstCast(QualType SourceType,QualType DestType)41 static bool needsConstCast(QualType SourceType, QualType DestType) {
42   while ((SourceType->isPointerType() && DestType->isPointerType()) ||
43          (SourceType->isReferenceType() && DestType->isReferenceType())) {
44     SourceType = SourceType->getPointeeType();
45     DestType = DestType->getPointeeType();
46     if (SourceType.isConstQualified() && !DestType.isConstQualified()) {
47       return (SourceType->isPointerType() == DestType->isPointerType()) &&
48              (SourceType->isReferenceType() == DestType->isReferenceType());
49     }
50   }
51   return false;
52 }
53 
pointedUnqualifiedTypesAreEqual(QualType T1,QualType T2)54 static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) {
55   while ((T1->isPointerType() && T2->isPointerType()) ||
56          (T1->isReferenceType() && T2->isReferenceType())) {
57     T1 = T1->getPointeeType();
58     T2 = T2->getPointeeType();
59   }
60   return T1.getUnqualifiedType() == T2.getUnqualifiedType();
61 }
62 
getReplaceRange(const ExplicitCastExpr * Expr)63 static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr) {
64   if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {
65     return CharSourceRange::getCharRange(
66         CastExpr->getLParenLoc(),
67         CastExpr->getSubExprAsWritten()->getBeginLoc());
68   } else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {
69     return CharSourceRange::getCharRange(CastExpr->getBeginLoc(),
70                                          CastExpr->getLParenLoc());
71   } else
72     llvm_unreachable("Unsupported CastExpr");
73 }
74 
getDestTypeString(const SourceManager & SM,const LangOptions & LangOpts,const ExplicitCastExpr * Expr)75 static StringRef getDestTypeString(const SourceManager &SM,
76                                    const LangOptions &LangOpts,
77                                    const ExplicitCastExpr *Expr) {
78   SourceLocation BeginLoc;
79   SourceLocation EndLoc;
80 
81   if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {
82     BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(1);
83     EndLoc = CastExpr->getRParenLoc().getLocWithOffset(-1);
84   } else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {
85     BeginLoc = CastExpr->getBeginLoc();
86     EndLoc = CastExpr->getLParenLoc().getLocWithOffset(-1);
87   } else
88     llvm_unreachable("Unsupported CastExpr");
89 
90   return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),
91                               SM, LangOpts);
92 }
93 
check(const MatchFinder::MatchResult & Result)94 void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
95   const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
96 
97   // Ignore casts in macros.
98   if (CastExpr->getExprLoc().isMacroID())
99     return;
100 
101   // Casting to void is an idiomatic way to mute "unused variable" and similar
102   // warnings.
103   if (CastExpr->getCastKind() == CK_ToVoid)
104     return;
105 
106   auto IsFunction = [](QualType T) {
107     T = T.getCanonicalType().getNonReferenceType();
108     return T->isFunctionType() || T->isFunctionPointerType() ||
109            T->isMemberFunctionPointerType();
110   };
111 
112   const QualType DestTypeAsWritten =
113       CastExpr->getTypeAsWritten().getUnqualifiedType();
114   const QualType SourceTypeAsWritten =
115       CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType();
116   const QualType SourceType = SourceTypeAsWritten.getCanonicalType();
117   const QualType DestType = DestTypeAsWritten.getCanonicalType();
118 
119   CharSourceRange ReplaceRange = getReplaceRange(CastExpr);
120 
121   bool FnToFnCast =
122       IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);
123 
124   const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() &&
125       DestTypeAsWritten->isRecordType() &&
126       !DestTypeAsWritten->isElaboratedTypeSpecifier();
127 
128   if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) {
129     // Function pointer/reference casts may be needed to resolve ambiguities in
130     // case of overloaded functions, so detection of redundant casts is trickier
131     // in this case. Don't emit "redundant cast" warnings for function
132     // pointer/reference types.
133     if (SourceTypeAsWritten == DestTypeAsWritten) {
134       diag(CastExpr->getBeginLoc(), "redundant cast to the same type")
135           << FixItHint::CreateRemoval(ReplaceRange);
136       return;
137     }
138   }
139 
140   // The rest of this check is only relevant to C++.
141   // We also disable it for Objective-C++.
142   if (!getLangOpts().CPlusPlus || getLangOpts().ObjC)
143     return;
144   // Ignore code inside extern "C" {} blocks.
145   if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context)
146            .empty())
147     return;
148   // Ignore code in .c files and headers included from them, even if they are
149   // compiled as C++.
150   if (getCurrentMainFile().endswith(".c"))
151     return;
152 
153   SourceManager &SM = *Result.SourceManager;
154 
155   // Ignore code in .c files #included in other files (which shouldn't be done,
156   // but people still do this for test and other purposes).
157   if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())).endswith(".c"))
158     return;
159 
160   // Leave type spelling exactly as it was (unlike
161   // getTypeAsWritten().getAsString() which would spell enum types 'enum X').
162   StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr);
163 
164   auto Diag =
165       diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
166 
167   auto ReplaceWithCast = [&](std::string CastText) {
168     const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
169     if (!isa<ParenExpr>(SubExpr) && !isa<CXXFunctionalCastExpr>(CastExpr)) {
170       CastText.push_back('(');
171       Diag << FixItHint::CreateInsertion(
172           Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM,
173                                      getLangOpts()),
174           ")");
175     }
176     Diag << FixItHint::CreateReplacement(ReplaceRange, CastText);
177   };
178   auto ReplaceWithNamedCast = [&](StringRef CastType) {
179     Diag << CastType;
180     ReplaceWithCast((CastType + "<" + DestTypeString + ">").str());
181   };
182   auto ReplaceWithConstructorCall = [&]() {
183     Diag << "constructor call syntax";
184     // FIXME: Validate DestTypeString, maybe.
185     ReplaceWithCast(DestTypeString.str());
186   };
187   // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics.
188   switch (CastExpr->getCastKind()) {
189   case CK_FunctionToPointerDecay:
190     ReplaceWithNamedCast("static_cast");
191     return;
192   case CK_ConstructorConversion:
193     if (ConstructorCast) {
194       ReplaceWithConstructorCall();
195     } else {
196       ReplaceWithNamedCast("static_cast");
197     }
198     return;
199   case CK_NoOp:
200     if (FnToFnCast) {
201       ReplaceWithNamedCast("static_cast");
202       return;
203     }
204     if (SourceType == DestType) {
205       Diag << "static_cast (if needed, the cast may be redundant)";
206       ReplaceWithCast(("static_cast<" + DestTypeString + ">").str());
207       return;
208     }
209     if (needsConstCast(SourceType, DestType) &&
210         pointedUnqualifiedTypesAreEqual(SourceType, DestType)) {
211       ReplaceWithNamedCast("const_cast");
212       return;
213     }
214     if (ConstructorCast) {
215       ReplaceWithConstructorCall();
216       return;
217     }
218     if (DestType->isReferenceType()) {
219       QualType Dest = DestType.getNonReferenceType();
220       QualType Source = SourceType.getNonReferenceType();
221       if (Source == Dest.withConst() ||
222           SourceType.getNonReferenceType() == DestType.getNonReferenceType()) {
223         ReplaceWithNamedCast("const_cast");
224         return;
225       }
226       break;
227     }
228     LLVM_FALLTHROUGH;
229   case clang::CK_IntegralCast:
230     // Convert integral and no-op casts between builtin types and enums to
231     // static_cast. A cast from enum to integer may be unnecessary, but it's
232     // still retained.
233     if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) &&
234         (DestType->isBuiltinType() || DestType->isEnumeralType())) {
235       ReplaceWithNamedCast("static_cast");
236       return;
237     }
238     break;
239   case CK_BitCast:
240     // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement.
241     if (!needsConstCast(SourceType, DestType)) {
242       if (SourceType->isVoidPointerType())
243         ReplaceWithNamedCast("static_cast");
244       else
245         ReplaceWithNamedCast("reinterpret_cast");
246       return;
247     }
248     break;
249   default:
250     break;
251   }
252 
253   Diag << "static_cast/const_cast/reinterpret_cast";
254 }
255 
256 } // namespace readability
257 } // namespace google
258 } // namespace tidy
259 } // namespace clang
260