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