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